MongoDBのcursor

f:id:y_d:20180605222656p:plain

下記の感じでなんとなくでMongoDBのcursorを使っているけど、今までちゃんと公式ドキュメントを読んだ事がなかったので改めてドキュメントを読んでみた。

var cursor = db.users.find();
while (myCursor.hasNext()) {
   printjson(cursor.next());
}

現行バージョン:
https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/

過去バージョン:
https://docs.mongodb.com/v3.0/core/cursors/

仕様概要

軽く読んだ感じ次の仕様になっているらしい。

  1. cursor.next() を使ってループする実装にしていても、実は毎回MongoDBサーバーからデータを取得している訳ではない(ネットワーク負荷がボトルネックになってパフォーマンスが出ないので)。

  2. MongoDBドライバは実は裏でMongoDBサーバーから小分けにデータを受け取っていて、 cursor.next() はその小分けのデータから1ドキュメントずつ返しているだけ。小分けで受け取っていたデータがなくなったらMongoDBサーバーに問い合わせて小分けのデータを補充する。(裏でMongoDBドライバがMongoDB Wire Protocolを使ってデータ取得やらなんやらしているが、それをMongoDBドライバが隠匿している)

  3. MongoDBドライバがどれくらい小分けにして受け取るかというと、一番最初は101個のドキュメントをまとめて取得し、それ以降は16MBずつ取得する。この挙動は batchSize()limit() を使って制御する事が可能。
    ※3.4より前のバージョンだとちょっと挙動が違う

  4. MongoDBドライバは、MongoDBサーバーから小分けにデータを貰ってくるが、MongoDBサーバーはその瞬間の最新データを返す。したがってデータの更新が重なると1回のカーソルのループで、同じレコードが複数回返ってくる事がある。
    ※insert,updateでnatural orderが変わった時に複数回返ってくると思われる
    ※ただし前述の通り、結果が101ドキュメント以下なら複数回返ってくるのは起こり得ないはず

  5. 上記の、同じレコードが複数回返ってくる問題は cursor.snapshot() を使って回避する事ができる。

という事で、cursorを使っていても最新のデータが返ってくるケースがあるっぽい(もちろん古いデータが返ってくることもある)。

個人的に注意が必要だと思ったのは下記の点。

  • cursorから返ってくるドキュメントは、 find() した瞬間のドキュメントが返ってくることもあれば、そうでないこともある
  • cursorをループしていると、同じドキュメントが複数回返ってくることがある

概念図

文章だけだとわかりづらいので図にしてみる。

+---------+   +---------+   +--------+
| MongoDB |   | MongoDB |   | Client |
| Server  |   | Driver  |   |        |
+-+-------+   +----+----+   +------+-+
  |                |               |
  |                |     find()    |
  |    OP_QUERY    <---------------+
  <----------------+               |
  | 101 documents  |               |
  +---------------->     cursor    |
  |                +--------------->
  |                |               |
  |                | cursor.next() |
  |                <---------------+
  |                |   document    |
  |                +--------------->
  |                |               |
  |                | cursor.next() |
  |                <---------------+
  |                |   document    |
  |                +--------------->
  :                :               :
        101個documentを取得する
  :                :               :
  |                | cursor.next() |
  |   OP_GET_MORE  <---------------+
  <----------------+               |
  | some documents |               |
  +---------------->   document    |
  |                +--------------->
  |                |               |
  |                | cursor.next() |
  |                <---------------+
  |                |   document    |
  |                +--------------->
  |                |               |

補足

  • MongoDBサーバーとMongoDB Driverは MongoDB Wire Protocol を使ってやり取りを行う。
  • (言語は何でもいいけど)例えばPHPからMongoDBクラスを使ってMongoDBサーバーと通信を行うとき、裏では MongoDB Wire Protocol を使っている。
  • PHPからすると、MongoDBからcursorを取得して、cursorから 1 documentずつドキュメントを受け取るように実装したとしても、実はある程度の塊でMongoDBサーバーからMongoDB Driverが複数のドキュメントを受け取っていて、MongoDB Driverがそれを 1 documentずつ返している。

参考資料

2018初夏、Visual Studio Codeに夢中

エディタなんて好きなものを使えばいいと思うし、他人に押し付けるものでもないし、なんなら自分はIDE派でもあるんだけど、最近Visual Studio Codevscode)を気に入って使っている、という話。

Microsoftの印象

自分にとってMicrosoftといえば悪いイメージしかなかったんだけど、最近はむしろ頑張ってるイメージが強い。なんなら軽く応援している。
きっとその要因の一つはvscodeだと思う。

vscodeがいい感じ

自分は最近少しだけGo言語を書いている。
Go言語を書いている人はvscodeを使っている人が多いと思う(超主観)。もちろん公式にはvimとかGoLandとかも紹介されているけど、自分はvscodeを気に入って使っている。

少し前までは色々なエディタを試してはしっくりこないくて乗り換える、というのを何年か繰り返していたけど、これをきっかけにvscodeでもういいか、という気になった。

コミット量

vscodeと他のエディタのコミット量をみるとこんな感じ。
vscodeが出て以降atomの開発が緩やかになった感じがする。
だからvscodeがいいんだ、という事ではもちろんないんだけど最近atomが元気ない感じがする。

atom/atom

f:id:y_d:20180518071802p:plain

adobe/brackets ※個人的に昔結構好きだった

f:id:y_d:20180518075820p:plain

Microsoft/vscode

f:id:y_d:20180518071757p:plain

↓雑に重ねるとこんな感じ(赤がvscode、青がbrackets

f:id:y_d:20180518075826p:plain

気に入らないところ

vscodeに対して不満に思っている箇所も多くあるけど、一番の不満は見た目がイマイチなところ。
アプリのアイコンだったり左側バーに並んだアイコン、隙間なくびっちり詰まったファイルエクスプローラーだったり。もうちょっとどうにかしてほしい。
※テーマを入れると配色やファイルアイコンの変更はできるけど



以上vscodeの見た目をどうにかして欲しいという話でした

golangでKVSサーバーを実装してみた

golangの勉強がてらKVSのDBサーバーを雑に実装してみた。
DBの実装自体はbuntdbというライブラリをそのまま使っているので、あまり大した事はやっていないけど。

実装したDBサーバーについて

  1. OrenoDBという名付けた(メンテするつもりはないので雑な命名)
  2. メモリベースのkey-valueストア
  3. ポート8888固定でtcp接続を待ち受ける
  4. redisプロトコルを採用したので一般的なRedisクラアントライブラリで接続できる

github.com

全体構成

大体こんな感じで実装されている。

  1. DBサーバーを起動して8888ポートで待ち受ける。
  2. clientが8888に接続しにくる。
  3. DBサーバーはclientを受け入れてgoroutineを起動する。
  4. groutineではclientからデータが送信されてくるのを待ち受ける。
  5. clientから送られた文字列を溜め込んで、コマンドとして解釈可能なレベルまで文字列が溜まるのを待つ。
  6. コマンドとして解釈可能なら、それを使ってDBエンジンにデータをRead/Writeしに行く。

f:id:y_d:20180418005307p:plain

その他

  • 軽く試した感じRedisよりもRead/Writeが速かった。これはBuntDBがredisよりも速い実装だから当たり前だけど。※index貼っていないので貼るともっと速くなるかも。
  • goroutineとchannelをうまく使いこなせていないのでgoroutineがリークしてるかもしれないのと、適当な実装がたくさんある。