groongaの管理画面が更新されない問題

C-APIからテーブルを作ったり消したりインサートしたりモゴモゴしたりしていると、ローカルのgroongaサーバは正しく動いているのに、ブラウザから管理画面を見たときにカラムがずれてたり値が全部0になってたりすることがある。groonga-httpdを再起動すれば直るので、これはキャッシュの問題だな…

まあでも毎回groonga-httpdを再起動すればいいといえるような頻度ではない(ものすごく頻度が高い)のでC-APIから解決する。

grn_obj* db = grn_ctx_db(&ctx);
grn_db_touch(ctx, db);

でOK!
なはずなんだが、それでも時々ずれてることがある。うーん、なんだろうなー

Linkerd

linkerd.io

まだできたばっかりのわりには随分しっかりしているようにみえる…うーんこれは来るかな?日本語の情報があんまりないので下手くそな訳おいときますね。

Linkerdとは?

Linkerdはクラウドネイティブアプリケーション用オープンソースでスケーラブルなサービスメッシュです。

我ながらひどい訳である

Linkerdの解決する問題

LinkerdはTwitterやYahoo, Google, Microsoftのような巨大なシステムを運営する際の問題を解決するために開発されました。我々の経験ではもっとも複雑で驚くべき緊急の振る舞いの原因はたいていサービスそのものではなく、サービス間の連携にあります。Linkerdはこれらの連携メカニズムそのものをコントロールするだけでなく、サービスの上に抽象層を提供することで解決できると考えています。

Linkerdは統一された単一の実装例やとサービスをまたぐ制御によって、サービスに最も適した言語をどれでも選択できるようにしています。また、アプリケーションのソースコードからサービス間のメッセージのやり取りメカニズムを邪魔しないようにすることで、アプリケーションを帰ることなくこれらのメカニズムを可視化し、コントロール可能にします。

今日、世界中の企業がソフトウェアインストラクチャの核心にフォーカスするためにLinkerdを使用しています。Linkerdはサービス間コミュニケーションの困難さ、エラー発生箇所に対処し――遅延を考慮したロードバランシングやコネクションプール、TLS、計測、リクエストレベルのルーティングも含みます――アプリケーションコードをスケーラブルで高性能かつ強靭なものとします。

使い方

Linkerdはスタンドアロンプロクシとして動作します。言語や依存ライブラリのことは気にする必要はありません。アプリケーションはふつう、既知の場所でインスタンスとして起動し、他のインスタンスからの呼び出しをプロキシサーバに送信することでLinkerdを使用します。つまり直接宛先へ接続するのではなく、対応するLinkerdインスタンスに接続して宛先のサービスを使用しているようにインスタンスを扱うのです。

ユーザの見えないところでLinkerdはルーティングルールを適用し、存在するサービスを検索するメカニズムでコミュニケーションを行い、相手のインスタンスとの間でロードバランシングを行います。またこれらの一連の挙動の間もコミュニケーションを計測し、メトリックスのレポートを行います。Linkerdへの呼び出しを行うメカニクスを遅延させることによってアプリケーションコードは下記から切り離されます。
1. 製品のトポロジーの知っていること
2. サービスを発見するメカニズムを知っていること
3. ロードバランシングと接続マネジメントロジック

アプリケーションは統一されたグローバルトラフィック制御メカニズムの恩恵も受けます。これは特に複数言語で書かれたアプリケーションにとっては重要なことです。なぜなら、ライブラリを通してこれらの一貫性を獲得するのは大変に難しいことだからです。


Linkerdのインスタンスサイドカー(アプリケーションサービスインスタンス毎に1インスタンス)またはホストに対してデプロイされます。Linkerdインスタンスはステートレスで独立しているので、既存開発のトポロジーにも簡単にフィットします。また、様々な構成と最小限の変更でアプリケーションコードと一緒にデプロイできます。

Blockstack DNS vs Traditional DNS

blockstack.org

取り急ぎ。BlockchainっていうとFinTechだけみたいなイメージがあるかもしれないが、DNSに応用できるとなると急に未来が広がる感じがしてきた。下手くそで申し訳ないがとりあえず訳置いときますね。

Blockstack DNS vs. 従来のDNS

従来のDNSとおなじように、Blockstack DNSはユーザの検索、登録、更新、転送、名前解決情報の管理ができます。しかしBlockstack DNSは従来のDNSとは基本的な部分が異なっています


■分散型


従来のDNSは国際機関ICANNのもとで運営されていましたが、Blockstack DNSへの名前登録は完全に分散管理の方法をとっています。誰でもBlockstack DNSを立てることができますし、誰もシステムの運営方法をコントロールしたりしません。これによりBlockstack DNSはincredibleになり、全く新しい技術的あるいは社会政治的な側面を回復させることができるのです。なぜそういうことができるかというと、それぞれのBlockstack DNSノードが独立して最新のネットワーク上のすべてのグローバルな名前空間を計算しているからです。従来の主なサードパーティ認証機関の信頼性は必要ありません。


■キャッシュ・ポイゾニング対策


従来のDNSは名前解決の情報をばらまくためにセキュリティ的に脆弱であるDNSキャッシュを使用するしかありませんでした。一方、Blockstack DNSの明示的なキャッシュ無効化システムは名前解決情報の転送に際してセキュリティの観念から強固なメカニズムを提供することができます。しかも提供する名前解決情報は適時かつ100%正確です。DNSSECルートサーバのような中央集権的サードパーティを使用する必要がなくなるため、Blockstack DNSはキャッシュポイズニングの脅威に対抗できます。


■暗号鍵ペアのバインディング


Blockstack DNSと従来のDNSDNS認証証明書による名前の関連付け方法が異なります。従来のDNSドメインのオーナであることを証明するために認証機関のヒエラルキーに従っていました。ここでそれぞれの認証機関は名前解決セキュリティの脅威にさらされています。どこか一つでも認証機関が誤った認証を行うと、影響は広範囲にひろがり、壊滅的な結果をもたらします。なぜなら認証機関が名前のコントロールに決定権を持っているからです。対して、Blockstack DNSのそれぞれの名前は自動的に暗号鍵ペアと関連付けられます。したがってオーナーの秘密鍵だけで名前のコントロールをすることができます。さらにいえば、エンドユーザーは名前やオーナの履歴から信頼性の監査や検査を行うことができるのです。

うーん、オルタネートルートなのかあ。世界中でBlockstack DNSが使われるようになるにはまだ時間がかかりそうや

役に立つものからたたないものまでTips集

clipping.g.hatena.ne.jp

グループからはお引っ越しできないんのんね。。。

バインディングされていない言語からgroongaにアクセスする

例えばJavaとか。というかJavaとかJavaとかJavaとか。自分で書けばいいのかもしれないが、Javaはよく知らないのであった(かけないことはないけどさ


お手軽なのはHTTPプロトコルを使うやつ。8083でgroonga httpサーバをたてておいてクライアントのコマンドラインからクエリを送る。

公式によると

HTTPでGroongaサーバーと通信する際には、以下のような形式でコマンドを指定します。:

Format: /d/COMMAND_NAME.OUTPUT_TYPE?ARGUMENT_NAME1=VALUE1&ARGUMENT_NAME2=VALUE2&...

とのことなのでselectコマンドを送ってJSONデータを得たい場合はCOMMAND_NAME="select", OUTPUT_TYPE="json"である。

$ curl "http://localhost:8083/d/select.json?table=00"

改行が…まあ読みたいときはXMLにすればいいわね

データのロードはPOSTをつかうのでブラウザからはたぶんできない(ファイルを読み込ませるならいけそうだが)

$ curl --data-binary '[{"_key":123123123, "record0_index":123123123, "record0_index.src_ip":121212}]' -H "Content-Type: application/json" "http://localhost:8083/d/load?table=record"

groongaを使う

昨日はいきなりマルチスレッドとかいい始めてしまいましたがやっぱちゃんと書かないとだめだなと気持ちを改めました…

インストー

http://groonga.org/ja/docs/install.html
こちらを参照。WindowsMacLinux各種で使える

ソースコードからビルド

昨日書いたとおりだが、githubからクローンしてくる

$ git clone --recursive git@github.com:groonga/groonga.git
$ cd groonga
$ cmake .
$ make && sudo make install

installしたくない場合はmake installは不要

アクセス方法

ちょっとよくわかってないんだが、groongaへのアクセス方法は3つある

デフォルトではGQTPが使われるらしい。このGQTPはマルチスレッド対応しており(HTTPとかもしてるけど)、特に指定しなくてもマルチスレッドで使用できる。なおサーバーを特に起動させずスタンドアロンで使用する場合もマルチスレッドで動くので、DBをopenしたらスタンドアロンサーバーが立ち上がってるんじゃないかな?よくわかんないけど。

HTTPはポートがデフォルトだと10041みたいなので自分で適当なポートを設定したい時は

$ groonga -p 8085 --protocol http -d /tmp/test.db

などとする。 -dオプションはデーモン化するやつ。

DBを作成する

DBはファイルベースになっている。簡単なのはコマンドから作るやつ

$ groonga -n /tmp/test.db

デフォルトだと/var/lib/groonga/db/に作られる…のかな?サーバー立ち上げると特に指定しない場合はそこにできるみたい。

Cのソースコードから作る場合は下記の通り。

#include <groonga.h>

int main(void)
{
    grn_ctx ctx;
    grn_obj* db;
    grn_init();
    
    grn_ctx_init(&ctx, 0);
    //オープンする。なかったら作る
    GRN_DB_OPEN_OR_CREATE(&ctx, path, 0, db);
    //確実にある場合はオープンするだけでもよい
    //db = grn_db_open(&ctx, path);

    grn_obj_close(&ctx, db);
    grn_ctx_fin(&ctx);
    return 0;    
}

テーブルを作成する

色々とオプションはあるのだが多分簡単に使うだけなら、もととなるテーブルと全文検索用インデックステーブルさえ作っておけば良さそう。
ただし全文検索用インデックスはカラムに対して紐付けを行うので、テーブルを作成する時はnormalizer_typeとtoken_bigramだけをしていしておけばよい。

コマンドラインで作る時は

> table_create --name "main_table" --flags "TABLE_HASH_KEY" --key_type "UInt32"
> table_create --name "index_name" --flags "TABLE_PAT_KEY_TYPE" --key_type "SHORT_TEXT_TYPE" --normalizer "NormalizerAuto" --default_tokenizer "TokenBigram"

おまじないみたいなもんだとおもっとけ。キーのタイプだけは指定しないといけないのとインデックス作ろうと思うとHASH_KEYタイプにしなきゃいけないみたいだが、インデックスいらないならキーのないタイプもあるのでそのあたりはよく読もう。
オープン・クローズするソースコードにテーブル作るのを足してみる。ちなみに存在確認用のAPIはないので自分で工夫しないといけない

bool create_grn_table(grn_ctx* ctx, char *_name, char *_flags, char *_key_type, char *_norm, char *_token)
{
    grn_obj *command, *name, *type, *flag, *norm, *token;
    char* result;
    uint32_t result_length;
    int32_t recv_flags;
    
    //grn_ctx_getで存在しない名前を指定した場合はNULLがかえる。これを利用して存在チェックする
    if(!grn_ctx_get(ctx, _name, strlen(_name))
    {
        command = grn_ctx_get(ctx, "table_create", strlen("table_create"));
        name = grn_expr_get_var(ctx, command, "name", strlen("name"));
        flag = grn_expr_get_var(ctx, command, "flags", strlen("flags"));
	type = grn_expr_get_var(ctx, command, "key_type", strlen("key_type"));
	norm = grn_expr_get_var(ctx, command, "normalizer", strlen("normalizer"));
	token = grn_expr_get_var(ctx, command, "default_tokenizer", strlen("default_tokenizer"));

	grn_obj_reinit(ctx, name, GRN_DB_TEXT, 0);
	grn_obj_reinit(ctx, flag, GRN_DB_TEXT, 0);
	grn_obj_reinit(ctx, type, GRN_DB_TEXT, 0);
	grn_obj_reinit(ctx, norm, GRN_DB_TEXT, 0);
	grn_obj_reinit(ctx, token, GRN_DB_TEXT, 0);

	GRN_TEXT_PUTS(ctx, name, _table_name);
	GRN_TEXT_PUTS(ctx, type, _key_type);
	GRN_TEXT_PUTS(ctx, flag, _flags);
	GRN_TEXT_PUTS(ctx, norm, _normalizer);
	GRN_TEXT_PUTS(ctx, token, _tokenizer);

	grn_expr_exec(ctx, command, 0);
	grn_ctx_recv(ctx, &result, &result_length, &recv_flags);
	grn_expr_clear_vars(ctx, command);
        //unlinkでメモリから解放しておかないとメモリーリークが発生する。
        //再帰的に開放されるのでgrn_ctx_getで取ってきたオブジェクトだけで良いみたい
        grn_obj_unlink(ctx, command);
	return memcmp(result, "true", result_length) == 0;        
    }

    return true;

}


int main(void)
{
    grn_ctx ctx;
    grn_obj* db;
    grn_init();
    
    grn_ctx_init(&ctx, 0);
    //オープンする。なかったら作る
    GRN_DB_OPEN_OR_CREATE(&ctx, path, 0, db);
    //確実にある場合はオープンするだけでもよい
    //db = grn_db_open(&ctx, path);

    //create table
    create_grn_table(&ctx, "main_table", "TABLE_HASH_KEY", "UInt32", "", "");
    create_grn_table(&ctx, "index_name", "TABLE_PAT_KEY", "ShortText", "NormalizerAuto", "TokenBigram");
    
    grn_obj_close(&ctx, db);
    grn_ctx_fin(&ctx);
    return 0;    
}

この時点ではテーブルには_idと_keyがある(かくれて_scoreとかもあるが)。まだインデックステーブルとテーブルの関係性はできていないので次はカラムをつくる

なお、grn_table_createというAPIもあるのでこっちを使っても良い。が、noramlizerオプションとかdefault tokenをどうやって指定するのかよくわからない

カラムをつくる

基本的にはスカラー型でつくるのだが、インデックスの場合だけは指定する型が違う。あと関連をもたせたりとかもしないといけない。実はカラムってのが重要なんじゃないか?

まずはメインテーブルにname, valueのカラムを追加する

> column_create --table "main_table" --name "name" --flags "COLUMN_SCALAR" --type "ShortText" 
> column_create --table "main_table" --name "value" --flags "COLUMN_SCALAR" --type "UInt32"

インデックステーブルにカラムを追加する
main_tableのnameは文字列なので全部検索のためにインデックスを作成してる。

> column_create --table "index_name" --name "index_name_col" --flags "COLUMN_INDEX|WITH_POSITION" --type "main_table" --source "name"

注意するのはtypeに指定するのがもととなるテーブル名、sourceにインデックスを作成したいカラム名を指定することくらいかな。これでmain_tableにデータを追加あすると自動的にindex_nameテーブルも更新されるようになる

Cで書くとこんな具合。

bool create_grn_table(char *_name, char *_flags, char *_key_type, char *_norm, char *_token)
{
(省略)
}

bool create_grn_column(grn_ctx* ctx, char* _table, char* _column, char* _flags, char* _type, char* _sources)
{
    grn_obj *command, *table, *name, *flag, *type, *source;
    char* result;
    uint32_t result_length;
    int32_t recv_flags;

    if(!grn_ctx_get(ctx, _column, strlen(_column))
    {
        command = grn_ctx_get(ctx, "column_create", strlen("column_create"));
        table = grn_expr_get_var(ctx, command, "table", strlen("table"));
        name = grn_expr_get_var(ctx, command, "name", strlen("name"));
        flag = grn_expr_get_var(ctx, command, "flags", strlen("flags"));
        type = grn_expr_get_var(ctx, command, "type", strlen("type"));
        source = grn_expr_get_var(ctx, command, "source", strlen("source"));

        grn_obj_reinit(ctx, table, GRN_DB_TEXT, 0);
        grn_obj_reinit(ctx, name, GRN_DB_TEXT, 0);
        grn_obj_reinit(ctx, flag, GRN_DB_TEXT, 0);
        grn_obj_reinit(ctx, type, GRN_DB_TEXT, 0);
        grn_obj_reinit(ctx, source, GRN_DB_TEXT, 0);

        GRN_TEXT_PUTS(ctx, table, _table);
        GRN_TEXT_PUTS(ctx, name, _column);
        GRN_TEXT_PUTS(ctx, flag, _flags);
        GRN_TEXT_PUTS(ctx, type, _type);
        GRN_TEXT_PUTS(ctx, source, _sources);

        grn_expr_exec(ctx, command, 0);
        grn_ctx_recv(ctx, &result, &result_length, &recv_flags);
        grn_expr_clear_vars(ctx, command);
        grn_obj_unlink(ctx, command);
        return memcmp(result, "true", result_length) == 0;
    }
    return true;
}


int main(void)
{
    grn_ctx ctx;
    grn_obj* db;
    grn_init();
    
    grn_ctx_init(&ctx, 0);
    //オープンする。なかったら作る
    GRN_DB_OPEN_OR_CREATE(&ctx, path, 0, db);
    //確実にある場合はオープンするだけでもよい
    //db = grn_db_open(&ctx, path);

    //create table
    create_grn_table(&ctx, "main_table", "TABLE_HASH_KEY", "UInt32", "", "");
    create_grn_table(&ctx, "index_name", "TABLE_PAT_KEY", "ShortText", "NormalizerAuto", "TokenBigram");

    //create column
    create_grn_column(&ctx, "main_table", "name", "COLUMN_SCALAR", "ShortText", "");
    create_grn_column(&ctx, "main_table", "value", "COLUMN_SCALAR", "UInt32", "");
    create_grn_column(&ctx, "index_name", "index_name_col", "COLUMN_INDEX|WITH_POSITION", "main_table", "name");
    
    grn_obj_close(&ctx, db);
    grn_ctx_fin(&ctx);
    return 0;    
}

こっちもgrn_column_createというAPIがある。ただしsourceの指定はどうやってするのかよくわからない。typeは指定できるんだが、、、

マルチスレッドでgroongaを使う

いきなりgroongaってなんぞやって感じですが、ElasticSearchみたいなもんです。ライブラリがCなのでCから高速に使いたいときはこっちを使うと楽です。日本人が開発してるので日本語完全対応もうれしい。もうちょっとドキュメント整理してほしいけど、、、

http://groonga.org/ja/docs/index.html

インストー

インストールは

sudo apt-get install groonga groonga-http groonga-server-gqtp

でできますが、詳しくはマニュアルを参照のこと。

(ちょっとおまけ)static ライブラリを作りたい

staticライブラリを作りたい時もありますよね?ありますよね?(白目
groongaはcmakeで楽ちんに作れますが、--help-property-listで出力してもstaticライブラリを作れそうなプロパティが出てきません。configureしてもいいけどどうしてもcmakeでやりたい。。。大丈夫。GRNG_EMBED=onとしてビルドすればできます。

まずはソースコードをダウンロードします。

git clone --recursive git@github.com:groonga/groonga.git

オプション付きでビルドします

cmake . -DGRNG_EMBED=on

libの下にlibgroonga.aができます。

ちなみにこれで作ったlibgroonga.aをリンクする際は一緒につくられるonigmoライブラリへのリンクもいっしょに追加しないといけません(onigmoを使わない場合は不要です)
例)

gcc -o sample main.c -L(groonga root)/lib -L(groonga root)/vendor/onigmo -lgroonga -ldl -lstdc++ -lonigmo -lz -lm

libstdc++, libmはbuild-essentialをインストールしたらだいたい一緒に入ると思うがlibdl, libzはlibltdl, zlib1gをインストールしないとだめみたい。

C-APIの基本的な使い方

C-APIは色々あるんだが、コマンドラインから使うのと同じ感じで使えるのが楽だと思う。というかまだよくわかっとらん。
使い方はこちらが詳しいです

qiita.com

マルチスレッドで使う

よーやく本題。
groongaはマルチスレッド対応で特になにか設定をする必要はないようなことを書いてあるが、よーく読むとgrn_ctxは各スレッドに対して一つ与えてやらないといけないらしい。
ためしにinsert/deleteをマルチスレッドでやるサンプルを書いてみます。

#include <stdlib.h>
#include <stdio.h>
#include <groonga.h>
#include <pthread.h>

#define DATA(str, key) sprintf(str, "[{¥"_key¥":¥"%d¥",¥"value¥":¥"value¥"}]", key);

grn_ctx ctx_insert;
grn_ctx ctx_delete;

grn_obj* open(grn_ctx* ctx, const char* path)
{
    grn_obj* db;
    int i = 0;
    //grn_ctxオブジェクトの初期化
    ctx = grn_ctx_open(0);
    grn_ctx_init(ctx, 0);
    //DBが存在しない場合は作成する。ある場合は開いてctxと関連付ける
    GRN_DB_OPEN_OR_CREATE(ctx, path, 0, db);
    //JSONで出力させる
    grn_ctx_set_output_type(ctx, GRN_CONTENT_JSON);
    return db;
}

int close(grn_ctx* ctx, grn_obj* db)
{
    grn_obj_close(ctx, db);
    return grn_ctx_fin(ctx);
}

int insert_record(grn_ctx* ctx, char* data)
{
    //レコードを追加する処理
}

int delete_record(grn_ctx* ctx, char* key)
{
    //レコードを冊書する処理
}

void *insert(void* arg)
{
    int i = 0;
    char str[128];
    for(i = 0; i < 100; i++)
   {
        DATA(str, i);
        insert_record(&ctx_insert, str);
   }
}
void *delete(void* arg)
{
    usleep(100);
    int i = 0;
    char index[2];
    for(i = 0; i < 100; i++)
    {
        sprintf(index, "%2d", i);
        delete_record(&ctx_delete, index);
    } 
}

int main(void)
{
    grn_init();
    grn_obj* db_insert = open(&ctx_insert, "db");
    //おなじDBなので同じオブジェクトを使い回せば良いのではと思ったが今のところうまく行ってないので
    grn_obj* db_delete = open(&ctx_delete, "db");

    //pthreadでスレッドを作り、joinさせて動かす
    pthread_t delete_th, insert_th;
    pthread_create(&insert_th, NULL, insert, NULL);
    pthread_create(&delete_th, NULL, delete, NULL);

    pthread_join(insert_th, NULL);
    pthread_join(delete_th, NULL);

   close(&ctx_insert, db_insert);
   close(&ctx_delete, db_delete);
   
    //一回で良い
    grn_fin();
    return 0;
}

割りと省略しまくりですが、大事なのはgrn_ctx, grn_dbをそれぞれのスレッドに対して作ること。