はじめに
ふと大量のフラグを持っているテーブルを見かけて、どうにかできないかなーと考える機会がありビットで管理することで保守性が高くなるのではと思い考えの整理とアプトプットのため記事を書きました。
利用言語/ツール
- Go
- Spanner
サンプルコード
bool値でのフラグ管理
テーブル定義を行い際にフラグを持たせるとすると下記のようなパターンが一般的だと思います。
CREATE TABLE Users (
UserId STRING(64) NOT NULL,
Name STRING(128) NOT NULL,
EntranceNormalFlag BOOLEAN NOT NULL,
EntranceSpecialFlag BOOLEAN NOT NULL,
EntrancePremiumFlag BOOLEAN NOT NULL,
) PRIMARY KEY(UserId);
カラムが何の意味を持っているのかわかりやすいですね。
しかし要件によってはフラグの増減が定期的にある可能性もあります。
その場合に都度マイグレーションをかけるのは辛いですね。
またSQLで検索する際に多数のフラグを使う場合に何をしているのかわかりずらくなります。
2進数でのフラグ管理
ビット演算でフラグの管理をする場合は下記のようなSchemaになります。
CREATE TABLE Users (
UserId STRING(64) NOT NULL,
Name STRING(128) NOT NULL,
EntranceFlag INT64 NOT NULL
) PRIMARY KEY(UserId);
Flagを管理するカラムに何種類のフラグが存在して、実際に登録されたデータを見てもわからないと思います。
しかしフラグの増減が発生してもマイグレーションの必要がないのがメリットです。
またフラグ管理はアプリケーション側のコードで行うのでDB上で視覚的にわかりやすくすることを捨てています。
では次にアプリケーション側でどのようにフラグを管理するのか紹介します。
フラグ管理のコード
フラグ管理を行うコードは下記になります。
type EntranceFlag int64
const (
EntranceFlag_UNSPECIFIED EntranceFlag = 0b000
EntranceFlag_NORMAL EntranceFlag = 0b001
EntranceFlag_SPECIAL EntranceFlag = 0b010
EntranceFlag_PREMIUM EntranceFlag = 0b100
)
func HasEntranceFlag(dst int64, src EntranceFlag) bool {
return dst & int64(src) == int64(src)
}
func MergeFlag(flags []EntranceFlag) int64 {
result := int64(0b0)
for _, v := range flags {
result = result | int64(v)
}
return result
}
フラグの種類は型定義で行い必要分のフラグを用意します。
今回紹介しているコード上では特定のフラグを持っているか、また複数のフラグをマージして返却する関数を定義しています。
アプリケーション上で必要なロジックに応じて関数を定義してフラグ単体のロジックをまとめていけばいいと思います。
メリット
- 1カラムでフラグ管理ができるのでマイグレーション頻度を落とせる可能性がある
- 複雑なフラグ検索をする場合に高速に検索が可能
- アプリケーション側に検索ロジックを持たせることが可能
- SQLでは単純な検索を行う
デメリット
- フラグを2進数で管理するため保守性が低下する可能性がある
- 特にEntranceFlagのコードは読み辛い
- DBの値を見た時に何のフラグが立っているのかわかりずらい
- 気軽にSQLで検索をする際の運用コストが高い
まとめ
いかがでしたでしょうか?
特段真新しい手法ではないと思いますが、実装のHowとして選択肢として持っておくのはありなのかなと思います。
実装の具体についてはサンプルコードのテストコード上で表現していますので、興味があればご一読ください
記事を読んでいただきありがとうございました。