あのBITFIELDをあと100個
今日でRedisは7周年を迎えました。ささやかながら記念に、この数日間、BITFIELDというちょっとクレイジーな新コマンドを実装する楽しいコーディングマラソンに没頭しました。
このコマンドの本質自体は目新しいものではありません。以前から私や他の人たちが提案してきたことですが、真剣に検討されたことはなく、アイデアはいつも少し奇妙に映っていました。Redisにはすでにビット操作があります。一部のユーザーはこれをとても気に入っていて、大量のデータをコンパクトに表現する優れた方法になっています。しかし、これまではビットを1つずつ個別に扱ってきました。ビットをセットしたり、テストしたり、取得したり、範囲内で立っているビットを数えたりといった具合です。
では、ビットフィールドを実装したらどうなるでしょうか。短いものから長いものまで、任意のサイズの整数を任意のオフセットに配置できれば、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はいずれも有効な型です。1つ制限があります。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オーバーフローを制御できるのも面白いところです。たとえば、1ビットの符号なしカウンターは、デフォルトのオーバーフローポリシーである「wrap」では、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
記事をランダムに読む