ふくふくHukuhuku Inc.
EP.13Algorithms 10分公開: 2026-05-11

LSM Tree:Cassandra・RocksDB・BigTable の書込み性能を支えるストレージエンジン

「ランダム書込みを順次書込みに変換」して 100 倍速くする LSM Tree を Python で実装。Cassandra / RocksDB / BigTable / DynamoDB の中身。

#LSM Tree#Cassandra#RocksDB#ストレージエンジン
CO📔 Google Colab で開く(上から順にセルを実行)
シェア

Cassandra が秒間 1 万 write/node を捌く / RocksDB が SSD の限界に近い書込み性能を出す / BigTable が Google の規模を支える ── これらすべての中核に LSM Tree (Log-Structured Merge Tree) がある。「ランダム書込みを順次書込みに変換する」 という発想が、ストレージ性能の壁を打ち破った。

1. これで何が動いているか

  • Apache Cassandra / ScyllaDB
  • RocksDB / LevelDB (Facebook / Google)
  • Google Bigtable / HBase
  • DynamoDB / Riak
  • InfluxDB / TimescaleDB / VictoriaMetrics (時系列 DB)
  • TiKV (TiDB) / CockroachDB

2. 仕組みのざっくり

  1. 1書込み: WAL (Write-Ahead Log) に append + MemTable (in-memory ソート済) に追加
  2. 2MemTable がフル: 新しい MemTable に切替、古い MemTable を SSTable (Disk) として書き出し
  3. 3SSTable: Immutable、ソート済、Bloom Filter + Sparse Index 付
  4. 4Compaction: 複数 SSTable をマージ、削除済 (tombstone) を物理削除
  5. 5読込み: MemTable → 新しい SSTable → 古い SSTable の順でキー検索 (Bloom Filter で skip)

3. Python 実装 (簡略版・動作確認済)

MemTable + SSTable の最小実装
Python
class LSMTree:    def __init__(self, mem_limit=4):        self.memtable = {}        # in-memory dict (本物は Skip List)        self.sstables = []        # [{k: v}, ...] (新しいものが末尾)        self.mem_limit = mem_limit
    def put(self, k, v):        '''書込み: MemTable に追加、フルなら flush'''        self.memtable[k] = v        if len(self.memtable) >= self.mem_limit:            self._flush()
    def _flush(self):        '''MemTable → SSTable'''        sstable = dict(sorted(self.memtable.items()))        self.sstables.append(sstable)        self.memtable = {}
    def get(self, k):        '''読込み: MemTable → 新しい SSTable → 古い SSTable の順'''        if k in self.memtable:            return self.memtable[k]        for sst in reversed(self.sstables):            if k in sst:                return sst[k]        return None
    def delete(self, k):        '''削除は tombstone を書く (物理削除は Compaction 時)'''        self.put(k, None)  # None = tombstone
    def compact(self):        '''複数 SSTable をマージ + tombstone を物理削除'''        merged = {}        for sst in self.sstables:            for k, v in sst.items():                if v is None:                    merged.pop(k, None)  # tombstone は削除                else:                    merged[k] = v        self.sstables = [merged]
# 動作確認lsm = LSMTree(mem_limit=4)for i in range(10):    lsm.put(f'k{i}', f'v{i}')
print(f'SSTable 数: {len(lsm.sstables)}')print(f'MemTable サイズ: {len(lsm.memtable)}')print(f'k3 = {lsm.get("k3")}')
# 削除lsm.delete('k3')print(f'\n削除後 k3 = {lsm.get("k3")}')
# Compactionlsm.compact()print(f'Compaction 後 SSTable 数: {len(lsm.sstables)}')print(f'Compaction 後 k3 = {lsm.get("k3")}')

4. Bloom Filter で読込み高速化

SSTable に Bloom Filter を付けて Read Amp を削減
Python
from typing import Optionalimport hashlib, math
class BloomedSSTable:    def __init__(self, data: dict, p=0.01):        self.data = data        self.bloom = self._build_bloom(list(data.keys()), p)
    def _build_bloom(self, keys, p):        n = max(len(keys), 1)        m = int(-n * math.log(p) / (math.log(2) ** 2))        bits = bytearray(math.ceil(m / 8))        k_h = max(int(m / n * math.log(2)), 1)
        def idx(item):            b = item.encode()            h1 = int.from_bytes(hashlib.sha256(b).digest()[:8], 'big')            h2 = int.from_bytes(hashlib.blake2b(b, digest_size=8).digest(), 'big')            return [(h1 + i * h2) % m for i in range(k_h)]
        for k in keys:            for i in idx(k):                bits[i // 8] |= (1 << (i % 8))
        self._idx_func = idx        self._bits = bits        return None
    def __contains__(self, k):        '''Bloom 判定: No なら確実、Yes なら次に data を見る'''        return all(self._bits[i//8] & (1 << (i%8)) for i in self._idx_func(k))
    def get(self, k) -> Optional[str]:        if k not in self:            return None  # Bloom が No なら data を見ない        return self.data.get(k)

5. Compaction Strategy 比較

戦略代表 DBRead AmpWrite AmpSpace Amp
Size-TieredCassandra (default)高 (~2x)
LeveledRocksDB / LevelDB高 (~10x)
Time-WindowCassandra (時系列)
UniversalRocksDB

6. メリットとデメリット

  • メリット: 書込み性能が圧倒的 (B-Tree の 10-100x)
  • メリット: 順次 I/O 主体で SSD / HDD ともに最適化
  • メリット: Compaction で空間使用が最適化
  • デメリット: 読込みが複雑 (Bloom Filter で軽減)
  • デメリット: Write Amplification が大きい (~10-30x)
  • デメリット: 範囲スキャンは複数 SSTable をマージ読込

7. 関連ツール

  • RocksDB: Facebook 製の LSM、最も広く使われる組込み DB
  • LevelDB: Google 製、RocksDB の元
  • python-rocksdb: RocksDB の Python バインディング
  • lsm.py: 学習用の Python LSM 実装

8. 次の話

EP.14 では MCTS (Monte Carlo Tree Search) を扱います。AlphaGo / AlphaZero / 将棋 AI が世界チャンピオンを倒した探索アルゴリズム ── ランダムプレイアウトと UCB の組合せの美しさ。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp