Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

はじめてのCouch db

3,088 views

Published on

  • Be the first to comment

はじめてのCouch db

  1. 1. はじめてのCouchDB 黒田英二 http://blog.kuroda.me/eiji/ 10 May 2012 1
  2. 2. CouchDBとはApache CouchDBNoSQLJSONを格納Erlangで書かれてるhttp://couchdb.apache.org/ 2
  3. 3. ところでNoSQLSQLインターフェイスを持たないDBスキーマレスモデルの結合(JOIN)が出来ないトランザクションがない水平スケーラビリティー(スケールアウト)しやすいキーバリュー型 Cassandra, HBase, DynamoDBドキュメント指向 MongoDB, CouchDB 3
  4. 4. CouchDBの特徴4
  5. 5. 主だった特徴ドキュメント指向型テーブルの概念が無いプロトコルにHTTPを使う(CRUD)最新のバージョンは1.20クエリはMap/Reduceベースマルチ・マスタのレプリケーション対応 5
  6. 6. 向いてるもの辞書、図鑑、レシピ、名簿テーブルの概念が必要ないオブジェクト間で親子のような関連性が無いオブジェクトにタグのようなツリー構造を含むトランザクションの必要性が低い更新頻度が低い 6
  7. 7. 不向きなもの操作履歴、業務系データ、SNSのデータテーブルの概念が必要オブジェクト間の関連が複雑トランザクションが必要更新頻度が高い 7
  8. 8. インストール8
  9. 9. Mac OSX 10.7+必要な環境 Xcode4.3+のCommand Line Tools ruby 1.9.3brewでインストール# 以前にCouchDBをインストールしてたらbrew remove --force openssl erlang couchdb icu4c spidermonkey nspr# インストールbrew updatebrew install erlang --no-docsbrew install couchdb 9
  10. 10. 起動方法 #初めてインストールしたら    mkdir -p ~/Library/LaunchAgents    cp /usr/local/Cellar/couchdb/1.2.0/Library/LaunchDaemons/org.apache.couchdb.plist ~/Library/LaunchAgents/    launchctl load -w ~/Library/LaunchAgents/org.apache.couchdb.plist #既にインストールしてたら    launchctl unload -w ~/Library/LaunchAgents/org.apache.couchdb.plist    cp /usr/local/Cellar/couchdb/1.2.0/Library/LaunchDaemons/org.apache.couchdb.plist ~/Library/LaunchAgents/    launchctl load -w ~/Library/LaunchAgents/org.apache.couchdb.plist #自動起動は下記    sudo launchctl list org.apache.couchdb >/dev/null 2>&1 &&       sudo launchctl unload -w /Library/LaunchDaemons/org.apache.couchdb.plist    sudo cp /usr/local/Cellar/couchdb/1.2.0/Library/LaunchDaemons/org.apache.couchdb.plist /Library/LaunchDaemons/    sudo launchctl load -w /Library/LaunchDaemons/org.apache.couchdb.plist #もしくは、下記で    couchdb 10
  11. 11. Futon+基本API11
  12. 12. FutonWebベースのDB管理ツールデフォルトでインストールされる全DB操作可能 12
  13. 13. APIのURL表記について太字のURLはAPIの例を記述Method URL [JSON] で記述URLの次行にJSONがある場合はレスポンスを示すPUT /members curl -X PUT ‘http://localhost:5984/members’PUT /members {“name”:”eiji”} curl -X PUT -d ‘{“name”:”eiji”}’ ‘http://...’curlでJSONをPOST/PUTする時は次のオプションが必要 -H Content-type:application/json 13
  14. 14. DBを作るDB名に使える文字 アルファベットの小文字 数字 _,$,(,),+,-,/ DB名がmembersの 場合PUT /members 14
  15. 15. DBの情報取得テーブルの概念は無いドキュメント(レコード)操作デザインの操作(後述) DB名がmembersの 場合GET /members 15
  16. 16. ドキュメントIDドキュメントを一意(※最新という意味で)に表すこの資料内ではDocIDと表記自動でDocIDが採番 or 手動で設定する
  17. 17. リビジョン番号 CouchDBのドキュメントは削除されず追加のみ (※1) DocID+リビジョン番号(RevID)で一意 DocIDのみ取得できるドキュメントは、最新のもの(※2) “id”:”eiji”, “name”:”eiji”, “rev”:”1-123” 新規追加時のDoc “id”:”eiji”, “name”:”test”, “rev”:”2-abc” 1回目の更新 更新や削除では、 “id”:”eiji”, “name”:”kuroda”, “rev”:”3-efg” DocID+RevIDを指定 2回目の更新=最新※1 空間最適化のために、DB別に古いリビジョンを物理削除するコマンドがある※2 コンフリクトが発生してない場合 17
  18. 18. ドキュメント追加POSTはDocID自動採番PUTはDocID指定POST /members {“name”:”Eiji Kuroda”,”age”:40}{"ok":true,"id":"7544d...", "rev":"1-1e60813a..."} リビジョン番号はも DocIDが自動採番 採番 DocIDを”eiji”に指定PUT /members/eiji {“name”:”Eiji Kuroda”,”age”:40}{"ok":true,"id":"eiji","rev":"1-1e60813a..."} 18
  19. 19. 一覧取得futon上はDBの初期画面DocID,key,valueが返るvalueはリビジョン番号 _all_docsは予約語GET /members/_all_docs{"total_rows":2,"offset":0,"rows":[{"id":"7544...","key":"7544...","value"{"rev":"1-1e60813c6ca45903.."}},{"id":"eiji","key":"eiji","value":{"rev":"1-96277b971e..."}}]}ソート順を逆にしたり、取得件数を指定するオプションあり 19
  20. 20. 1件取得Key値をクリックするfutonではフィールド別とJSON形式の2種類の画面がある DocIDが”eiji”のドキュメント取 得GET /members/eiji{"_id":"eiji","_rev":"3-33c49f...","name":"Eiji Kuroda","age":40}以前のバージョン(リビジョン)で取得できるオプションあり 20
  21. 21. 更新詳細画面でSourceをクリック仮に1フィールドだけ更新でもドキュメント全体を再設定リクエストにはリビジョン番号の指定が必須リビジョン番号が更新されるfutonでは1フィールドずつ追加も可能 リビジョンは必須、無ければ新規とみなされるPUT /members/eiji {“_rev”:”123ABC”,“name”:”Eiji Kuroda”,”age”:18}{"_id":"eiji","_rev":"4-33c49f...","name":"Eiji Kuroda","age":18} リ ビジョン番号が更新される 21
  22. 22. 削除詳細画面で削除を選択リクエストにはリビジョン番号の指定が必須削除にもリビジョン番号が設定される リビジョンは必須、無いとエラーDELETE /members/eiji?rev=123ABC{"ok":true,"id":"eiji","rev":"5-49c6c.."} リビジョン番号が付く 22
  23. 23. バルクAPI23
  24. 24. バルク取得複数のドキュメントを1回のリクエストで取得_all_docs に POST で DocIDs を配列で投げる戻ってくるJSON形式は_all_docsと同じPOST /members/_all_docs {“keys”:[“eiji”,”hoge”]}{"total_rows":2,"offset":0, "rows":[ {"id":"eiji","key":"eiji","value":{"rev":"abc123"}, {"id":"hoge","key":"hoge","value":{"rev":"efg987”} ]} 24
  25. 25. バルク追加、更新、削除複数のドキュメントの追加、更新、削除を1回のリクエストで可能更新、削除は各ドキュメントのリビジョン番号を指定次のようなファイル(data.json)があるとして 追加{"docs":[ {"_id":"hoge", "name":"hoge"}, 更新 {"_id":"eiji", "_rev":"abc123", "name":"eiji"}, {"_id":"kuroda", "_rev":"efg987", “_deleted”:true},]} 削除POST /members/_bulk_docs @data.json[ {"ok":true,"id":"hoge","rev":"1-c1b7ada1c9b31..."}, {"ok":true,"id":"eiji","rev":"2-c1b7ada1c9b31..."}, {"ok":true,"id":"kuroda","rev":"2-a84c9f34a74285a2..."}] 25
  26. 26. バルクAPI注意点デフォルトの動きでは、複数ドキュメントを追加した場合に、その中の1つがエラーになっていても他のドキュメントは正常に保存される上記ケースで1つも保存したくない場合は、all_or_nothingオプションを利用するall_or_nothingオプションはIDのコンフリクトチェックをしない同じIDで違う内容のドキュメントが登録できる→コンフリクト状態ただし、ドキュメントの内容が同じ場合はコンフリクト状態にならない 26
  27. 27. コンフリクト状態first.json second.json “id”:”abc”は{"docs":[ {"all_or_nothing":true,"docs":[ 中身が違うドキュメン {"_id":"abc", "name":"abc"}, {"_id":"abc", "name":"ABC"}, ト {"_id":"def", "name":"def"} {"_id":"def", "name":"def"}]} ]}POST /members/_balk_docs @first.json コンフリクトが発生したが[ {"ok":true,"id":"abc","rev":"1-123xxx"}, レスポンスは全件OK {"ok":true,"id":"def","rev":"1-123yyy"}] しかも、全部保存されてるPOST /members/_balk_docs @second.jso[ {"ok":true,"id":"abc","rev":"1-123zzz"}, {"ok":true,"id":"def","rev":"1-123yyy"}] conflicts=trueは 普通に取得可能 コンフリクトしてるかどうかをGET /members/abc{"_id":"abc","_rev":"1-123xxx","name":"abc"} 属性に含めて返すオプションGET /members/abc?conflicts=true{"_id":"abc","_rev":"1-123xxx","name":"abc","_conflicts":["1-123zzz"]}GET /members/def?conflicts=true コンフリクトしてる{"_id":"def","_rev":"1-123yyy","name":"def"} コンフリクトしてない 27
  28. 28. viewの概念28
  29. 29. viewとはクエリを発行したり、レポートを作ったりするのに使う機能1つのview属性は1つのmapを持ち、オプションで1つのreduceを持てるいくつかのview属性が1つのデザイン・ドキュメントに格納されるデザイン・ドキュメントも、他のドキュメントと同様の操作ができる デザイン・ドキュメントの名 view属性の名前実行時のURLは以下のようになる “/{dbName}/_design/{デザイン名}/_view/{view名}” ”_design/{デザイン名}” でひとつのドキュメントID
  30. 30. デザイン・ドキュメント _design/people “people”というデザイン名 “_design/people”というDocID“all”: “map”:”function(doc){..}” “all”というview名“over20”: “map”:”function(doc){..}” “map”:”function(doc){..}” 実際に表示する際のURL“total_age”: GET /{dbname}/_design/people/_view/total_age “reduce”:”function(keys,. 30
  31. 31. ところでMap&Reduce大量のデータを分散して処理する仕組み map reduce“バナナ”, 300円 “りんご”, 400円 400円+300円 “おにぎり”, 150円“りんご”, 400円 150円+700円“ぶどう”, 700円“おにぎり”, 150円 “バナナ”, 300円 “ぶどう”, 700円 1550円 31
  32. 32. viewを作る32
  33. 33. Futonで操作API上は、普通のドキュメントと同じ操作テストのためにTemporaryビューを使うFuton上は右上のプルダウンから”temporary_view...”を選択
  34. 34. 買い物リストでサンプル作成 date category item price 5/10 果物 バナナ 300 5/10 魚 鯛 1000 5/11 果物 みかん 400 5/11 果物 バナナ 250 5/12 魚 鯛 900 34
  35. 35. 最も単純なmap emitで指定したkeymap 結果function(doc){ key id date category item price emit(null, doc)} null 123xx 5/10 果物 バナナ 300 null 234xx 5/10 魚 鯛 1000 null 345xx 5/11 果物 みかん 400•mapにドキュメントがパラメータとして来る null 456xx 5/11 果物 バナナ 250•reduceに渡すのはemitメソッド null 567xx 5/12 魚 鯛 900•検索結果の行数(total_rows)も出力•結果はJSONで返る mapしたドキュメントのdocID{"total_rows":5,"offset":0,"rows":[{"id":"123xx","key":null, "value":{ "_id":"123xx","_rev":"1-123xx","date":"5/10", "category":"果物","item":"バナナ","price":300 }},.... 35
  36. 36. 値段の順にソートmap 結果function(doc){ key id date category item price emit(doc.price, doc);} 250 456xx 5/11 果物 バナナ 250 300 123xx 5/10 果物 バナナ 300 400 345xx 5/11 果物 みかん 400 900 567xx 5/12 魚 鯛 900 1000 234xx 5/10 魚 鯛 1000•key+idの降順•逆順にする場合は descending=true例) GET /items/_design/items/_view/by_price?descending=true 36
  37. 37. 果物だけ抽出map 結果function(doc){ key id date category item price if(doc.category == ”果物”) null 123xx 5/10 果物 バナナ 300 emit(null, doc);} null 345xx 5/11 果物 みかん 400 null 456xx 5/11 果物 バナナ 250 37
  38. 38. 値段を合計するmap 結果function(doc){ key value emit(null, doc.price); null 2850} {"rows":[ {"key":null, value:2850}reduce ]}function(keys, values){ keysには次のような値が入る return sum(values); [[null, “123xx”], [null, “234xxx”], ..]} valuesには次のような値が入る sumは数値の配列を合計するメソッド [300, 1000, 400, 250, 900] returnで1つの値を返すように作る 38
  39. 39. 日付毎に値段を合計map 結果function(doc){ key value emit(doc.date, doc.price);} 5/10 300 5/11 1650reduce 5/12 900function(keys, values){ 次のように3回に分けて reduce が呼ばれる return sum(values);} keys [[“5/10”, “123xxx”]] values [300]普通に呼び出すと合計の2850だけが戻る keys [[“5/11”, “123xx”], [“5/11”, “234xxx”], ..]key値毎の塊をグループと言う。 values [250, 400, 1000]グループ毎に出力するには、次のように呼び出す。 keys [[“5/12”, “123xx”]] values [900]GET /.../_view/daily_cost?group=true 39
  40. 40. 日付+カテゴリ毎に値段を合map 結果function(doc){ key value emit([doc.date, doc.category], doc.price); [“5/10”,”果物”] 300} [“5/11”,”果物”] 650 [“5/11”,”魚”] 1000reduce [“5/12”,”魚”] 900function(keys, values){ return sum(values);}グループに深さができる。1階層目は日付ごと、2階層目は日付+カテゴリ毎GET /.../_view/dail_cat_cost?group=true #日付+カテゴリ毎GET /.../_view/dail_cat_cost?group=true&group_level=2 #日付+カテゴリ毎GET /.../_view/dail_cat_cost?group=true&group_level=1 #日付毎 40
  41. 41. カテゴリ毎の件数を調べるmap 結果function(doc){ key value emit(doc.category, 1);} 果物 3 魚 2reducefunction(keys, values){ 次のようにreduceが呼ばれる return sum(values);} keys [[“果物”, “123xxx”], [“果物”,...]] values [1,1,1] keys [[“魚”, “123xx”], [“魚”, “234xxx”]] values [1,1] 41
  42. 42. rereduce42
  43. 43. reduceの再帰呼び出しHadoopではcombine同一グループの件数が多いとreduceの再帰呼び出しで高速化function(keys, values, rereduce)rereduceがfalseの時 valuesはemitで渡した値の配列rereduceがtrueの時 valuesには、計算途中の配列が入る
  44. 44. 失敗するcount 下記の処理でも件数が求まるように思えるが 実際には想定通り動かない場合がある。map reducefunction(doc){ function(keys, values, rereduce){ emit(doc.category, 1); return values.length;} } 再帰呼び出しの際に5個のりんごが2個とカウントされてしま reduceの戻り値の配列がvaluseに入るうkeys [[“りんご”]...] values [1,1,1] rereduce false rereduce処理 values.length -> 3 keys null values [3,2] rereduce truekeys [[“りんご”]...] values [1,1] rereduce false values.length -> 2 values.length -> 2
  45. 45. 成功するcount①map reducefunction(doc){ function(keys, values, rereduce){ emit(doc.category, 1); return sum(values);} }keys [[“りんご”]...] values [1,1,1] rereduce false rereduce処理 sum(values) -> 3 keys null values [3,2] rereduce truekeys [[“りんご”]...] values [1,1] rereduce false sum(values) -> 2 sum(values) -> 5
  46. 46. 成功するcount②map reducefunction(doc){ function(keys, values, rereduce){ emit(doc.category, 1); if(rereduce)} return sum(values); else return values.length; }keys [[“りんご”]...] values [1,1,1] rereduce false rereduce処理 values.length -> 3 keys null values [3,2] rereduce truekeys [[“りんご”]...] values [1,1] rereduce false values.length -> 2 sum(values) -> 5
  47. 47. viewについて補足sum,countには組込のメソッド {“map”:”function(doc){emmit(doc,1)}” “reduce”:”_count”},がある {“map”:”function(doc){emmit(doc,doc.price)}” “reduce”:”_sum”}デバッグ用にlogメソッドがあ function(keys, values, rereduce){る。ログファイルに出力される。 log(values); return sum(values); }Validationが設定できるDocument Update Validation一括Updateの方法があるDocument Update Handler
  48. 48. レプリケーション48
  49. 49. レプリケーションの仕様Target(Slave)のサーバにSource(Master)を設定データベース単位でレプリケーションを設定Sourceに流れたPUT,POST,DELETEのクエリがTargetにも流れるレプリケーションを設定する前のデータは同期しないコンフリクトが起きてもエラーにはならない1.10からレプリケーション設定の仕様が変更
  50. 50. マルチマスタも実装可能http://localhost:5984/some_db/_all_docs nginxなどをProxyとして使う proxy couchdb couchdb couchdb 相互にクエリを投げ合う
  51. 51. パフォーマンス51
  52. 52. 速くない...正直、MySQLの方が速い気がする。10万件のINSERT MySQL 3.7秒 CouchDB 30秒SELECT系もMySQLの勝利だった今度 mongo dbのベンチマークするので詳細はそこで。
  53. 53. おしまい53

×