こんにちは!Redisの世界へようこそ。
今日は、Redisが裏側でやっている「めちゃくちゃ頭のいい節約術」についてお話しするね。
データベースやキャッシュを扱うエンジニアにとって、メモリ(RAM)の容量は常に戦いの場なんだ。「いかに速く、いかにコンパクトにデータを詰め込むか」が腕の見せ所なんだけど、その中でもRedisの「Intset(イントセット)」という仕組みは、思わずうっとりしちゃうくらい美しいアイデアで作られているんだよ。
「難しそう…」なんて思わなくて大丈夫。
ここをクリアすれば、Redisのメモリ管理の基本はバッチリマスターできますよ!一緒に紐解いていこう。
—
1. 日常の例え:サイズが伸び縮みする「マジックお弁当箱」
想像してみてほしい。
あなたは毎日お弁当を持っていくんだけど、ある日は「小さなおにぎり1個」、またある日は「大きなどんぶり」を食べたいとするよね。
もし、最初からどんな大食いにも耐えられる「特大の弁当箱」を常に持ち歩いていたらどうなる?
小さなおにぎり1個のときでも、カバンの中のスペースを無駄に占領しちゃうよね。逆に、最初から「小さなお弁当箱」にしておけばカバンはスッキリするけど、大盛りにした途端にフタが閉まらなくなる。
RedisのIntsetは、この悩みを完璧に解決する「中身に合わせてサイズが自動で変形するマジックお弁当箱」なんだ。
—
2. Intsetってなに?(基本のキ)
Redisには「Set(セット)」という、重複のないデータを集めておく便利なデータ構造があるよね。
例えば、ユーザーのIDや、タグのリストを保存するときによく使われる。
このSetの中身が「すべて整数(数字)」だけで構成されているとき、Redisは裏側で「Intset」という特別なコンパクトな構造に変身させるんだ。文字(文字列)が1つでも混ざると普通の構造に戻っちゃうんだけど、数字だけならIntsetが発動する。
このIntset、何がすごいって、メモリの無駄を極限まで削ぎ落としていることなんだよ。
—
3. 3つのサイズモードと「自動拡張」の魔法
Intsetの中身をのぞいてみよう。Redisは、保存する数字の「最大値・最小値の大きさ」に合わせて、次の3つのサイズ(ビット幅)を賢く使い分けているよ。
1. int16_t(2バイト): `-32,768` から `32,767` までの可愛い数字用
2. int32_t(4バイト): `-2,147,483,648` から `2,147,483,647` までの一般的な数字用
3. int64_t(8バイト): 宇宙的なデカい数字まで扱える最強モード
ここでRedisの天才的なところは、「最初は一番小さい `int16_t` からスタートする」という点。
最初は小さな数字ばかり入れていたから、全員「2バイト分の部屋」で仲良く暮らしていたとする。そこに、突然「99,999,999」みたいなデカい数字がポーンと放り込まれたらどうなると思う?
「おっと、今の2バイトの部屋じゃこのデカい数字が入らないぞ!」
Redisは慌ててデータを捨てたりエラーを出したりしない。
なんと、Intset全体をまるごと「4バイト(または8バイト)」の部屋にリフォームし、既存のすべての数字を新しいサイズに拡張してコピーし直すんだ。
百聞は一見に如かず。実際にRedisを動かしているところをイメージしてみよう。
小さな数字だけを追加していく(この時点では省スペースな int16_t モード)
127.0.0.1:6379> SADD small_numbers 10 20 30
(integer) 3
メモリの使用量をチェック(超コンパクト!)
127.0.0.1:6379> MEMORY USAGE small_numbers
(integer) 48 # ヘッダー情報含めてもこれだけしか使わない
ここに、int16の限界を超えるようなデカい数字を投入!
127.0.0.1:6379> SADD small_numbers 999999
(integer) 1
この瞬間、Intset全体が自動でサイズアップ(アップグレード)される!
この「自動拡張」の瞬間、裏側ではメモリの再割り当てとデータの再配置が起きるから、ほんの少しだけコストがかかる。だけど、普段は最小限のメモリしか消費しないというメリットは、それを何倍も上回る恩恵をもたらしてくれるんだ。
—
4. 知っておくべき「プロの現実(トレードオフ)」
さて、ここまで聞くと「Intsetって最高じゃん!全部の数字をこれで管理しよう!」と思うよね。
でも、世界最高峰のエンジニアなら、ここで「裏の顔」もちゃんと伝えておかなければならない。
- ダウングレードはしない
一度「デカい数字」が入ってきてIntsetが `int64_t` に拡張されると、その後でそのデカい数字を削除(`SREM`)したとしても、部屋のサイズは小さく戻らない(ダウングレードはしない仕様)。
なぜなら、ダウングレードのたびにメモリを再計算して縮小するのは、CPUに負荷がかかりすぎて「速さ」というRedisのアイデンティティが失われちゃうからだね。
- O(N) の探索コスト
Intsetの中身は、実はシンプルな「ソートされた配列」としてメモリ上に連続して並んでいる。データ数が数万件以上になってくると、要素を探すときにプチッとコストがかかるようになる。
(※もちろん、データ数が少ないうちは配列の連続性によるキャッシュ効率の良さで爆速だよ!)
—
まとめ:本質をわかっているエンジニアになろう
Intsetの仕組みをまとめるとこうだ。
- 整数だけのSetは、自動的に「Intset」という超圧縮モードで保存される。
- 数字の大きさに合わせて、部屋のサイズ(16/32/64ビット)が自動でジャンプアップする。
- 一度大きくなった部屋は小さく戻らないので、データの入れ方には少しだけ気を配ると美しい。
「なぜRedisはこんなにメモリ効率が良いのか?」
その答えの一つが、このIntsetのような細やかなデータ構造の工夫にあるんだ。
仕組みを知っていれば、「あ、今このデータ構造が選ばれているからメモリに優しいな」「この入れ方をすると拡張コストがかかるな」ということが頭に描けるようになる。これが、ただの使用者から「真のRedis使い」への第一歩だよ。
今日の学びを武器に、ぜひ明日の開発や設計に活かしてみてね。
それじゃあ、また次の深淵でお会いしよう!
コメント