2018/02/24

[tech]お悩みのKVS DB

愚痴というか、悩みというか、反省談というか。


いま作っているシステムでは、lmdbというKey-Value Storeのデータベースを使っている。
というといろいろ考えて選択したように見えるが、

  • SQL使うほどでも無い
  • C言語で使えるもの
  • よく使われている

というところで選んだだけだ。
そもそも、私はストレージが使えない組み込みシステムをやることが多かったので、DB自体くわしくない。


lmdbは、NoSQLと呼ばれる類のDBらしい。
https://ja.wikipedia.org/wiki/NoSQL

DBという全体集合があり、その部分集合としてSQL DBがある。
そして、NoSQLは、その反転した集合になりそうな気がするのだが、Wikipediaに書いてある"Not only SQL"が語源だとするならば、SQLを含んでいてもよいし、含まなくてもよい、ということになりそうだ。


lmdbは、SQL文は使わない。
DBの要素としては、こういうものがある。

  • environment
  • DB
  • key
  • data

まず、environmentという大きな広場があり、その中にDBを複数持つことができる。
そして、DBの中にkey-dataのセットでデータが格納されていく。

「トランザクション」というアクセス単位は、environment単位になる。
これに気付かなくてねぇ・・・。
トランザクションってDB単位でやるものだろうと思い込んでいたので、DBのオープンAPIにトランザクションが必要だとわかったときはショックだったわ。

イメージとしては、ドライブとディレクトリみたいなものか。
environmentがドライブで、DBがディレクトリ。
そして、ロックを掛けられるのはドライブ単位だ、と。

どうしても同時にロックしたくて、でもシステム上うまいことやれそうにないなら、environment自体を分けてしまうしかない。
システムを作って半年以上あれこれいじっているが、設計がよくなかったのか、lmdb以外だったらうまくやれたのか、未だによくわかっていない。


結局、environmentを分けることで対応したのだが、未だにどうするのがよかったのか、あるいはどうする方がよいのか、私の中で結論が出ていない。

SQLとかリレーショナルとか、そういう関連性がないということだけはわかっている。
そして、C言語から使いたい。
あとはメンテナンスされていっているとか、枯れているとか、そのくらいか。


https://ja.wikipedia.org/wiki/NoSQL
https://en.wikipedia.org/wiki/NoSQL


うーん、jaとenのWikipediaを並べてみたのだが、内容はともかく、数が多い!
これだけあるということは、特色がそれぞれあって、望む形はいろいろあるということなのか・・・。
「Aである」という事象はAだけだが、「Aではない」という事象は無限にあるのと同じか。

つまり、私が"lmdbでよかったんだろうか"という悩みは、ある意味では当然のことというわけだ。
無限にあるものの中から正解を1つだけ見つける、ということはあり得ないからだ。

[c/c++]memory.hは標準ヘッダではない

よくやってしまうのだが、malloc()やmemcpy()を使ったのに、ヘッダファイルを追加し忘れていることがある。
まあ、コンパイル時にエラーが出るので分かるのだが、問題はその次だ。
どれをインクルードするとよいのだっけ?


ふっと思いつくのが、連続データに対する処理だからストリング命令、ということで<string.h>。
しかし、心の中で「もしかしたら<memory.h>だったかも・・・」と心配になる。
コンパイルすればわかるのだけど、なんか追加した標準ヘッダを間違えるって、なんか素人っぽいじゃないか。
できれば、修正後は一発で正解したいのだ。

そして、malloc()/free()は<stdlib.h>だということを思い知らされるのである。。。

どうでもいいけど、strcpy()なんかも<string.h>に入っていると、文字列用のヘッダだと勘違いしてしまうよね。


それはさておき。
<stdlib.h>と<string.h>をインクルードすれば何とかなるのはわかるが、では<memory.h>には何が入っているのだろう?

オライリーのCクイックリファレンスで調べてみたのだが・・・<memory.h>は標準ヘッダではなかった!
そうか、そうなんだ・・・。

/usr/include/memory.hを見ると、<features.h>と<string.h>をインクルードしているだけだった。
<features.h>なんて通常はインクルードさせたことが無かったのだが、GLIBCのバージョンなどいろいろ定義されている。
じゃあ、単に<string.h>をインクルードするより<memory.h>の方がいいのか?と思ったが、<string.h>も先頭で<features.h>をインクルードしていた。


というわけで、私の結論はこうだ。

  • <string.h>でも<memory.h>でもいい
  • <memory.h>は標準ヘッダではないので、すべてのコンパイラが持っているかは定かでは無い
  • 標準ヘッダでないからといって、<memory.h>をインクルードしていても変ではない


標準じゃないからといって、使って問題があるわけではないのだ。
それに、個人的には「str***()」は<string.h>に、「mem***()」は<memory.h>に、となっていた方がわかりやすいと思う。

2018/02/17

[btc]addwitnessaddressのアドレスからdumpprivkeyはできない

bitcoindの話だ。

bitcoin-cli getnewaddressすると、P2PKHのアドレスが返ってくる。
HDウォレットのインデックスがインクリメントされて新しいアドレスが生成されたということか。

そのアドレスについてbitcoin-cli addwitnessaddressすると、P2WPKHのアドレスが返ってくる。
ただし、現在(2018/02/17)のところ、bitcoindではnativeのP2WPKHアドレスを扱うことができない。
だから、P2WPKH nested in BIP16 P2SHというアドレスがが返ってきている。

nested in BIP16 P2SH形式のよいところは、見た目上はP2SHアドレスになっているというところだ。
これなら、segwitに対応していないウォレットであっても送金することができる。
じゃあ、その形式だけでいいじゃないか、となりそうだが、segwitのnativeアドレスには利点がある。
その場合には、トランザクションのサイズを小さくすることができるのだ。

nativeなP2WPKHの場合、今までのscriptSigの部分がまったく無くなり、witnessのセクションに移動する。
segwitにするとブロックに入るトランザクションの数が増えるというが、たしかにトランザクションの数は増えるものの、トランザクション自体のサイズについては別の話だ。
nested in BIP16 P2SH形式の場合、scriptSigにもデータを置かないと既存ウォレットとの互換性が無くなってしまうので、トランザクションとしてのサイズが多少増えてしまうのだ。
ブロックに入れる際にwitnessの部分は別の場所に入るため、そこまで影響は無いのかもしれんが、大きくなることはなるのだ。
これがnative形式だとトランザクション自体が別物という扱いになるからかscriptSigのサイズは0になり、署名などはすべてwitnessに押し込められる。


私が不勉強なので、ブロックに取り込むところなどのしくみをよくわかっていないのだが、まあ、そこはbitcoindなんかがやってくれるからいいや、と割り切っている。
フルノードの庇護を受けている間は、けっこう楽ができるのだ。


ただ、というか、しょうがないか、というところだが、addwitnessaddressでつくったアドレスはdumpprivkeyで秘密鍵が取って来れないのだ。
P2SHという時点であきらめてしまう気がする。


次の0.16ではBECH32形式のアドレスが使えるようになるとか。
そうなると、直接nativeの形式でアドレスを生成できるようになるのかもしれん。

2nd Layerというか、BOLTではfundingにnativeなアドレスを使うことになっているので、今の時点では何とかしてP2WPKHから送金させる必要があるのだった。

まあ、そういうこともあるさっ。

2018/02/15

[c/c++]getopt_long()のlongindexが返るのは戻り値が0のときだけ?→ときだけだった

(2018/02/17更新)

ずっと先延ばしにしてきた、getopt_long()の勉強をすることにした。

組み込みだと、引数とか設定できないじゃないか。
値はNVに保存して、起動時に読み出す。

そういう生活をしていたので、Linuxでツールを作るときも、設定ファイルを読ませるようにしているのだが、これの評判が悪い。。
「ファイルでやる意味が分からん」とまでいわれてしまい、しぶしぶ引数を列挙するタイプで対応していた。

が、似たような機能を同じツールで実現したいことが多くなり、引数で見分ける必要性が出てきた。
しぶしぶgetopt()で対応していたのだが、さらに引数が増え、使える文字が減り、自分でも訳がわからなくなってきたのだ。


以上、自分の中で、どうしてもやらないといけないという動機づけを行う作業でした。


最初に見たのが、こちら。

getopt_long関数の利用 - コマンドラインオプションの処理 - 碧色工房

まずは動かして考えよう、とWindowsのWSLでやってみたのだが、なんか、ときどきSegmentation Faultが発生する。


よくわからんので、こちらのサンプルも動かしてみた。

Man page of GETOPT

こちらは動いたので見比べたのだが、Man pageの方はlongindexを初期化しているし、戻り値が0のときにしかlongindexを使っていない。
最初の方は未初期化で、getopt_long()に任せている。


しかし、説明文を読む限りでは、longindexをNULL以外で指定しておけば、正常な場合はインデックス値が戻ってきそうである。
うーん、WSLの不具合かなぁ。。。


2018/02/17

Azureで動かしているUbuntuで確認したが、やはり同じ動作になった。
WSLよ、疑って済まん。。。


よく読めば、こちらのサイトにもちゃんと書かれていた。
getopt_long関数の利用 - コマンドラインオプションの処理 - 碧色工房


manとしては、こう書かれている。

longindex は、NULL でなければ、 長いオプションのインデックスを longopts からの相対位置として保持している変数へのポインターとなる。

つまり、「長いオプションのインデックスを」だから、短いオプションのときには更新しない、ということか。
読み取るのが難しいですよ。。。


長いオプションかどうか見分けたい場合は、longindexを-1などに初期化してからgetopt_long()を呼び出して、更新されたかどうか確認すればよいのかな。
そういうシーンは少ない気がするので、私の場合はNULLにしてしまうだろう。


せっかくなので、オプションの使い方で気になるところも見ておこう。
「値付きのオプションに、イコールを付けてよいのかどうか」問題だ。
ソースファイルは、工房さんのサンプルをそのまま使っている。

$ ./tst --clear 100
c = 1, 100


$ ./tst --clear=100
c = 1, 100


$ ./tst -c=100
c = 1, =100


$ ./tst -c 100
c = 1, 100


./tst -c100
c = 1, 100

あー、こうなるんだ。

長いオプションの方はイコールを付けて値、短いオプションの方はスペースを入れるか入れないかで値、という使い方が一般的なようだ。
短いオプションでイコールを使ってもうまくいくやつがあるけど、あれはアプリ側で取り除いてるのかな。

2018/02/14

[c/c++][make]dependを作成せずにcleanしたい

例えば、test.cとtest.hがあり、test.cはtest.hをincludeしていたとしよう。
依存関係を設定せずにMakefileを作ると、一度ビルドしてからtest.hを変更しても、makeで作り直してくれない。


では、依存関係を"gcc -MM"で作ってincludeさせるとしよう。

01: SRC=test.c
02: OBJ=test.o
03: BIN=tst
04: 
05: all: $(BIN)
06: 
07: $(BIN):$(OBJ)
08: 	gcc -o $(BIN) $(OBJ)
09: 
10: $(OBJ):
11: 	gcc -c $(SRC)
12: 
13: clean:
14: 	-rm -rf $(OBJ) $(BIN) .Depend
15: 
16: .Depend:
17: 	gcc -MM $(SRC) > .Depend
18: 
19: -include .Depend


これで初回ビルドすると、こう。

$ make
gcc -MM test.c > .Depend
gcc -c test.c
gcc -o tst test.o

2回目は、こう。

$ make
make: Nothing to be done for 'all'.

test.hだけ変更すると、こう。

$ make
gcc -c test.c
gcc -o tst test.o

言うことなしだ。


が、cleanする。

$ make clean
rm -rf test.o tst .Depend

ここまではよいのだが、続けてcleanする。

$ make clean
gcc -MM test.c > .Depend
rm -rf test.o tst .Depend

そう、一度.Dependを生成してから削除するのだ。。。

細かい動作は知らないのだが、.Dependをincludeするときに、依存を調べてしまうんじゃないだろうか。


GNU make 日本語訳(Coop編) - makeの実行方法

$(MAKECMDGOALS)などというものがあるのか!

01: SRC=test.c
02: OBJ=test.o
03: BIN=tst
04: 
05: all: $(BIN)
06: 
07: $(BIN):$(OBJ)
08: 	gcc -o $(BIN) $(OBJ)
09: 
10: $(OBJ):
11: 	gcc -c $(SRC)
12: 
13: clean:
14: 	-rm -rf $(OBJ) $(BIN) .Depend
15: 
16: .Depend:
17: ifneq ($(MAKECMDGOALS),clean)
18: 	gcc -MM $(SRC) > .Depend
19: endif
20: 
21: -include .Depend

$ make clean
rm -rf test.o tst .Depend
$ make clean
rm -rf test.o tst .Depend

そうそう、それでいいんだよ、それで!