2010/12/02

Alix上のCouchDBに郵便番号データを入力してみた

LDAPの時も使ったCSV形式で配布されている郵便番号データをCouchDBに入力してみました。

今回の使い方は初期に大量のデータを入力して、使用フェーズではもっぱら参照だけになる、という使い方になるので、CouchDBらしくないとは思ったのですが、Viewを定義してListやShowをテストするのに使おうと思います。

準備したデータの量やハードウェアなどについて

郵便番号データは12万2千件余りで、CouchDBに入力した後のデータサイズはおよそ100MBです。

まずは結論、困った事や思った事

_bulk_docsを使って大量のデータを入力しようとしたのですが、30件程度、サイズで4KB以内程度でないと失敗しました。サイズに上限があるのかどうかは、はっきりしていません。

Alixは256MBしかメモリがないですし、少し特殊なハードウェアですからVMWare上のUbuntu Server 10.04 LTSでメモリを増やして確認しましたが、やはり似たような挙動になりました。

その他にはApacheをSSLで接続するReverse Proxyにした場合のデータスループットは、Stunnelを利用した場合と比較すると、およそ2倍ちょっと遅いという結果になりました。(15分→35分)

これは単純にシングルコア、かつ256MBのメモリで動くAlixにSSLとReverse Proxyが負荷を与えたのかなぁと考えています。 ただ手元のデータでは、ここら辺の考えを証明できてはいません。

最後に全文書を対象に郵便番号をキー(Key)に、文書を値(Value)にするシンプルなViewを追加した場合に、100MBのデータに対して80MB程度のファイルが作成されています。

ここら辺の動きはLDAPと比べても、あまり変わりのないところかなと思いました。

データの加工とCouchDBへの入力

以前LDAPを相手にした時のようなスキーマは必要ないので適当なデータ構造をでっちあげて、次のようなスクリプトを組みました。

カレントディレクトリに置いたcouchdb.rbには前回も使ったCouchDB Wikiにあるサンプルを改造したものを使っています。

あらかじめ /postalにDBを作成しておき、次のようにスクリプトを実行しています。

$ nkf -w ken_all.csv > ken_all.utf8.csv
$ ./initdb.rb ken_all.utf8.csv

initdb.rbスクリプトファイル

#!/usr/bin/env ruby1.9
# -*- coding: utf-8 -*-
##
## CouchDB Wiki : Transactional Semantics with Bulk Updates
## http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API
##

$:.unshift File.dirname($0)

require 'csv'
require 'couchdb'
require 'json'

couch = Couch::Server.new("couchdb.example.org","5984",
         {'user' => 'admin','password' => "xxxxxxxxxxxx"})

uri = '/postal/_bulk_docs'
num = 0
bulk_docs = Hash.new
bulk_docs['non_atomic'] = true
bulk_docs['docs'] = Array.new
entry = Hash.new
CSV.open(ARGV[0], 'r').each do |row|
  ## prepare output format
  entry = Hash.new
  entry['_id'] = "entry." + num.to_s
  entry['num'] = num
  entry['id'] = row[0]
  entry['pref'] = row[6]
  entry['pref_kana'] = row[3]
  entry['city'] = row[7]
  entry['city_kana'] = row[4]
  entry['street'] = row[8]
  entry['street_kana'] = row[5]
  entry['code'] = row[2]
  entry['code_prefix'] = row[1]
  entry['other'] = [row[9],row[10],row[11],row[12],row[13],row[14]]

  bulk_docs['docs'] << entry
  num += 1

  if num % 10 == 9
    res = ""
    while not res.kind_of?(Net::HTTPSuccess)
      begin
        res = couch.post(uri, bulk_docs.to_json)
        if res.kind_of?(Net::HTTPUnauthorized)
          print "num: #{num}, #{res.to_s}\n"
        end
      rescue
        p $!
        p bulk_docs.to_json
        res = ""
      end
    end
    bulk_docs['docs'] = Array.new
  end
end

res = ""
while not res.kind_of?(Net::HTTPSuccess)
  begin
    res = couch.post(uri, bulk_docs.to_json)
    print "num: #{num}, #{res.to_s}\n"
  rescue
    p $!
    res = ""
  end
end

先頭部分は#!/usr/bin/rubyやら/usr/local/bin/rubyやら適当に変更してください。

それとcouch = Couch::Server.new("couchdb.example.org","5984",の行は、それぞれの環境に合せて修正してください。

ここまで終って適当にデータが入っているか確認だけしておきます。

$ curl -u admin:xxxxxxxxxxx http://couchdb.example.org:5984/postal/_all_docs?limit=10
{"total_rows":122971,"offset":0,"rows":[
{"id":"_design/all","key":"_design/all","value":{"rev":"1-5dfb6015c7dde046e055d54f02a2b8d3"}},
{"id":"entry.0","key":"entry.0","value":{"rev":"1-f53d3fc4f2e20aa9a26a642248d442d6"}},
...

出力1行目の total_rowsが12万件を越えているところを確認します。

Viewの追加

テストのために、いくつかの値をキーにする /postal/_design/all文書を加えました。

#!/usr/bin/env ruby1.9
# -*- coding: utf-8 -*-

$:.unshift File.dirname($0)

require 'csv'
require 'couchdb'
require 'json'
require 'uri'

couch = Couch::Server.new("couchdb.example.org","5984",
         {'user' => 'admin','password' => "xxxxxxxxxxxx"})

uri = '/postal/_design/all'

json = Hash.new
## check existing document
begin
  res = couch.get(URI.escape(uri))
  json = JSON.parse(res.body)
rescue
end
json = Hash.new if json.has_key?("error")
p json

## override existing document or write new document
json['language'] = 'javascript'
json['views'] = Hash.new if not json['views'].kind_of?(Hash)
json['views']['by-code'] = Hash.new if not json['views']['by-code'].kind_of?(Hash)
json['views']['by-code']['map'] = <<-MAP
function(doc) {
  if(doc._id.indexOf('entry.') == 0) {
    emit(doc.code, null)
  }
}
MAP
json['views']['by-pref'] = Hash.new if not json['views']['by-pref'].kind_of?(Hash)
json['views']['by-pref']['map'] = <<-MAP
function(doc) {
  if(doc._id.indexOf('entry.') == 0) {
    emit(doc.pref, null)
  }
}
MAP
json['views']['by-street'] = Hash.new if not json['views']['by-street'].kind_of?(Hash)
json['views']['by-street']['map'] = <<-MAP
function(doc) {
  if(doc._id.indexOf('entry.') == 0) {
    emit(doc.street, null)
  }
}
MAP
json['views']['by-code_prefix'] = Hash.new if not json['views']['by-code_prefix'].kind_of?(Hash)
json['views']['by-code_prefix']['map'] = <<-MAP
function(doc) {
  if(doc._id.indexOf('entry.') == 0) {
    emit(doc.code_prefix, null)
  }
}
MAP
res = couch.put(uri, json.to_json)
puts res

ここでも、検索が成功するか確認しておきます。

$ curl -u admin:xxxxxxxxxxx http://couchdb.example.org:5984/postal/_design/all/_view/by-code?key="9650000"&include_docs=true'
{"total_rows":122970,"offset":115550,"rows":[
{"id":"entry.20279","key":"9650000","value":null,"doc":{"_id":"entry.20279","_rev":"1-efbdc6e3516306c14e784eacf30412b8","num":20279,"id":"07202","pref":"\u798f\u5cf6\u770c","pref_kana":"\u30d5\u30af\u30b7\u30de\u30b1\u30f3","city":"\u4f1a\u6d25\u82e5\u677e\u5e02","city_kana":"\u30a2\u30a4\u30c5\u30ef\u30ab\u30de\u30c4\u30b7","street":"\u4ee5\u4e0b\u306b\u63b2\u8f09\u304c\u306a\u3044\u5834\u5408","street_kana":"\u30a4\u30ab\u30cb\u30b1\u30a4\u30b5\u30a4\u30ac\u30ca\u30a4\u30d0\u30a2\u30a4","code":"9650000","code_prefix":"965  ","other":["0","0","0","0","0","0"]}}
]}

このviewを登録すると、だいたい160MBほどの中間ファイルが/usr/local/var/lib/couchdb/.postal_design/に生成されます。

問題なのは時間で、これで1時間半ぐらいでしょうか。 もっともファイルが出来てしまえば動きには問題なくて、検索結果を素早く得る事ができています。

これを元ネタに簡単なWebページを作ってみようと思います。

この記事で取り上げた品々

0 件のコメント: