あと100個のBITFIELD
原文は Salvatore Sanfilippo により に公開されました。 このブログを購読する
今日でRedisは7周年を迎えた。この記念日を少し祝うために、私はここ数日、BITFIELDというクレイジーな新コマンドを実装するという楽しいコーディングマラソンに没頭していた。
このコマンドの本質自体は新しいものではない。私や他の人たちが過去に提案したことはあったが、真剣に検討されたことはなく、アイデアはいつも少し奇妙に見えていた。Redisにはすでにビット操作がある。一部のユーザーはそれを気に入っており、大量のデータをコンパクトに表現する良い手段になっている。しかし、これまでは各ビットを個別に扱ってきた。ビットをセットしたり、テストしたり、取得したり、範囲内で立っているビットを数えたりといった具合だ。
ビットフィールドを実装したらどうだろうか?大小さまざまな、任意のビット長の整数を、任意のオフセットに配置できるものだ。これがあれば、Redisの文字列を5ビット符号付き整数の配列として使え、1ビットたりとも無駄にせずに済む。
数日前、Redis LabsのYoav Steinberg氏が、ビットオフセットに格納された任意サイズの整数を扱う一連のコマンドを、より真剣な形で提案してきた。メールを読んだとき、私は思わず微笑んだ。これはある意味、私の密かな夢だったからだ。Yoav氏の提案を起点に、Redis Labsのエンジニアからの他のフィードバックも取り入れながら、サブコマンドを持つ単一のコマンドとして初期仕様をまとめた。型定義には短い名前を使い、オーバーフロー時の挙動についてもきめ細かく制御できるようにした。
最初の実装は数分前に完成したばかりだ。今日リリースする予定だったので、Redisが誕生日に我々がちゃんと働いていることを評価してくれることを願っている。
出来上がったBITFIELDコマンドは、次のようなサブコマンドをサポートしている。
SET <type> <offset> <value> — 指定した値をセットし、以前の値を返す。
GET <type> <offset> — 指定した値を取得する。
INCRBY <type> <offset> <increment> — 指定したカウンタをインクリメントする。
さらに、OVERFLOWというメタコマンドがあり、これから続くコマンドのオーバーフロー時の挙動を設定するために使う(お察しのとおりだ)(そのためOVERFLOWは何度でも指定できる)。
OVERFLOW SAT — サチュレーション。どちらかの方向にオーバーフローした場合、整数はその方向の最大値で飽和する。
OVERFLOW WRAP — いわゆるラップアラウンドだが、興味深いのは符号付き整数でも同様に機能し、最も負の値や最も正の値へとラップすることだ。
OVERFLOW FAIL — このモードでは、オーバーフローが発生する場合はそもそも操作が実行されない。
整数型は「u」または「i」のプレフィックスにビット数を続けて指定する。例えばu8、i5、u20、i53は有効な型だ。ただし制限が一つある。現在のRedisプロトコルでは64ビットの符号なし整数を返せないため、u64は指定できない。
いくつか例を見てみよう。8ビットの符号なし整数をインクリメントするには、次のようにする。
127.0.0.1:6379> BITFIELD mykey incrby u8 100 1
1) (integer) 3これは、オフセット100(ビットマップの101ビット目)にある8ビットの符号なし整数をインクリメントしている。
ただし、オフセットの指定には別の方法もある。オフセットの前に「#」を付けることで、「文字列を指定サイズのカウンタの配列として扱い、N番目のカウンタを操作する」という意味になる。基本的に、8ビット型で#10を使えば、オフセットは8*10を掛けて求められるということだ。こうすれば、オフセット計算を自前で行うことなく、複数のカウンタをそれぞれ独立して扱える。
127.0.0.1:6379> BITFIELD mykey incrby u8 #0 1
1) (integer) 1
127.0.0.1:6379> BITFIELD mykey incrby u8 #0 1
1) (integer) 2
127.0.0.1:6379> BITFIELD mykey incrby u8 #1 1
1) (integer) 1
127.0.0.1:6379> BITFIELD mykey incrby u8 #1 1
1) (integer) 2オーバーフローを制御できる点も面白い。例えば、デフォルトのオーバーフローポリシーである「wrap」では、1ビットの符号なしカウンタは0と1の間でトグルすることになる。
127.0.0.1:6379> BITFIELD mykey incrby u1 100 1
1) (integer) 1
127.0.0.1:6379> BITFIELD mykey incrby u1 100 1
1) (integer) 0
127.0.0.1:6379> BITFIELD mykey incrby u1 100 1
1) (integer) 1
127.0.0.1:6379> BITFIELD mykey incrby u1 100 1
1) (integer) 0ご覧のとおり、0と1が交互に現れる。
サチュレーションも有用だ。
127.0.0.1:6379> bitfield mykey overflow sat incrby i4 100 -3
1) (integer) -3
127.0.0.1:6379> bitfield mykey overflow sat incrby i4 100 -3
1) (integer) -6
127.0.0.1:6379> bitfield mykey overflow sat incrby i4 100 -3
1) (integer) -8
127.0.0.1:6379> bitfield mykey overflow sat incrby i4 100 -3
1) (integer) -8お分かりのとおり、3ずつ減らしても-8を下回ることはない。
なお、1つのコマンドで複数の操作を実行できる。常に結果の配列が返される。
127.0.0.1:6379> BITFIELD mykey get i4 100 set u8 200 123 incrby u8 300 1
1) (integer) -8
2) (integer) 123
3) (integer) 7このコマンドの想定用途は、リアルタイム分析やA/Bテスト、整数のオーバーフローを利用してユーザーに毎回少しずつ異なるものを表示するといったものだ。これほど多くの小さなカウンタを共有かつメモリ効率良く詰め込めるという特性は、さまざまな形で活用できるが、それはRedisコミュニティの優秀なプログラマーたちへの宿題としておこう。
このコマンドは数週間以内にRedisの安定版にもバックポートされる予定なので、すぐに使えるようになるはずだ。
実装に興味があるだろうか?おそらく想像以上に複雑だ: https://github.com/antirez/redis/commit/70af626d613ebd88123f87a941b0dd3570f9e7d2
記事をランダムに読む
コメント
ログインしてコメントする