A Peak Never Ending !

Nginx Proxy_storeディレクティブ

恐らくあまり使いたい人はいないかなと思うディレクティブだけど使いたいと思った時に少し詰まったのでメモ。
どんなことが出来るのかというとリクエストを受けた際に

  • ローカルのディレクトリをみる
  • ファイルが存在していればそのままレスポンスを返す
  • ファイルがない場合はupstreamにリクエストを流し見つかった場合はローカルに保存しつつ返す

といった事ができる。
nginx wikiに「To be clear proxy_store is not a cache, it’s rather mirror on demand.」とあるように 要はリクエストを受けたファイルのみコピーしてミラーを作るような事ができる。

設定例はこんな感じ。

nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
location /images/ {
    root                 /data/www;
    error_page           404 = @fetch;
}

location @fetch {
    internal;

    proxy_buffering      on;
    proxy_pass           http://backend;
    proxy_store          on;
    proxy_store_access   user:rw  group:rw  all:r;
    proxy_temp_path      /data/temp;

    root                 /data/www;
}

1つめのlocationは/images/配下にアクセスが来た際に/data/wwwディレクトリの下にファイルがなければ(404) @fetch locationに移動する。
2つめの@fetch locationではリバースプロキシとしてbackendホストにリクエストをフォワードしてファイルがあった場合は/data/wwwにパーミッションを 664にして保存するといった感じ。
proxy_bufferingをoff(デフォルトはon)にしてたせいでファイルが保存されなかったけど、proxy_storeの説明には特に記述がなかったため 自分で試したときに結局nginxのソースまで追うはめになってしまった。
まあいずれにせよあんまり使いたいと思う人はいないんじゃないかなぁ。

Mongodb Cappedコレクションを試す

MongoDBではロギングに向いてるRRDライクな固定長サイズのcapped collectionという物が定義できるらしいので試してみた。

Cappedコレクション

cappedコレクションを作成してみる。sizeオプションでmaxのデータサイズ, maxオプションで最大のDocument数が指定できるみたい。 下の場合だと1000 byte?, 5 Documentのどちらかの上限に達するまで追加できる感じ。

1
2
> use example
> db.createCollection("capped", {capped: true, size:1000, max: 5})

5個アイテムを入れてみる。

1
2
3
4
5
6
7
8
9
10
11
> db.capped.insert( {col:1} )
> db.capped.insert( {col:2} )
> db.capped.insert( {col:3} )
> db.capped.insert( {col:4} )
> db.capped.insert( {col:5} )
> db.capped.find()
{ "_id" : ObjectId("4f61e9ad3d52866fbe626969"), "col" : 1 }
{ "_id" : ObjectId("4f61e9af3d52866fbe62696a"), "col" : 2 }
{ "_id" : ObjectId("4f61e9b23d52866fbe62696b"), "col" : 3 }
{ "_id" : ObjectId("4f61e9b43d52866fbe62696c"), "col" : 4 }
{ "_id" : ObjectId("4f61e9b63d52866fbe62696d"), "col" : 5 }

実際登録したデータのサイズがどれくらいか確認するには以下のコマンドを打てば良い。

1
2
> db.capped.validate().datasize
180

6個目のアイテムをいれてみる。これでMaxの値を超えるので最初のレコードが上書きされるはず。

1
2
3
4
5
6
7
> db.capped.insert( {col:6} )
> db.capped.find()
{ "_id" : ObjectId("4f61e9af3d52866fbe62696a"), "col" : 2 }
{ "_id" : ObjectId("4f61e9b23d52866fbe62696b"), "col" : 3 }
{ "_id" : ObjectId("4f61e9b43d52866fbe62696c"), "col" : 4 }
{ "_id" : ObjectId("4f61e9b63d52866fbe62696d"), "col" : 5 }
{ "_id" : ObjectId("4f61e9d93d52866fbe62696e"), "col" : 6 }

findして見る限り一番最後のレコードが常に最新レコードとなる模様。 なので最新のレコードから順にとりだすには逆順にsortすれば良いみたい。

1
2
3
4
5
6
> db.capped.find().sort( {$natural:-1} )
{ "_id" : ObjectId("4f61e9d93d52866fbe62696e"), "col" : 6 }
{ "_id" : ObjectId("4f61e9b63d52866fbe62696d"), "col" : 5 }
{ "_id" : ObjectId("4f61e9b43d52866fbe62696c"), "col" : 4 }
{ "_id" : ObjectId("4f61e9b23d52866fbe62696b"), "col" : 3 }
{ "_id" : ObjectId("4f61e9af3d52866fbe62696a"), "col" : 2 }

ということでlimit使うと「tail -n 3」みたいなことができる。

1
2
3
4
> db.capped.find().sort( {$natural:-1} ).limit(3)
{ "_id" : ObjectId("4f61e9d93d52866fbe62696e"), "col" : 6 }
{ "_id" : ObjectId("4f61e9b63d52866fbe62696d"), "col" : 5 }
{ "_id" : ObjectId("4f61e9b43d52866fbe62696c"), "col" : 4 }

同様にMongoidで「tail -n 3」みたいな事をしたい場合は「desc」と「limit」メソッドを使えば良い。 また前回mongoidを使った時と違ってコレクション名を明示的に指定したいので「self.collection_name」を使って指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require 'mongoid'
require 'pp'

class Item
  include Mongoid::Document
  self.collection_name = 'capped'

  field :col, :type => Float
end

Mongoid.configure do |conf|
conf.master = Mongo::Connection.new('localhost', 27017).db('example')
end

items = Item.desc(:_id).limit(3)
  items.each do |item|
  puts "#{item.col}"
  end
end

Mongoid使ってみた

mongooseを使ってみた流れで今度はrubyでmongoidを使ってMongoDBにアクセスしてみた。rubyもさほど触ったことないけどjsよりはなんかなじむ。 mongoidはMongoDB (ver 2.0.0) 以降しかサポートしてないみたいだけど、debianのパッケージで入るのは1.4.x系なので公式のリポジトリから2.0系に入れ替えてみた。

2.0系をインストールする

/etc/apt/sources.list
1
deb http://downloads-distro.mongodb.org/repo/debian-sysvinit dist 10gen
/etc/apt/sources.list
1
2
3
4
5
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
sudo aptitude remove mongodb
sudo aptitude update
sudo aptitude install mongodb-10gen

mongoidテスト

1
gem install mongoid bson_ext

前回mongooseで試したデータを使いまわし。すごくお手軽。

memo.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require 'mongoid'

class Memo
  include Mongoid::Document

  field :title, :type => String
  field :body, :type => String
end

Mongoid.configure do |conf|
  conf.master = Mongo::Connection.new('localhost', 27017).db('memo')
end

memos = Memo.all
memos.each do |memo|
  puts "#{memo.title} / #{memo.body}"
end

Mongooseを使ってmongodbにアクセスしてみる

前回mongodbを触ってみたので、次はmongodbのデータをwebsocket経由で表示できるようにしたいな〜 と思って調べたところnode.js + mongodb + socket.ioで出来そうな気がしたのでnode.js + mongoose をまず試してみた。mongooseはmongodbを扱うためのライブラリでsocket.ioはwebsocket (+未対応ブラウザでのエミュレーション)を実現してくれるライブラリらしい。 まずは、node.js自体動かす環境がないので環境の構築から。ちなみにnode.jsは騒がれた頃に少し触った程度 でjavascriptも全然書いたことなかったり。

node.jsの準備

node.jsのインストールはnvm、ライブラリ群はnpmを使って管理すると楽らしいので、まずはnvmをインストール。 nvmをインストールするとnpmも入るらしい。

nvmインストール
1
2
3
4
git clone git://github.com/creationix/nvm.git ~/.node
. ~/.node/nvm.sh
nvm install v0.4.12
nvm use v0.4.12

.bashrcに以下を追加

~/.bashrc
1
2
. ~/.node/nvm.sh
nvm use v0.4.12

mongooseをインストールしてアクセス

まずはmongooseをインストール

mongooseインストール
1
npm install mongoose

mongooseを使ってデータを取り出してconsoleに出力するテストプログラムを書く。 流れ的にはmongoに保存するデータの構造を定義するSchemaを作って、mongooseにmodelとして登録する感じかな。 下の場合だと、title,bodyをキーとしてもつスキーマを定義してMemoモデルとして登録。その後memoデータベース に接続してfindを行うといった具合。ちなみにコレクション名を指定してる場所がないけど、mongooseはモデル名を lower caseにしてsを付けた名前をコレクション名として使うっぽい(この場合はmemosがコレクション名になる) もしかして明示的に指定できるのかもしれないけど、その辺はまだ調べてなくてコレクション名がmemosになるのも mongoose経由で一度データを登録してみて、mongodbを直接叩いてわかった感じ。

memo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
 * Mongooseでデータを取り出して表示するテスト
 */
var mongoose = require('mongoose');

// スキーマ定義
var MemoSchema = new mongoose.Schema({
  title: String,
  body: String
});
// モデルとして登録
var Memo = mongoose.model('Memo', MemoSchema);

// mongodbに接続
mongoose.connect('mongodb://localhost:27017/memo', // memoの部分はデータベース名
  // コールバックでエラー時の処理が書けるみたい。
  function(err) {
    if (err) {
      console.log(err);
    } else {
      console.log('connection success!');
    }
  }
);

// findしてコンソールに出力
Memo.find({}, function(err, docs) {
  if(!err) {
    console.log("num of item => " + docs.length)
    for (var i = 0; i < docs.length; i++ ) {
      console.log(docs[i]);
    }
    mongoose.disconnect()  // mongodbへの接続を切断
    process.exit()         // node.js終了
  } else {
    console.log("find error")
  }
});

mongodbにサンプルデータを入れる。mongoose経由で入れても良いけど、mongodb自体もちょっと触り慣れたいのであえて直接いれてみたりしてる。 (さっき言ったとおり一回mongoose経由で入れるテストはしたけど)

1
2
3
> use memo
switched to db memo
> db.memos.save({title:'test title', body:'test body'})

作ったスクリプトを実行して動作確認

1
2
3
4
5
6
7
8
node memo.js

# 出力
connection success!
num of item => 1
{ _id: 4f54a721601f550c008585e4,
  title: 'test title',
  body: 'test body' }

こんな感じ。

Mongodbインストールしてみた

mongodb使ってみようかな〜と思ってたけど触ったことなかったのでどんなこと出来るかちょっと試してみた。 昨今みんながワイワイ言ってるだけあって便利そうだな〜といった印象。 使えそうなので次はfluentd -> mongo -> railsとかwebsoketってやってサーバのリソースモニタを試してみるつもり。 触ったことない人は下のリンクを順番にたどると感じがつかめると思う。

Quick Start Unix

インストール(ubuntu)

1
sudo aptitude install mongodb

デフォルトの設定はこうなってた

/etc/mongodb.conf
1
2
3
4
5
6
7
8
9
10
# This is an config file for MongoDB master daemon mongod
# it is passed to mongod as --config parameter

logpath = /var/log/mongodb/mongod.log
dbpath = /var/lib/mongodb/

# use 'true' for options that don't take an argument
logappend = true
bind_ip = 127.0.0.1
#noauth = true

mongoコマンドを実行するとmongodbを操作するシェルが立ち上がる。 データ保存して表示するテストをしてみる。mysqlと同じように複数のdatabaseを作成することができて、 その中にtableに似たcollectionっていうのが作れるらしい。デフォルトだとtestデータベースに 接続する様子。下の例だとexampleのところがcollectionになるみたい。

1
2
3
4
5
> db.example.save( {a:1} )
> db.example.save( {b:2} )
> db.example.find()
{ "_id" : ObjectId("4f50783e7dcfb0a8e8fc8a32"), "a" : 1 }
{ "_id" : ObjectId("4f507dd67dcfb0a8e8fc8a33"), "b" : 2 }

とりあずhelpすると良いっぽいので叩く。

1
2
3
4
5
6
7
8
9
10
11
12
> help
HELP
    show dbs                     show database names
    show collections             show collections in current database
    show users                   show users in current database
    show profile                 show most recent system.profile entries with time >= 1ms
    use <db name>                set curent database to <db name>
    db.help()                    help on DB methods
    db.foo.help()                help on collection methods
    db.foo.find()                list objects in collection foo
    db.foo.find( { a : 1 } )     list objects in foo where a == 1
    it                           result of the last line evaluated; use to further iterate

めぼしいものをちょっと叩いてみる。show dbsしてみる。

1
2
3
4
> show dbs
admin
local
test

show collectionsしてみる。スキーマレスなだけあってmysqlみたいに事前にcollectionを つくらなくても値を入れるとcollectionが出来るのかexampleがある。

1
2
3
> show collections
example
system.indexes

show usersしても何もでない。db.example.find({a:1})してみる

1
2
> db.example.find({a:1})
{ "_id" : ObjectId("4f50783e7dcfb0a8e8fc8a32"), "a" : 1 }

変数使える。

1
2
3
4
5
6
7
> j = { name : "mongo" };
{ "name" : "mongo" }
> db.example.save(j)
> db.example.find()
{ "_id" : ObjectId("4f50783e7dcfb0a8e8fc8a32"), "a" : 1 }
{ "_id" : ObjectId("4f507dd67dcfb0a8e8fc8a33"), "b" : 2 }
{ "_id" : ObjectId("4f50817d7dcfb0a8e8fc8a34"), "name" : "mongo" }

forまで使えちゃう

1
2
3
4
5
6
7
8
> for (var i = 1; i <= 3; i++) db.example.save({x : 1, y : i});
> db.example.find()
{ "_id" : ObjectId("4f50783e7dcfb0a8e8fc8a32"), "a" : 1 }
{ "_id" : ObjectId("4f507dd67dcfb0a8e8fc8a33"), "b" : 2 }
{ "_id" : ObjectId("4f50817d7dcfb0a8e8fc8a34"), "name" : "mongo" }
{ "_id" : ObjectId("4f50833d7dcfb0a8e8fc8a38"), "x" : 1, "y" : 1 }
{ "_id" : ObjectId("4f50833d7dcfb0a8e8fc8a39"), "x" : 1, "y" : 2 }
{ "_id" : ObjectId("4f50833d7dcfb0a8e8fc8a3a"), "x" : 1, "y" : 3 }

特定のキーが特定の値のものだけ取り出す

1
2
3
4
> db.example.find( {'x':1} )
{ "_id" : ObjectId("4f50833d7dcfb0a8e8fc8a38"), "x" : 1, "y" : 1 }
{ "_id" : ObjectId("4f50833d7dcfb0a8e8fc8a39"), "x" : 1, "y" : 2 }
{ "_id" : ObjectId("4f50833d7dcfb0a8e8fc8a3a"), "x" : 1, "y" : 3 }

メソッドチェーンしてcountとかも出来るみたい

1
2
> db.example.find({'x':1}).count()
3

countの他に使えるメソッドはhelpで確認できる模様

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
> db.example.help
function () {
    print("DBCollection help");
    print("\tdb.foo.count()");
    print("\tdb.foo.dataSize()");
    print("\tdb.foo.distinct( key ) - eg. db.foo.distinct( 'x' )");
    print("\tdb.foo.drop() drop the collection");
    print("\tdb.foo.dropIndex(name)");
    print("\tdb.foo.dropIndexes()");
    print("\tdb.foo.ensureIndex(keypattern,options) - options should be an object with these possible fields: name, unique, dropDups");
    print("\tdb.foo.find( [query] , [fields]) - first parameter is an optional query filter. second parameter is optional set of fields to return.");
    print("\t                                   e.g. db.foo.find( { x : 77 } , { name : 1 , x : 1 } )");
    print("\tdb.foo.find(...).count()");
    print("\tdb.foo.find(...).limit(n)");
    print("\tdb.foo.find(...).skip(n)");
    print("\tdb.foo.find(...).sort(...)");
    print("\tdb.foo.findOne([query])");
    print("\tdb.foo.getDB() get DB object associated with collection");
    print("\tdb.foo.getIndexes()");
    print("\tdb.foo.group( { key : ..., initial: ..., reduce : ...[, cond: ...] } )");
    print("\tdb.foo.mapReduce( mapFunction , reduceFunction , <optional params> )");
    print("\tdb.foo.remove(query)");
    print("\tdb.foo.renameCollection( newName ) renames the collection");
    print("\tdb.foo.save(obj)");
    print("\tdb.foo.stats()");
    print("\tdb.foo.storageSize() - includes free space allocated to this collection");
    print("\tdb.foo.totalIndexSize() - size in bytes of all the indexes");
    print("\tdb.foo.totalSize() - storage allocated for all data and indexes");
    print("\tdb.foo.update(query, object[, upsert_bool])");
    print("\tdb.foo.validate() - SLOW");
    print("\tdb.foo.getShardVersion() - only for use with sharding");

ruby-buildでruby-1.9.3p125をインストール

ruby-buildでシステム自体にruby-1.9.3p125を入れるお話。 シンボリックリンクいけてないけど、rootでrbenvもいかがなものか。 なんか良い方法ないのかな。素直にPATH通すのが良いかなぁ。

1
2
3
4
5
6
cd /usr/local/src
git clone git://github.com/sstephenson/ruby-build.git
cd ruby-build
./install.sh
ruby-build 1.9.3-p125 /usr/local/ruby-1.9.3-p125
for i in `ls /usr/local/ruby-1.9.3-p125/bin/`; do ln -s /usr/local/ruby-1.9.3-p125/bin/$i /usr/local/bin/$i; done

Chef-soloを使ってみる

そろそろ本腰いれてchef使ってみようかなと思ったのでちょっと試してみる。 ちょうどさくらのVPSを2つ借りてて、bashからzshに移ろうかな〜と思ってたのでこいつをchefでやってみる。 chefは一言でいうとrecipe(設定のルール)を作ってrole(どのcookbookを使うか)をnode(管理対象ホスト)に割り当てるって感じみたい。 クライアントだけで動かすchef-soloと管理用のサーバを用意するchef-serverとがあるみたいだけど、 chef-serverはもっぱら面倒という噂を聞くのでchef-soloを使ってみた。

zshをchefでインストール

インストールして雛形を取ってくる。ちなみにrootでやらかしてます。

1
2
3
4
gem install chef
git clone git://github.com/opscode/chef-repo.git
cd chef-repo
mkdir .chef

chef-soloを実行するときのconfigを準備する

/root/chef-repo/.chef/solo.rb
1
2
file_cache_path "/tmp/chef-solo"
cookbook_path "/root/chef-repo/cookbooks"

recipeを用意する。recipeはcookbook単位で管理されるみたいなのでcookbookをまず作る。 cookbookはapacheとかzshとかパッケージ単位で作るのが一般的みたい。 とりあえずzshインストールしたいのでzshていう名前のcookbookを作ってみる。

zshクックブックを作成
1
rake new_cookbook COOKBOOK=zsh

つづいてインストール用のレシピを作成

/root/chef-repo/cookbooks/zsh/recipes/default.rb
1
2
3
package "zsh" do
  action :install
end

適用するレシピを定義する

/root/chef-repo/.chef/chef.json
1
2
3
{
    "run_list": [ "recipe[zsh]" ]
}

chef-soloコマンドでレシピを実行する

1
chef-solo -c /root/chef-repo/.chef/solo.rb -j /root/chef-repo/.chef/chef.json

インストール出来た。基礎はわかったので、もう少し細かい機能を追うかな。

おまけ

Shaperdを使ってポート単位で転送量を制限する

solrとか使っててIndexサイズが肥大化すると再生成して全台にレプリケーションとかする時に solrが全力で転送とかしちゃうものだから、ネットワークとかIOが涙目になる。そうじゃなくても ちょっとscpでログ転送とかしたいけど、サイズ大きいし、ゆるゆる転送したいな〜とか思う事がある。 そんなときにDebianだとshaperdってやつが使えるらしいので他のサーバにscpする時に転送量に制限をかける テストをしてみたメモ。アドレスで絞ったりもできるらしい。同じ要領でsolrマスターからスレーブに転送する時 に節度を保った転送が出来れば良い感じになるんじゃないだろうかという狙い(未確認)

準備

インストール。shaperdの他にiptablesでパケットをキューにつっこむ必要があるみたいなので、 そのためのモジュールも追加。

1
2
sudo modprobe ip_queue
sudo aptitude install shaperd

shaperd設定。configのサンプルは/usr/share/doc/shaperd/examples/にある

/etc/shaperd/shaperd.conf
1
2
3
4
5
6
7
8
9
10
11
12
log level = info
packet forwarding = ipq
daemon = yes
pidfile = /var/run/shaperd.pid

class all {
    ipv4 classifier proto=all
    bandwidth = 1.0 kbyte/s
    queue limits = 10 kb 10 packets
    ipv4 classifier prio=1 proto=tcp \
        out_if=eth0 dport=22
}

iptablesにもキューに入ってもらう設定がいるので入れないとダメなんだけど、

  • shaperd起動 -> iptables追加 -> iptables削除 -> shaperd停止

っていう手順を踏まないとパケットがキューに入ったまま出てこなくて涙目になるみたいなんで気をつける。 今回みたいに22番ポートが絡む設定とかで失敗するとssh出来なくなって悲しくなる。怖い人は80番とかで 試してみるといいと思う。

1
2
sudo /etc/init.d/shaperd start
sudo iptables -A OUTPUT -p tcp --dport 22 -j QUEUE

テスト

なんか大きいファイルをどこかに向けてscpする。1kb/s に絞る設定なのでちょっとしたファイルでも効果を 感じられると思う。うまくいかない場合は下みたいなコマンドでキューに入るルールにマッチしてるか確認すると良いと思う。

1
watch -n 1 -d 'iptables -n -L -v'

rails3.1でフォームのラベルを日本語化する手順

rails(3.1)でフォームを作ってる時にヘルパーのlabelメソッドを使ってるとタイトルが英語で出ちゃったり、 エラーが英語で出ちゃったりで日本語にしたいな〜て思った時にとりあえず日本語にする方法

  • githubから以下のファイルを取ってきてconfig/localesに置く https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml

  • config/application.rbに以下の設定を追加する

config/application.rb
1
config.i18n.default_locale = :ja
  • rails serverとかでデーモンを起動してる場合は一回再起動

さらに別々のモデルに同じ名前のカラムがあったとして、モデル(フォーム)毎にlabelメソッドで表示する名前を別々にしたい場合があると思います。 例えば、 * userモデルのテーブルのnameカラム => 氏名 * groupモデルのテーブルのnameカラム => グループ名 とか、この場合はja.yamlに以下のように書けばok

config/locales/ja.yml
1
2
3
4
5
6
activerecord:
  attributes:
    user:
      name: '氏名'
    group:
      name: 'グループ名'

Rails(3.1)でDB操作色々

Rails3辺りからDBの操作周りでArelが使われるようになったりで書き方が良く分からなかったり、忘れたりするのでメモ。Gistにあげてちょびちょびメモってます。参考になるページはこのあたり。