ラベル NoSQL の投稿を表示しています。 すべての投稿を表示
ラベル NoSQL の投稿を表示しています。 すべての投稿を表示

2015年5月5日火曜日

CouchDB テーブルデータを表示/更新

さて単純なデータの表示/更新はできました。続いてテーブルデータです。例としては高度による静圧を取り上げます。とりあえず、出来た結果の表示例を以下に示します。(注:うっかり例には「標準大気圧テーブル」と書いてしまいましたが、正確には標準大気圧を元にした静圧のテーブルです。後で気づきました。(^_^;))


これの元になったCouchDBのドキュメントは以下です。

{ "_id": "StaticPressure", 
 "_rev": "16-482692fb61559cf3ad7224021b093d63", 
 "name": "StaticPressure", 
 "value": { "StaticPressure": [ { "Alt": 0, "P": 10332 }, { "Alt": 1, "P": 9167 }, { "Alt": 2, "P": 8106 } ] }, 
 "unit": "Alt[km],P[kgf/m2]" }

取り扱いたいデータは、"value"項にあるObjectのArrayです。(後にデータの追加を説明しますが、最初はこれもjson形式だと勘違いしていましたが、正確には違うんですね。そのためjsonのデータハンドリングが使えず、JavaScriptのObjectのデータとして取り扱います。つまりObjectのArrayがjsonの一つのデータとして格納されている形になっています)

「読込」ボタンで動くJacaScriptを以下に示します。


   var url = 'http://localhost:5984/environment/StaticPressure';
   var json;
   var json_part; // 静圧テーブルデータ部分(正確に言うと狭義のJSONとは違う)


   function exec() {
    // データの読み込み
    $.ajax({
     type: "GET",
     url: url,
     dataType:'json',
    }).done(function(data) {
     json = data;
     json_part = data.value;

     for (var i=0; i<json_part.StaticPressure.length; i++) {
      // 表(table)の最後の行に追加
      $('#table1 > tbody:last').append('<tr><td>'+ json_part.StaticPressure[i].Alt +'</td><td>'+ json_part.StaticPressure[i].P +'</td></tr>');     
     }

    }).fail(function(xhr, textStatus, errorThrown){
     alert('error!!');
     console.log("NG:" + xhr.status);
     console.log("NG:" + textStatus.status);
     $('#msg').append("失敗しました");
    });

   }


#table1というidのtableタグがhtml側に用意してあり、そこに読み込んだ静圧テーブルのデータを1行づつ追加していくだけです。

次に「追加」を実装します。最初の例に既に「読込」の右横に2個の入力フィールドとともに準備してあります。ここに、「0.5」「1000」という 値を入れて「追加」してみます。
無事データが一行追加されました。「追加」ボタンの実装を以下に示します。
function add() {
 var newAlt = parseFloat( $('#inputAlt').val() );
 var newPressure = parseFloat( $('#inputPressure').val() );

 // 入力データを追加する(単純に検索する)
 for (var i=0; i<json_part.StaticPressure.length; i++) {

  if (json_part.StaticPressure[i].Alt <= newAlt) {
   continue;
  }

  var newdata = {};
  newdata.Alt = newAlt;
  newdata.P = newPressure;

  // 表(table)のi行に追加
  $('#table1 tr').eq(i).after('<tr><td>'+ newdata.Alt +'</td><td>'+ newdata.P +'</td></tr>');

  // 静圧テーブルデータにも追加
  // 第1引数:変更を加えるindex
  // 第2引数:第1引数から難行のObjectを削除するか指定
  // 第3引数:変更として追懐したいObject
  json_part.StaticPressure.splice(i, 0, newdata);

  break;    
}

今回は注意点が2箇所あります。
①17行目でtableに値を追加しています。前回はappend( )を使いましたが、今回はafter( )を使いました。理由がわかりませんが、append( )だとテーブルの形が崩れたからです。(右横に1列追加される感じになってしまいました)この辺はもう少し勉強が必要です。
②23行目で実際のメモリ中のデータにも挿入しています。最初にも書きましたが、てっきりこれがjson形式のデータと思い込んでいたので、pushやなんか使おうとしたんですが、エラーばかり返されてきました。JavaScriptのObjectというデータ形式の中で、特にハンドリングしやすいようにフォーマットを決められたのがjsonとのことで、JavaScript内部のメモリでの持ち方はどちらも同じですが、使う関数が違います。splice( )と呼ばれるArrayに対する操作を行う関数を使っています。

ところで最初に「読込」時、テーブルデーを扱いやすくするため、json内部のvalueの部分だけjson_partという変数に代入してました。これまさしくポインタの代入で、「追加」の方でjsn_partにデータを追加したら、しっかしjsonの方にも反映されていました。(この辺りはjavaと同じで、変数は基本ポインタで値を保持しているようです)
後はこの新しいデータをCouchDBに戻してやるのと、せっかくテーブルデータなのでグラフが欲しいところですが、それはまた次回に。



2015年4月30日木曜日

CouchDB データを表示/更新するサンプル

ずいぶん前回から時間が空いてしまいました。余裕がなかったのもありますが、一番大きいのがこのBloggerでHTMLのソースを表示する方法が中々わからなかったのが一番大きい理由です。prettyprint使っても、どうしても内部のhtmlを本物のタグと解釈してしまうんです。結局そのままブログにペーストするのは諦めて、「偽の」HTMLに変換してからprettyprintすることにしました。(一手間増えるのと、ブログ編集中に確認がしづらいのでこの方法はどうしても嫌だったんですが、どうもこれしかなさそうです。あと変換ツールの問題なのか、シングルクオート、ダブルクオートの前にバックスラッシュが入ってしまいます。手作業で少しは削除しましたが、途中でめげました。) さて以下がサンプルで作成したHTMLの表示例です。
地球半径をCouchDBに登録したものです。「読み込み」ボタンでCouchDBからデータを読み込み表示し、「変更」でvalueに入力されている値をCouchDBに書き込みます。デバッグ用に、一番下に読み込んだり、書き込んだりするjson形式のデータを表示しています。
以下がそのサンプルです。(注:クオートの前にバックスラッシュが入ってしまっているのは無視してください。)

<!DOCTYPE html>
<html lang="ja">
 <head>
  <meta charset="UTF-8">
  <title>HTML5</title>
  <script src="jquery-1.11.2.min.js"></script>
  <script src="stringify.js"></script>
 </head>
 <body>
  <p>Hello Ajax, couchdb データ更新をします</p>
  <input type="button" value="読み込み" onclick="readCouch('msg');">
  <input type="button" value="変更" onclick="change()">
  <!-- <TEXTAREA cols="40" rows="6" id="texts" wrap="on"></TEXTAREA> -->
  <script>
   var url;
   url = 'http://localhost:5984/environment/Radius';
   var json;

   function readCouch(id) {
    var target = document.getElementById(id);

    $('#texts').text("これから読みます:");
    $('#msg').append("これから読みます:</br>");

    $.ajax({
     type: "GET",
     url: url,
     dataType:'json',
    }).done(function(data) {
     // alert('読み込み');
     json = data;
     $(\'#id_text\').val(data._id);
     $(\'#name_text\').val(data.name);
     $(\'#value_text\').val(data.value.toString());
     $(\'#unit_text\').val(data.unit);

     var str = $.stringify(data);
     $(\'#msg\').append(str);
    }).fail(function(xhr, textStatus, errorThrown){
     alert(\'error!!\');
     console.log(\"NG:\" + xhr.status);
     console.log(\"NG:\" + textStatus.status);
     $(\'#msg\').append(\"失敗しました</br>\");
    });

    $(\'#msg\').empty();
    $(\'#msg\').append(\"うまくいった?</br>\");

   }

   function change() {
    json.value = parseFloat($(\'#value_text\').val());
    var jsonStr = JSON.stringify(json);

    $.ajax({
     type: \"PUT\",
     url: url,
     data: jsonStr,
     dataType:\'json\',
    }).done(function(data) {

     var str = $.stringify(data);
     $(\'#msg\').append(str);
    }).fail(function(xhr, textStatus, errorThrown){
     alert(\'error!!\');
     console.log(\"NG:\" + xhr.status);
     console.log(\"NG:\" + textStatus.status);
     $(\'#msg\').append(\"失敗しました</br>\");
    });
   }
  </script>
  <br>
  <table border=\"0\">
   <tr>
    <td align=\"right\"><label>id : </label></td>
    <td><input type=\"text\" id=\"id_text\"></td>
   </tr>
   <tr>
    <td align=\"right\"><label>name : </label></td>
    <td><input type=\"text\" id=\"name_text\"></td>
   </tr>
   <tr>
    <td align=\"right\"><label>value : </label></td>
    <td><input type=\"text\" id=\"value_text\"></td>
   </tr>
   <tr>
    <td align=\"right\"><label>unit : </label></td>
    <td><input type=\"text\" id=\"unit_text\"></td>
   </tr>
  </table>
  <br>
  <p id=\"msg\"></p>
 </body>
</html>

これまでGUIでのデータ入出力画面をMotifやらViduslaStuioで組んできましたが、こっちの方がOSを選ばないので汎用性が高いです。(ただ2次元のグラフも表示したい、3次元のグラフィックが欲しい、とか言われるとHTML5のCanvsタグが必要になってくるので、現時点ではブラウザを選んでしまうのが悩ましいところです。)

追記:
ところで上記サンプルでは更新したいのは"value"だけなのに、ドキュメント全体のjsonデータをCouchDBに送信しています。これって無駄が多くない?と思い調べてみると、jsonStrを作るstringify( )で第2引数に特定のjsonデータだけを指定してやればjsonStrもそれだけが作れることがわかりました。
これはいい!と思って早速試してみたんですが、CouchDBのデータを管理するのがドキュメント単位で、さらにそれにrev番号まで付いています。つまり変更したい部分だけのjsonStrを送信してしまうと、残りの部分がなくなったドキュメントになってしまいます。ちょっとこれではうまくありません。あきらめるしかないようです。(あまりでかいドキュメントが必要になるようなデータベース構成にすると処理時間が問題になってきます)

2015年2月22日日曜日

c++でcurlを使う:couchDBへのアクセス

c++でcouchDBにアクセスしようと思ったら、curlライブラリを組み込んでやりましょう。今回はとりあえず自分のメモ用にサンプル載せておきます。

#include <string> 
#include <iostream> 
#include <curl/curl.h> 

using namespace std;

size_t callbackWrite(char *ptr, size_t size, size_t nmemb, string *stream)
{
  int dataLength = size * nmemb;
  stream->append(ptr, dataLength);
  return dataLength;
}

int main()
{
  CURL *curl;
  CURLcode ret;

  curl = curl_easy_init();
  string chunk;

  if (curl == NULL) {
    cerr << "curl_easy_init() failed" << endl;
    return 1;
  }

  curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:5984/environment/");
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callbackWrite);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk);
  ret = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  if (ret != CURLE_OK) {
    cerr << "curl_easy_perform() failed." << endl;
    return 1;
  }

  cout << chunk << endl;

  return 0;
}

couchDBにenvironmentというデータベースを事前に作っておきます。これでうまくレスポンスが返ってきました。JSON形式で返ってきますので、後は前回のpicojsonでハンドリングするだけです。

2015年1月31日土曜日

CouchDB JavaScriptからの読み込みについて

CouchDBのUIを作ってみたくなり、htmlを書いてみます。せっかくなのでjQueryを使って、Ajaxを試して見たいと思ったのですが、ここからが苦難の始まりでした。

簡単なサンプルを書いてみたんですが、どうしてもNo 'Access-Control-Allow-Origin' header is present on the requested resource.というエラーがコンソールに出てきて失敗します。(ChromeのJavaScriptコンソールで確認しています)色々、調べてみたんですが、エラーの意味はXSSを防ぐため、ドメインが異なるJavaScriptを実行したり、読み込もうとするとでるエラーのようです。(数年前、これおを悪用したサイトの改竄が流行った記憶があります)しかし自分はローカル環境、あるいは家庭内の別PCで動かしているcouchdbから読もうとしているんですが、このエラーから逃げれません。
そこで、jQueryの問題なのかcouchdbの問題なのか区別するため、素に戻って単純にJavaScriptでローカルPCに置いたjsonデータを読むプログラムを書いてみました。

   function loadJson(id) {
    $('#msg').append("これから読みます:");

    var url = "http://localhost/~hogehoge/test1.json";
    var xmlHttp;

    xmlHttp = new XMLHttpRequest();
    xmlHttp.open("GET", url, false);
    xmlHttp.send(null);

    $('#msg').append(xmlHttp.responseText);

    $('#msg').append("うまくいった?");

   }

(これをやるために前回「ウェブ共有」の設定を調べました)
ところがMac上のchromeからでは相変わらず同じエラーが出ます。幾ら何でもこれはおかしいだろうと、調べてみるとchromeではセキュリティを厳しくしていて、ローカルPC上のファイルをhttp経由で読もうが、同じチェックをしていてエラーを出すそうです。ちなみに同じものをSafariで実行してみたら、あっさり動きました。(ちなみに同様のスクリプトをjQueryで書くと、Safariでもエラーがでます。どうもjQuery内部で同様のチェックをしているようで、簡単に自分のとこだけで試してみようと思っているのに難儀なことです)

さてCouchDBのUIを作ろうと思ったらなんとかcouchdbの設定で'Access-Control-Allow-Origin' のヘッダをつけるようにしないといけません。
現状、使用しているcouchdbは1.6ですが、1.4のドキュメントには以下の設定があるのがわかりました。

local.iniに以下の設定を追加。

[httpd]
enable_cors = true

[cors]
origins = *

特に[cors]の項目はデフォルトの設定ファイルには存在しておらず大丈夫かと思いましたが、うまくいきました。(ただこの設定は「全て許可する」という意味なんで、あまり推奨できませんが…本当は単にローカルPC上だけで動かしたいだけなら、origins=http://localhostとかするのがいいと思います。でもこれだと、http://127.0.0.1でアクセスすると拒否されてしまい、難しいところです。)
さて実際にcouchDBからデータを読み込んでみます。以下のプログラムで試してみました。(htmlにはボタンをつけて、以下のスクリプトを実行するようにしています)

   function readCouch(id) {
    $('#msg').append("これから読みます:");
    var url;
    url = 'http://localhost:5984/kakeibo';
    $.ajax({
     type: "GET",
     url: url,
     dataType:'json',
    }).done(function(data) {
     alert('読み込み');
     var str = parseJson(data);
     $('#msg').append(str);
    }).fail(function(xhr, textStatus, errorThrown){
     alert('error!!');
     console.log("NG:" + xhr.status);
     console.log("NG:" + textStatus.status);
     $('#msg').append("失敗しました");
    });

    $('#msg').append("うまくいった?");
   }

   function parseJson(json) {
    var ret = 'db_name : ' + json.db_name + '';
    ret = ret + 'doc_count : ' + json.doc_count;
    return ret;
   }

chromeで実行すると以下のようになります。(html上に、<p id=msg>が付けてあり、そこに追記するようにしています)


うまく読めましたね。

2015年1月12日月曜日

CouchDB Replicatorについて

もう少しCouchDBの基本的な動作を確認します。今度はReplicator(複写)についてです。 作成したDBのBackUpや、あるいは複製を取る機能としてFutonにはReplicatorの機能があります。localにBackUpを保存してもいいんですが、折角なので別のPCにもCouchDBを設定して試してみます。(ちょっと別に興味があったので、MacBookAirにHomebrew入れてCouchDBをインストールしてみました) 

Replicatorの画面は以下の様な感じです。ここで簡単にkakeiboをMacBookAirに新規インストールしたCouchDBにコピーを作ります。(なお、先にMacBookAir側で内容は空でいいんですがコピー先のDBを作成しておく必要があります。どうもいきなり作成はしてくれないようです。PS:remoteから取ってくる場合は新規に作成するかと聞かれて、OKすれば自動的に作成してくれるようです。)


うまくいくとEvent欄にsessionの動作ログが表示されます。

さてここで実験です。localとMacBookAir側でわざとDBを異なる状態にしてReplicatorを動かしたらどうなるかを見てみます。まず、それぞれを以下の状態にします。(赤字がそれぞれ追加したドキュメントです)

local側

date    category      item       price
5/10     果物           バナナ       300
5/10       魚               鯛         1000
5/11     果物           みかん       300
5/11     果物           バナナ       300
5/12       魚               鯛           900
5/14       魚            まぐろ       1000


MacBookAir側

date    category      item       price
5/10     果物           バナナ       300
5/10       魚               鯛         1000
5/11     果物           みかん       300
5/11     果物           バナナ       300
5/12       魚               鯛           900
5/13     果物           みかん       600

相互にReplicateしてみると、それぞれ追加したドキュメントが相手に入ってくれます。


Replicate後

date    category      item       price
5/10     果物           バナナ       300
5/10       魚               鯛         1000
5/11     果物           みかん       300
5/11     果物           バナナ       300
5/12       魚               鯛           900
5/13     果物           みかん       600
5/14       魚            まぐろ       1000

その後、local側で5/13のドキュメントを削除した後、最後MacBookAir側にReplicateしてもMacBookAir側の5/13のドキュメントはすぐには削除されません。単に複写をしているだけではないようです。(しばらく時間がたってからReplicateすると相手のDBにも最新の状況が反映されます)

このあたりの動きとして関連して、先のReplicateするときボタンの横にCotinuousトグルがあるので、それをOnにしてReplicateすると連続して同期してくれます。Statusで動作していることが確認できます。

(ただこれは、local→MacBookAirの方向のReplicateなので、localの変更は自動的にMacBookAir側に反映されますが、MacBookAirの変更はlocalに自動的には反映してくれません。)
なお、この設定はマシンを再起動すると消えてしまいます。恒常的にReplicateさせるには別途設定が必要なようです。(当たり前か)






2015年1月2日金曜日

CouchDBのMapReduceをアプリ(python)で動かす

前回、Futon上でMapReduceを定義、動かしてみました。サンプル的にDBの集計をしたい場合はそれでいいんですが、やはりアプリ(python)からMapReduce処理を指示し、集計処理をしてみたいです。

とりあえず前回、日付毎の集計をするMapReduceを”eachdate”に作成しましたので、これをpythonからCallしてみます。(Keyを指定せずに、全部を対象にしています)

# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server['kakeibo']

result = db.view("application/eachdate")

for r in result:
    print r

ところがこれを実行すると以下の結果になってしまいました。

&lt Row key="None," value="2850">

なんかkeys(日付)毎に集計されるのを期待したのですが、keys無視で集計されています。色々、調べまくってみると、どうもデフォルトではreduce関数の入力はMapの結果だけではなく、Reduceの結果も再度受け入れてしまうようです。(そのため、全部集計してしまう:注)これを期待どおり、keys毎に集計したい場合は以下のOptionを入れるそうです。

# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server['kakeibo']

result = db.view("application/eachdate", group=True)

for r in result:
    print r

こうすると、期待どおり日付毎の集計をしてくれます。

&lt Row key=u'5/10', value=1300>
&lt Row key=u'5/11', value=650>
&lt Row key=u'5/12', value=900>


(ちなみに、ここにはReduceを行わないoptionとして'reduce=False'というのもあります)

注:Reduce関数の出力が再度Reduce関数の入力に入ってしまう、というのは説明が足りてませんでしたね。(元が英語だったので、意図を解釈しきれてなかった)これは、Mapの結果が大きいと、Map関数が入力データをsplitして複数のMap結果が生成されるそうです。(具体的にどのくらい大きいとsplitされるのかはまだ調べてません。)そうすると、各Map結果に対してReduce関数が動き、最後にそれらのReduce関数の結果をまとめるため、もう一度Reduce関数がCallされるんだそうです。同じReduce関数がCallされるので、そのままだと再集計をしてしまう、ということが起きるようです。それを避けるため、rereduceというflagがあり、Reduce関数内ではそのflagでsplitされたMap関数を処理するReduce関数なのか、最後に集計するReduce関数なのかをif文で識別して動作する、という方法を使うそうです。ここで述べたgroupとsplit、rereduceの関係はまた調べて整理しないとダメですね。

さて、MapReduceのコードをFutonで入力するのもいいですが、python上でコーディングしてしまう方法もあります。

# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server['kakeibo']

map_fun = '''function(doc) {
 emit(doc.date, doc.price);
}'''

reduce_fun = '''function(keys, values) {
 return sum(values);
}'''

result = db.query(map_fun, reduce_fun, group=True)

for r in result:
    print r

何のことはありません、関数定義を文字列として渡しているだけですね。
後、これらの結果を見るとreduce定義の引数のところで(keys, values)としましたが、特に(key, value)でもいいようです。まあわかれば当たり前の話ですが、関数定義の時の引数名なんで、順番が大事なだけですね。


PS:
最後に、ログのとり方についてだけ追記しておきます。特に今回悩んだような症状のとき、Reduce関数には何が引数に入っているか知りたくなります。そんな時、ログ関数があります。

reduce_fun = '''function(keys, values, rereduce) {
 log(rereduce);
 log(keys);
 log(values);
 if (rereduce)
  return sum(values);
 else
  return values;
}''

こんなふうにlog()を入れるだけで、/var/log/couchdb/couch.logに記録されます。これを見ると、group=Trueがある時とない時の違いがひと目でわかります。
groupの指定をしていないと以下のログが記録されます。

[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: false
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [["5/10","102d4440a25efcd79349859b67000302"],["5/10","102d4440a25efcd79349859b67000a91"],["5/11","102d4440a25efcd79349859b67000eee"],["5/11","102d4440a25efcd79349859b67001c9b"],["5/12","102d4440a25efcd79349859b67002b7f"]]
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [300,1000,400,250,900]


id情報まで表示されるので見難いのですが、Map関数を通しても、すべての日付のリストがkeysに入っており、対応するpriceもvaluesにリストで5個集められてしまっていることがわかります。(これでは、日付毎の集計なんてされないはずだ)
一方、group=Trueの指定をすると以下のログになりました。

[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: false
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [["5/10","102d4440a25efcd79349859b67000a91"],["5/10","102d4440a25efcd79349859b67000302"]]
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [1000,300]
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.109.0="">] 127.0.0.1 - - POST /kakeibo/_temp_view?group=true 200
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: false
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [["5/11","102d4440a25efcd79349859b67001c9b"],["5/11","102d4440a25efcd79349859b67000eee"]]
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [250,400]
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: false
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [["5/12","102d4440a25efcd79349859b67002b7f"]]
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [900]


今度は、一回のreduce関数がcallされる時にはkeysには日付ごとのリストが集められていることがわかります。(つまり3回reduce関数はcallされています)これではっきりと、想定した動作をなぜしなかったのかがわかります。

2014年12月31日水曜日

CouchDBをMapReduceで処理してみる

さてせっかくなので、MapReduce機能を使ってCouchDBのデータ処理をメモしておきます。
調べているうちに非常にわかりやすい所があり、MapReduceの説明にもちょうどよかったので自分なりに解釈して記しておきます。

まずデータを用意しないといけません。以下のような家計簿もどきを用意します。

date    category      item       price
5/10     果物           バナナ       300
5/10       魚               鯛         1000
5/11     果物           みかん       300
5/11     果物           バナナ       300
5/12       魚               鯛           900

各行が1個のドキュメントにあたります。(とりあえず全ドキュメントが持つFieldは同じにしておきます。その方が説明が楽なので。)

FutonでViewをTemplateに切り替えます。するとMapとReduceのプログラムを入力する欄が表示されます。そこでMapとして以下を入力します。

function(doc) {
  emit(null, doc.price);
}

すると全ドキュメントから価格だけ抜き出して一覧表示してくれます。


Reduceのプログラムをしてないので、データの抜き出しだけした結果です。
後、if文で特定のFieldのドキュメントだけ抜き出すこともできます。


次にReduce欄に以下を入力します。Reduce処理として集めたデータの加算を指示しています。

function(keys, values) {
  return sum(values);
}

すると以下の様になります。(emitの第1引数がkeysになり、第2引数がvaluesになるという関係です)


って言いたいところですが、Reduceが動いていない... よく見たらviewの右上にReduceにチェックを入れるボタンが。


どうもここにチェックを入れないとReduce処理をしてくれないようです。無事合計がでてくれました。

次に日付毎の価格を集計してみます。Map欄に以下を入力します。

function(doc) {
  emit(doc.date, doc.price);
}

key欄にdateを指定し、それ毎の塊を作ります。その時、priceの値だけを集めてきます。それをReduceで集計すれば日付ごとの価格の集計になります。


次にcategory毎の件数を集計してみます。とりあえずkeyにcategoryを指定すれば、category毎の塊は作成してくれます。

function(doc) {
  emit(doc.category, 1);
}

この時、valuesに「1」を固定して指定します。つまり集めた塊ごとに「1」の値が与えられることになります。これを集計すれば塊の件数になります。Reduce欄には例によってvaluesの集計をするようにします。


ということで、categoryの件数が出力されました。

本来、MapReduceはGoogleが考えだしたみたいに多数の計算機で処理するのが本当で、1台のPCでやっても旨味があまりありませんが、非常にわかりやすくドキュメント型のデータを処理してくれます。
後はこのMap欄やReduce欄のコードをアプリ側から自由に実行できないと、CouchDBの本当のいいところがでてこないでしょうが、それはまたメモできる程度、試行できたら。(Map欄のコードを実行した例は2回前のCouchDBのメモでもう記してますから、わかりやすいデータが思いつけば書けるでしょう)

CouchDBをpythonで操作する

さて実際のDB操作のための実装に移ります。例題として、ちょっと理科年表のデータを入れてみます。(地球半径とか高度ごとの気圧です)


ちょっと見にくいですが、Raidus, SeaLevelGravity, StaticPressureの3個のドキュメントを作成しました。これをプログラムで参照、変更してみます。
Futon上で最初にデータベースを作成してて困ったのが以下のStaticPressureです。
「高度:気圧」のペアのリストの構造なので、JSON形式でも扱いやすいしpythonでも辞書型で扱い易いと思ったんですが、Futon上からはどうしても思うように入力できません。結局、valueにはカンマ区切りの数字の羅列(高度1、気圧1、高度2、気圧2、。。。)という形式にしました。(そのうち3次元テーブルもでてくるから、このフォーマットの方が汎用性高いでしょう。javaにもpythonにもsplit()関数があって、valueを文字列で取得、個々の数字を簡単に切り出せますし。)
しかし、どうも辞書形式が登録できないのが気に入りません。頑張って上図のdataのように、「文字列:数字」のペアなら辞書として登録できることがわかりました。(どうもこれはFutonというより、CouchDBの制約のようです。後に実装でpythonから数字ペアの辞書を登録しますが、CouchDB上では最初の数字は文字列と認識されてしまいます)

ということで、pythonから直接、数字ペアを登録してみます。ドキュメント「Radius」にdataというFieldを作成しておいて、以下のコードで実行してみます。


# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server['environment']

doc = db['Radius'] # ドキュメントの取得(コピーを作成するので注意)

dl = { 1:1, 2:2, 3:3} # サンプルデータの辞書

doc['data'] = dl # ドキュメントの一部を変更

db[doc.id] = doc # ドキュメントの更新
#db.commit() # なくてもいい

見た目は数字ペアの辞書が登録できたように見えます。


ところが、以下のプログラムで確認してみると、
# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server['environment']

doc = db['Radius'] # ドキュメントの取得(コピーを作成するので注意)

print doc['data']

実行結果は以下の様になります。
{u'1': 1, u'3': 3, u'2': 2}
全然ダメです、最初の数字が文字列として認識されてしまっています。

余談:
ところでCouchDBは古いリビジョンを残していて、「適当」なところで自動的にガベージコレクションしてくれるそうです。ただ、プログラムを作って、動かしている方としてはHDDの容量も気になるので、意図的に古いのを削除したくなります。python-couchdbでは以下のメソッドで古いリビジョンを削除します。

# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server['environment']

#db.cleanup() # これは古い設計
db.compact() # こっちが今の、古いリビジョンのドキュメントは削除する

説明見てたら同じようなメソッドが2個あったので試してみましたが、やはりcleanup()の方はダメで、そもそもエラーが出てきました。(説明にはそこまで書いてなかった)

2014年12月30日火曜日

CouchDBを設定し、pythonで操作してみる

NoSQLのデータベースで、高速性よりもフォーマットを自由に扱いたい場合、CouchDBの評判が高いようです。ちょっと試しに自PC上に(Ubuntu14.04)CouchDBを設定し、ついでにデータ操作をどうやるかを試すために、pythonでのサンプルを作ってみます。(オリジナルでJavaScriptのAPIは持っているそうです)

まずCouchDBのHP(launchpad.net)にいってインストール方法を調べます。Ubuntu14.04の場合は以下のようにするらしい。

$ sudo apt-get install software-properties-common

(aptのリポジトリの状態を確認、更新するらしい。
自分の場合あ既にこのリポジトリが入っていたらしく、最新の状態ですと言われた)

$ sudo add-apt-repository ppa:couchdb/stable

既に(古いバージョンが)インストールされているとまずいので、一度削除します。

$ sudo apt-get remove couchdb couchdb-bin couchdb-common

(自分は3個とも入ってない言われました)

いよいよインストールします。

$ sudo apt-get install -V couchdb
$ sudo stop couchdb
$ sudo start couchdb

ブラウザでhttp://127.0.0.1:5984/にアクセスします。
動いてるようですが、文字列しか表示されません。
couchdbのユーザー設定ないから?
(でも/etc/passwdで確認するとcouchdbのアカウントは登録されていました)
HPのドキュメントを見たら、現状はこれでいいようです。

http://127.0.0.1:5984/_utils

でFutonが動きます。大丈夫なようです。(データベースをWebベースで参照、変更、作成ができます)



PCを再起動しても自動的にデーモンは起動しています、Futonが動きました。

pythonでcoudbを使いたいので、/etc/couchdb/local.iniに以下を追加して再起動しろとのこと。

[query_servers]
python=/usr/bin/couchpy

念の為に/usr/bin以下を知らべてみたら、couchjs(おそらくJavaScript用のモジュール)はあるが、couchpyは存在していません。(注:この設定は必要なかったかも、なくてもcouchpyさえインストールすれば動きました。でも本当は必要な筈...)しばらくぐぐった結果、以下のパッケージをインストールしたらよかった。

$ sudo apt-get install python-couchdb

しかしcouchdbの再起動方法がわからない。(/etc/init.d以下にcoudbというスクリプトがあるという情報があったが、ubutu14.04だとそんなのない!どうやって最初に起動してるんだ?→後述)
PS:ちなみにこのpython-couchdb、昔はgoogle-Codeで開発されていたんですが、githubに移っていました。googleでも特に制約はないと思うけど、流行りですかね。
というか、再起動は必要なかった。(うまくいかないようなら、インストール直後にやったみたいにコマンドで直接、停止、起動をすればいいでしょう。)

試しにpythonでプログラムを書いてみました。試験用に用意したのは以下の様なデータです。
ぱっと見わかりにくいでしょうが、Fieldにわざと"name"と”名前”と英語、日本語の両方を使っています。


# coding: utf-8
import couchdb

db = couchdb.Server('http://localhost:5984')
sampledb = db['sample_db']

for id in sampledb:
 dbitem = sampledb[id]
 name = dbitem[u'名前']
 print name

日本語のフィールド名でも動きました。(まあそれでも、英語にしといた方が無難だろうね)

ところでCouchDBはデフォルトではPCの外部からのアクセスは禁止してあります。(データの操作にhttp使うくせに、こういうとこはしっかりしてます)
これを許可しないと使いずらいので、以下の場所を編集します。(コメントにデフォルトでなっているのをはずして、全部のIPアドレスを許可する、といったところでしょうか)

/etc/couchdb/local.ini
;bind 127.0.0.1
        ↓
bind 0.0.0.0

特に起動用スクリプトが見当たらないんですが、勝手に再起動してくれるようです。(他のディストリビューションだと起動用スクリプトができるものもあるらしいですが、今度はcouchdbのアカウントを自分で作ってあげないといけないとか)
あとFuton上でもConfigurationというToolがありそこからもできそうですが、、、ちょっとよくわかりません。うっかいこの項目を削除という操作をしてしまったらしく、いきなりWeb上から何もできなってしまって(まあ意味から考えて通信ができなくなりますわな)、あわてて手動で設定ファイルを編集しなおしました。

ところでCouchDBを通常のDBとして使おうと思ったんですが、どうもサンプルを探すとCouchDBの特徴であるMap/Reduceにより全文中の特定のFieldを探してくる、という例ばかりひっかかります。(考えてみたら、フォーマット自由のドキュメント型DBなんだから、そういう使い方が本道なんでしょうね)
でも自分はとりあえず普通に(ユニークな)idで検索するのをやりたいんです。

とりあえず、以下のプログラムでまずDBを作成します。
# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server.create('python-tests')

db['johndoe'] = dict(type='Person', name='John Doe', sex='male')
db['maryjane'] = {'type':'Person', 'name':'Mary Jane', 'sex':'female'}  # あえてjson形式で指定してみる

db.commit()

# 特にcloseとかsaveは必要ない?
# 不安なら時にcommitするか
# 他のPCからすぐに編集した結果を参照したいアプリでは、あったほうがいい

次に以下のプログラムでidを直接指定します。

# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server['python-tests']

print db['maryjane']

これで以下の出力がでてきます。
<Document u'maryjane'@u'1-6d8ea1b66198a41c398662aa3ae07514' {u'type': u'Person', u'name': u'Mary Jane', u'sex': u'female'}>

うまくいきました。(couchdb-pythonにAPIとかのマニュアルが見当たらないので、試行錯誤です)

せっかくなので、Map機能を使い検索してみます。FutonでTemplate Viewにして、

以下の関数をMap関数(左側の欄)として定義します。

function(doc) {
  emit(doc.sex, doc);
}

デフォルトは、emit(null, doc);でどうも全文対象という意味らしいんですが、sexというFieldにマークをします。
これをapplication/sample_viewという名前で保存します。(このスクリプト自体がDB内に保存されるのに注意。Futonで参照できます。)

これを使ったサンプルを以下に示します。
# coding: utf-8
import couchdb

server = couchdb.Server('http://localhost:5984')
db = server['python-tests']

#print db['maryjane']

result = db.view("application/sample_view", key="female")

for r in result:
 print 'id',r['id']
 print 'key',r['key']
 print 'name', r['value']['name']

keyの与え方はともかく、検索された後の他のタグを知る方法がちょっとクセがあります。
以下の様に出力されます。

id maryjane
key female
name Mary Jane

とりあえず最低限、必要なことはわかりました。

2014年6月28日土曜日

Kyototycoonを久しぶりにセットアップしてみる

自宅のLinux(Ubuntu)をついに14.04(trusty)にアップしたのを機に、ちょっと久しぶりにKyototycoonをセットアップしてみました。(kyotocabinet-1.2.76 & kyototycoon-0.9.56)
Kyotoって何?という説明は、高速なkey-value型のデータベースだということだけで済ませます。

さてまず上記最新バージョンをDLしてきて、まずはkyotocabinetからです。おきまりの以下のコマンドです。

$ ./configure
$ make

と行こうと思ったんですが、忘れてましたzlib.hがありません。4年前に初めてセットアップしたときはzlibをソースからビルドしましたが、今回はUbuntuのapt-getですませます。

$ sudo apt-get zlib1g-dev

今はubuntu packagesのHPで必要なファイルが入っているpackageを教えてくれますので簡単です。続けて確認です。

$ make check

結構時間がかかりました。(10分くらい?)無事終了してインストールします。

$ sudo make install

/usr/local以下にインストールされました。
続けて、kyototycoonのセットアップに移ります。

$ ./configure
$ make

としたんですが、なんかgetpid()がscopeに見当たらないとエラーがでてきました。ググってみると、どうも"ktdbext.h"に、#include <unistd.h>を追加してやればいいそうです。
おそらく開発してる人は色んなツールを入れていて、知らないうちにデフォルトで入っちゃってるんでしょうね。OSSビルドしてるとたまに、この基本のincludeがないエラーがでてきます。(gccの問題だという報告もありました。includeのネストで何か問題がでるバージョンがあるんですかね。)

さてkyototycoonのビルド、checkも終わり、テストです。試しに以下のコマンドをコンソールから実行します。

$ ktserver kyoto.kch

kyoto.kchというファイルを作成してデータベースを起動するんですが、ここでまたエラーが・・・ libkyototycoon.so.2が見つからないといってきました。/usr/local/libにあるんですが、なぜかライブラリパスの設定がうまくいっていないようです。ということで以下を行います。

$ sudo vi /etc/ld.so.conf/kyoto.conf

ここに、/usr/local/libを入力します。/etc/ld.so.confにも/usr/local/libはセットしてあるんですが、なぜkyotoだとこの設定ファイルを追加しないといけないのかよくわかりませんが。

$ sudo ldconfig

これで設定を有効にしてやればサーバーは無事起動します。
さて今度は別のコンソールでデータの入力、確認をしてみます。

$ ktremotemgr set japan tokyo
$ ktremotemgr set korea seoul
$ ktremotemgr set china beijing

$ ktremotemgr inform

count: 3

size: 6297824

$ ktremotemgr get japan
tokyo

うまくうごきますね。

2012年1月2日月曜日

NoSQL:現状のまとめ

これまでいくつかのNoSQLを試してきましたが、この業界”栄枯盛衰”が激しく、競争が激しいです。ちょっと現状での状況をまとめておきます。


  1. CouchDB:ドキュメント指向という考え方で、今人気です。インターフェースもHTTPを持っており、非常に使いやすいと評判です。
  2. Redis:有名なオンメモリDBのmemcachedをファイルに永続化する機能を持っています。(以前、気に入っていたTTとかKTと考え方は同じです)インターフェースがtelnetしかないのが玉にキズですが、爆速だとのこと。(ただクライアントにjavaが用意されているのがちょっと理解しにくいですが。確かに今時のjavaは速いんですが、時にGC入ってしまって制御できないのが使いにくいんですが。)
  3. Cassandra:Facebookで開発され公開された有名なjavaで作成されたDBです。とにかくスケールするのが簡単で、巨大なDBもなんなく構築できます。インターフェースには、これまた特別に開発したthriftも開発がしやすく、魅力的です。
  4. HBase:Facebookのメッセージシステムに使われているそうです。インターフェースにはHTTP/thriftが用意されており、巨大テーブルが使えるのが特徴だとか。
  5. LevelDB:googleの公開しているnoSQLです。コンセプトはRedisと同じで、とにかく高速なのと、chrome内に内蔵させているSQLiteの入れ替えを考えているそうです。(ファイルへのデータの永続化に、高速圧縮ルーチン"Snappy"を使っているのも特徴です)昔、mixiでTTやKTを開発した方が今はgoogleに入社していますが、特にこれにはタッチしていないようですが、速度比較にはKTが使わています)

基本的に高速性をうたっているため、memcachedのファイル永続化をしているのがほとんどですが、なにせファイルベースだと、1ファイルのサイズの大きさにDBの大きさが制約されてしまう制限ができてしまいます。(だから速いんですが)

RDBMSだとデータベース管理者がどうしても必要になります。(ログの整理とか、バックアップなど時間のかかる手間がかかります)一方、NoSQLは手間がかからないかといえば、基本的にバックアップはマシンを増やして、そちらにコピーを常時作るという考え方なので、システムをスケールするのにかかる手間がどうかという話になります。ただそれでも、とにかく巨大なDB(TB級)が欲しいとなるとCassandraに行き着くんでしょうか。でも、結局技術の移り変わりが激しいんで、データベース管理者は減らせても、システム開発者は必要になるので、DBを「道具として」簡単に使ってシステム組みたい場合は従来のMySQLみたいなものの方で妥協してしまいますね。