結論

Redisとは、データをメモリに置いてキーと値で読み書きする高速なデータベースです。主にキャッシュやセッションに使い、正式なデータは RDB に置きます。

この記事でわかること

  • 用途16通りとデータ型・コマンドの早見表
  • PostgreSQLとの読み書き速度の実測(最大54倍)
  • エラー原文14種と直し方・消えたデータの戻し方

2026年10月24日更新|Redis 8.10.2・7.0.15、PostgreSQL 16で実行確認

Redis はアプリと RDB(PostgreSQL・MySQL などのリレーショナルデータベース)の間に置き、よく読む値をメモリから返します。無いときだけ RDB を読み、結果を有効期限(TTL)付きで Redis に入れます。

flowchart TD
    A[アプリ] -- 1 まず読む --> R[(Redis<br>メモリ上)]
    A -- 2 無ければ読む --> D[(RDB<br>PostgreSQL など)]
    A -- 3 TTL付きで入れる --> R
    R -. 保存 .-> F[RDB ファイル<br>AOF]

用途ごとに使うデータ型とコマンドが決まっています。多く使われる順に並べました。

用途 データ型 コマンドの例
DB の結果をキャッシュ String SET k v EX 300 / GET k
有効期限を付ける・見る 全部 EXPIRE k 60 / TTL k
ログインセッションを共有 Hash+TTL HSET session:<id> ...
アクセス数・いいね数 String INCR page:views
ランキング Sorted Set ZINCRBY / ZREVRANGE
ジョブキュー・最新 N 件 List RPUSH / BLPOP / LTRIM
1分に N 回までの制限 String INCR + EXPIRE ... NX
処理を1人だけに(ロック) String SET lock:x a NX PX 10000
項目のある値(会員情報) Hash HSET user:1 name Sato
重複なしの集合・タグ Set SADD / SINTER
処理済みを管理するイベント Stream XADD / XREADGROUP / XACK
今つながっている相手へ通知 Pub/Sub PUBLISH / SUBSCRIBE
まとめて実行・競合の検出 全部 MULTI / EXEC / WATCH
ユニーク数の概算 HyperLogLog PFADD / PFCOUNT
近くの店の検索 Geo GEOADD / GEOSEARCH
キーを探す 全部 SCAN 0 MATCH user:*

確認環境:redis-cli のデータ型・TTL・用途別の例・メモリ上限・永続化・やってはいけない操作の出力は、Redis 8.10.2(GitHub のタグ 8.10.2 のソースを Ubuntu 24.04・2 vCPU で make -j2 によりビルドしたコア構成。2026年10月16日に実行)、redis-py 8.1.0、node-redis 6.3.0・ioredis 6.0.0(Node.js 24.21.0)で得たものです。RDB との速さの比較・キャッシュの実測・トランザクション・レプリケーション・Cluster のキースロット・Valkey は、2026年10月24日に Ubuntu 24.04 の apt パッケージの Redis 7.0.15 と Valkey 7.2.13、PostgreSQL 16.15(データはディスク上、synchronous_commit on)、SQLite 3.45.1、Python 3.11.15・redis-py 8.1.0・psycopg 3.3.6 で、すべて 127.0.0.1 の同じマシン(2 vCPU)で実行しました。Java(Jedis 8.0.1・Lettuce 7.8.0)はコードの書き方を各公式ドキュメントで、JSON・時系列など Redis 8 の追加データ型、トランザクション・レプリケーション・Cluster の仕様、版とライセンスの経緯は Redis 公式ドキュメント・リリースノート・ライセンスページ・公式ブログ・GitHub のリリース一覧・Linux Foundation の発表で確かめました(確認日 2026年10月24日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Redisとは|何に使う・なぜ速いかをRDBとの実測で解説【用途早見表】」

Redisとは(意味・読み方・何ができるか)

この節では、Redis が何者で、どんな種類のデータベースなのかが分かります。

Redis(レディス)は、すべてのデータをメモリ上に置き、「キー」と「値」の組で読み書きするデータベースです。 名前は REmote DIctionary Server(ネットワーク越しに使う辞書のサーバー)の略です。表と SQL を使う RDB とは違い、「user:1 というキーの値をください」のように、キーを指定して取り出すのが基本です。この形を「キー・バリュー型(KVS)」と呼び、RDB 以外のデータベースをまとめた「NoSQL」の1つに数えられます。

Redis の公式ドキュメントは、Redis を「データベース、キャッシュ、ストリーミングエンジン、メッセージブローカーなどとして使える」インメモリのデータ構造ストアと説明しています。値には文字列だけでなく、リスト・集合・順位付きの集合・ハッシュ・ストリームなどの型があり、それぞれに専用のコマンドがあります。

1メモリに置くので速い

1件の読み書きは 0.03〜0.07 ミリ秒(下の実測)。ディスクを読まずに返す。

2型ごとに便利な操作がある

数を足す、順位を出す、キューから取り出す、期限で消す、が1コマンドでできる。

3複数のサーバーで共有できる

ネットワーク越しに使うので、何台のアプリからも同じ値を読める。

たとえるなら、RDB は「倉庫」、Redis は「レジ横の棚」です。全商品の正式な在庫は倉庫(RDB)にあり、よく売れる品だけを手元の棚(Redis)に並べておくと、毎回倉庫まで取りに行かずに済みます。棚は小さく(メモリの大きさまで)、片付けると消える(設定しだいで再起動で消える)ので、正式な記録は倉庫に残します。

RDB・NoSQL を含むデータベースの種類の全体像は、関連記事「データベースとは|種類・Excelとの違い・SQLの基本を実例で解説」で扱っています。

Redisは何に使う?用途一覧と向かないもの

この節では、Redis が選ばれる用途と、Redis に任せないほうがよいデータが分かります。

用途 なぜ Redis が向くか 本文の節
キャッシュ DB の結果を TTL 付きで置くと、2回目以降は DB を読まない Redisでキャッシュを作る
セッションストア 複数のアプリサーバーで同じログイン状態を共有できる 用途別の実例
ランキング Sorted Set が常にスコア順を保つので、上位の取得が速い 用途別の実例
カウンター・レート制限 INCR が「読む・足す・書く」を1操作で行う(同時に来ても数がずれない) 用途別の実例
ジョブキュー・イベント処理 List の BLPOP、Stream のコンシューマーグループ 用途別の実例
通知(Pub/Sub) つながっているクライアントに即時配信する 用途別の実例
分散ロック SET NX PX で「無いときだけ作る」+自動解除 用途別の実例
ユニーク数・集合演算・位置情報 HyperLogLog・Set・Geo を標準で持つ データ型ごとの使い方

Redis に置くもの・置かないもの

置かない失ってはいけない業務データの唯一の保存先、SQL のような条件検索や結合が要るデータ、メモリに載らない大きさのデータ
↔
置く消えても RDB から作り直せる写し、短い期限で消える一時データ(セッション・ワンタイムコード)、高頻度で増減する数

再起動で消える条件は「永続化」の節で、5通りの止め方を実際に試して示す

Redisはなぜ速い?(仕組みと実測)

この節では、Redis が速い3つの理由と、実際にどれくらい速いかが分かります。

Redis が速いのは、データがメモリにあること、1本のスレッドがコマンドを順に処理すること、書き込みのたびにディスクへ書き終わるのを待たないことの3つが理由です。 公式ドキュメント(latency のページ)では「Redis は ほぼ シングルスレッドの設計で、1つのプロセスが多重化(multiplexing)ですべてのクライアントの要求をさばく」と説明されています。

理由 中身 RDB との違い
メモリに置く 読み書きはメモリの上だけで終わる RDB は表をディスクに置き、よく使う部分をメモリに読み込む
1本のスレッド コマンドを1つずつ処理するので、ロックの待ち合わせが無い RDB は同じ行を同時に更新すると、行ロックで待ち合わせる
ディスクを待たない 保存は後で子プロセス(RDB ファイル)か、1秒ごと(AOF)にまとめて行う RDB は既定で COMMIT のたびにログをディスクに書き終えてから返す
単純なデータ構造 キーで直接たどる。SQL の解析や実行計画が無い SQL を解析し、インデックスをたどる

動いている Redis 7.0.15 のスレッドを見ると、5本のうちコマンドを処理するのは redis-server の1本だけで、残りはファイルを閉じる・AOF を書く・メモリを解放する裏方でした。

$ ps -T -p 31970 -o spid,comm
 SPID COMMAND
31970 redis-server
31983 bio_close_file
31984 bio_aof_fsync
31985 bio_lazy_free
31986 jemalloc_bg_thd
$ redis-cli -p 26379 CONFIG GET io-threads
1) "io-threads"
2) "1"

redis-benchmark の実測

同梱の redis-benchmark で 10 万件ずつ実行した結果です(Redis 8.10.2、2 vCPU の Linux、同じマシンから 50 接続)。

$ redis-benchmark -t set,get,incr,lpush,zadd -n 100000 -q
SET: 82304.52 requests per second, p50=0.367 msec
GET: 82372.32 requests per second, p50=0.351 msec
INCR: 90497.73 requests per second, p50=0.335 msec
LPUSH: 55803.57 requests per second, p50=0.407 msec
ZADD: 52521.01 requests per second, p50=0.407 msec

$ redis-benchmark -t set,get -n 100000 -P 16 -q      # 16 コマンドずつまとめて送る(パイプライン)
SET: 309597.50 requests per second, p50=1.175 msec
GET: 495049.50 requests per second, p50=0.623 msec

1コマンドの往復は 0.35 ミリ秒前後(50 接続が同時に送った場合)で、パイプライン(複数コマンドをまとめて送る)を使うと秒間 30〜50 万件になりました。速さの多くはネットワークの往復で決まるため、まとめて送れる処理はパイプラインにします。

RedisとRDB(PostgreSQL・MySQL)の違い

この節では、Redis と RDB の違いと、同じマシンで速さを比べた実測値が分かります。なお、ここでの「RDB」はリレーショナルデータベースのことで、Redis の保存方式の「RDB ファイル」とは別のものです。

Redis は RDB の代わりではなく、組み合わせて使います。 正式なデータは RDB に置き、よく読むデータ・一時的なデータ・高頻度で書き換える数を Redis に置きます。

項目 Redis RDB(PostgreSQL・MySQL など)
データの置き場所 メモリ ディスク(よく使う部分をメモリに置く)
データの形 キーと値(値に複数の型がある) 表(行と列)
検索 キーで取り出すのが基本 SQL で自由に検索・結合・集計
書き込みの確定 ディスクへの保存は後でまとめて COMMIT のたびにログをディスクへ
トランザクション MULTI/EXEC。途中で失敗しても取り消さない ROLLBACK で全部取り消せる
容量 メモリの大きさまで ディスクの大きさまで
主な用途 キャッシュ、セッション、ランキング、カウンター、キュー 業務データの正式な保存場所

実測1:1件ずつの読み書き(Redis・PostgreSQL・SQLite)

Python から1接続で 1 万回ずつ読み書きし、3回測りました。読みは1万件の表から主キーで1行、書きは同じ1つの数を1ずつ足します(PostgreSQL は UPDATE を1回ごとに COMMIT、SQLite もファイルに1回ごとに COMMIT)。

run("読み Redis GET", lambda i: r.get(f"product:{i}"))
run("読み PostgreSQL SELECT(主キー)", lambda i: cur.execute("SELECT body FROM product WHERE id=%s", (i,), prepare=True).fetchone())
run("書き Redis INCR", lambda i: r.incr("counter"))
run("書き PostgreSQL UPDATE+COMMIT", lambda i: cur.execute("UPDATE counter SET n=n+1 WHERE id=1", prepare=True))
1件ずつ 10000 回(同じマシン・1接続) ※3回のうち2回目
読み Redis GET                           17360 件/秒  p50=0.048ms  p99=0.176ms
読み PostgreSQL SELECT(主キー)              20834 件/秒  p50=0.026ms  p99=0.347ms
読み SQLite SELECT(主キー)                 213410 件/秒  p50=0.004ms  p99=0.009ms
書き Redis INCR                          14988 件/秒  p50=0.062ms  p99=0.168ms
書き PostgreSQL UPDATE+COMMIT             2613 件/秒  p50=0.232ms  p99=0.808ms
書き SQLite UPDATE+COMMIT                 1282 件/秒  p50=0.729ms  p99=1.867ms
確認: 10000 10000 10000
操作(1接続・1万回) Redis PostgreSQL SQLite(ファイル)
主キーで1件読む(件/秒、3回) 12,006〜17,360 16,536〜20,834 139,319〜213,410
1件書いて確定(件/秒、3回) 12,432〜14,988 164〜4,336 293〜1,348
書きの p99(3回) 0.146〜0.285 ms 0.511〜173.287 ms 1.867〜67.921 ms

読み取りは、小さな表なら PostgreSQL も同じくらい速く、Python からでは PostgreSQL のほうが速い回もありました。 1万件の表は PostgreSQL のメモリ(shared_buffers 128MB)に載るため、どちらもメモリから返し、時間の大半はクライアントの処理と往復です。SQLite はアプリの中で動くライブラリで、ネットワークの往復が無いので桁違いに速くなります(その代わり、複数のサーバーから共有できません)。

差が出たのは書き込みです。 PostgreSQL と SQLite は COMMIT のたびにディスクへの書き込みを待つため、Redis の 3〜90 分の1の件数になり、1回目は p99 が 173 ms まで伸びました(ディスクの書き込みを待った回)。

実測2:専用の計測ツールで、接続数を増やす

Python の処理を除くため、C で書かれた専用の計測ツール(Redis は redis-benchmark、PostgreSQL は pgbench)で、1 接続と 50 接続を比べました。PostgreSQL の読みは pgbench -S(10 万行の表から主キーで1行)、書きは同じ1行への UPDATE(自動 COMMIT)を 10 秒ずつ流しています。

$ redis-benchmark -p 26379 -t get,incr -n 100000 -c 1 -q
GET: 37835.79 requests per second, p50=0.031 msec
INCR: 32175.03 requests per second, p50=0.031 msec
$ redis-benchmark -p 26379 -t get,incr -n 100000 -c 50 -q
GET: 96339.12 requests per second, p50=0.247 msec
INCR: 101729.40 requests per second, p50=0.239 msec

$ pgbench -p 26432 -S -c 1 -j 2 -T 10 -M prepared r26db
latency average = 0.032 ms
tps = 30988.941332 (without initial connection time)
$ pgbench -p 26432 -n -f incr.sql -c 50 -j 2 -T 10 -M prepared r26db
latency average = 26.729 ms
tps = 1870.625560 (without initial connection time)
操作 Redis(回/秒) PostgreSQL(回/秒) 差
主キーで読む・1接続 37,836 30,989 1.2 倍
主キーで読む・50接続 96,339 47,843 2.0 倍
同じ値を更新・1接続 32,175 3,801 8.5 倍
同じ値を更新・50接続 101,729 1,871 54 倍
1.2倍主キーの読み(1接続)小さな表なら差は小さい
8.5倍同じ値の更新(1接続)COMMIT ごとのディスク待ちの差
54倍同じ値の更新(50接続)行ロックの待ち合わせで RDB が遅くなる

同じ値に50接続が同時に書くと、PostgreSQL は1接続のときより遅くなり(3,801 → 1,871 回/秒、平均 26.7 ms)、Redis は速くなりました(32,175 → 101,729 回/秒)。 PostgreSQL は同じ行の更新を行ロックで1つずつ待たせ、Redis は1本のスレッドが順に INCR するだけだからです。いいね数・在庫の残数・レート制限のように「同じ数を大勢が同時に書き換える」処理は Redis に向いています。

実測3:重い集計の結果をキャッシュする

30 万行の注文の表(15MB)から「10 月の売上数量の上位 10 商品」を集計する SQL を、Redis にキャッシュして 5 回呼びました(コードは「Redisでキャッシュを作る」の節と同じ Cache-Aside)。

SELECT product_id, sum(qty) AS total FROM orders
WHERE ordered_at >= '2026-10-01' GROUP BY product_id ORDER BY total DESC LIMIT 10
1回目:   18.55 ms  (DB)  1位=[133, 2403]
2回目:    0.24 ms  (Redis)  1位=[133, 2403]
3回目:    0.14 ms  (Redis)  1位=[133, 2403]
4回目:    0.15 ms  (Redis)  1位=[133, 2403]
5回目:    0.21 ms  (Redis)  1位=[133, 2403]
TTL: 325
値の大きさ: 200 bytes

集計に 18.55 ms かかる結果が、2回目からは 0.14〜0.24 ms で返りました。 主キーで1行読むだけの処理では差が小さく、集計・結合・外部 API の呼び出しのように「作るのに時間がかかる結果」をキャッシュすると差が大きくなります。何をキャッシュするかの判断は、下の「Memcached・アプリ内キャッシュとの使い分け」の表にまとめています。

Memcached・アプリ内キャッシュとの使い分け

項目 Redis Memcached RDB アプリ内キャッシュ
データ型 String・Hash・List・Set・Sorted Set・Stream など 文字列(任意のバイト列)のみ 表 言語のオブジェクト
複数サーバーでの共有 できる できる できる できない(プロセスごと)
永続化 RDB ファイル・AOF なし(再起動で消える) あり なし
上限時の動き maxmemory-policy で選ぶ LRU で消す ディスクの上限 実装次第
向く用途 キャッシュ+セッション・ランキング・キュー 文字列だけの単純なキャッシュ 正式なデータ 1台だけで完結する小さなデータ

Memcached の公式ドキュメントでは、キーは 250 バイトまで、値は任意のデータ、既定では LRU キャッシュとして動き、スレッドは既定で 4 本と説明されています。項目の大きさの上限は既定 1MB(item_size_max 1048576)です。

状況 条件 選ぶもの
集計・結合が重い アプリサーバーが複数台 Redis(Cache-Aside)
DB の読み取りが遅い 1台だけ・小さいデータ・更新の遅れは許容 アプリ内キャッシュ(Caffeine、functools.lru_cache など)
主キーで1行読むだけ 表がメモリに載る大きさ まず RDB のまま(実測1で差が小さい)
同じ数を大勢が書き換える いいね数・在庫・レート制限 Redis(実測2で 8.5〜54 倍)
ランキング・キュー・ロックも要る ― Redis
文字列のキャッシュだけでよく、既に Memcached がある ― Memcached のまま
消えてはいけないデータ ― RDB(Redis は写し)

Redisの使い方:インストールと redis-cli での接続

この節では、Redis の入れ方・起動・接続の確かめ方と、redis-cli の表示の読み方が分かります。

環境 入れ方 入る版(2026年10月24日時点)
Ubuntu 24.04 の標準リポジトリ sudo apt install redis-server 7.0.15(apt-cache policy の候補版)。valkey-server は 7.2.13
Redis 公式リポジトリ(Ubuntu/Debian) 下のコマンド(公式ドキュメント) 8 系(packages.redis.io)
macOS brew install redis → brew services start redis Homebrew の最新
Docker docker run -d --name redis -p 6379:6379 redis:8 8 系の最新
Windows WSL2 の Ubuntu に上の手順で入れる(公式ドキュメントの案内) ―
ソースからビルド GitHub のタグを取得して make -j2(2 vCPU で 3 分 36 秒) 任意の版
1

入れる

Ubuntu なら apt、Mac なら Homebrew。Redis 8 の機能(JSON・Hash の項目 TTL など)を使うなら公式リポジトリか Docker の redis:8。

入れる

2

起動する

サービスとして起動するか、redis-server 設定ファイル で起動する。ログに Ready to accept connections が出れば待ち受け中。

起動

3

つながるか確かめる

redis-cli ping が PONG を返せば使える。版は redis-cli info server の redis_version。

確認

4

書いて読む

SET key value → GET key。アプリからは redis-py・node-redis・Jedis などのクライアントで同じコマンドを送る。

使う

公式リポジトリの手順(Redis 公式ドキュメントのとおり)です。

sudo apt-get install lsb-release curl gpg
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
sudo chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
sudo apt-get update
sudo apt-get install redis

起動と接続の確認

$ redis-server ./redis.conf        # 設定ファイルを指定して起動(フォアグラウンド)
626:M 16 Oct 2026 19:54:39.007 * Running mode=standalone, port=6379.
626:M 16 Oct 2026 19:54:39.008 * Server initialized
626:M 16 Oct 2026 19:54:39.008 * Ready to accept connections tcp
626:M 16 Oct 2026 19:54:39.008 # WARNING: Redis does not require authentication. Redis will accept connections from any local client.

$ redis-cli ping
PONG

$ redis-cli info server | grep -E "redis_version|tcp_port"
redis_version:8.10.2
tcp_port:6379

Ready to accept connections tcp が出れば待ち受け中です。8.10.2 の起動ログには、パスワード未設定だと WARNING: Redis does not require authentication が出ます(既定では 127.0.0.1 からしか受け付けません。後述)。

redis-cli の返り値の読み方

対話モード(引数なしの redis-cli)では、結果が次の形で表示されます。この記事の出力はこの形です。

表示 意味 例
OK 成功(SET など) SET k v → OK
"..." 文字列の値 GET k → "v"
(integer) N 整数(件数・残り秒数・真偽 1/0) INCR c → (integer) 1
(nil) 値がない(キーが無い) GET nokey → (nil)
1) ... 2) ... 複数の値(配列) MGET a b
(empty array) 空の配列 LRANGE empty 0 -1
(error) ... エラー。先頭の大文字がエラーの種類 (error) WRONGTYPE ...

1行だけ実行するときは redis-cli GET member:1 のように引数に書きます。出力先が端末でないと「生の値」だけが出るので、対話モードと同じ形にしたいときは --no-raw を付けます。

$ redis-cli GET member:1
Sato
$ redis-cli --no-raw GET member:1
"Sato"
$ redis-cli --scan --pattern "member:*"     # KEYS の代わりに SCAN でキーを列挙
member:1

データ型ごとの使い方(redis-cli の実行結果)

この節では、主なデータ型のコマンドと、それぞれが何に向くかが実行結果で分かります。

キーの名前は user:1、session:<id>、ranking:2026-10 のように 種類:ID:項目 をコロンで区切るのが慣例です。TYPE key で型を確かめられます。

String:もっとも基本の型(値・カウンター)

127.0.0.1:6379> SET greeting "hello"
OK
127.0.0.1:6379> GET greeting
"hello"
127.0.0.1:6379> GET nokey
(nil)
127.0.0.1:6379> SET page:views 0
OK
127.0.0.1:6379> INCR page:views
(integer) 1
127.0.0.1:6379> INCRBY page:views 10
(integer) 11
127.0.0.1:6379> DECR page:views
(integer) 10
127.0.0.1:6379> APPEND greeting " world"
(integer) 11
127.0.0.1:6379> MSET a 1 b 2
OK
127.0.0.1:6379> MGET a b c
1) "1"
2) "2"
3) (nil)
127.0.0.1:6379> SETNX greeting "x"
(integer) 0
127.0.0.1:6379> GETDEL a
"1"

INCR は「読んで、足して、書く」を1つの操作で行います。 複数のアプリから同時に呼ばれても数がずれないため、アクセス数・いいね数・採番に向いています。SETNX(無いときだけ作る)は (integer) 0 が「既にあったので何もしなかった」の意味です。GETDEL(6.2 以降)は読み取りと削除を1回で行います。

Hash:項目のある値(ユーザー情報など)

127.0.0.1:6379> HSET user:1 name "Sato" email "sato@example.com" plan "free"
(integer) 3
127.0.0.1:6379> HGET user:1 name
"Sato"
127.0.0.1:6379> HINCRBY user:1 login_count 1
(integer) 1
127.0.0.1:6379> HGETALL user:1
1) "name"
2) "Sato"
3) "email"
4) "sato@example.com"
5) "plan"
6) "free"
7) "login_count"
8) "1"
127.0.0.1:6379> HMGET user:1 name plan
1) "Sato"
2) "free"
127.0.0.1:6379> HDEL user:1 email
(integer) 1
127.0.0.1:6379> HLEN user:1
(integer) 3

項目ごとに読み書きできるのが、JSON を丸ごと String に入れる方法との違いです。1項目だけ更新する・1項目だけ数えるときに向いています。Redis 7.4 からは項目単位の有効期限(HEXPIRE)、8.0 からは期限付きで項目を書く HSETEX が使えます。

127.0.0.1:6379> HEXPIRE user:1 60 FIELDS 1 login_count
1) (integer) 1
127.0.0.1:6379> HTTL user:1 FIELDS 2 login_count name
1) (integer) 60
2) (integer) -1
127.0.0.1:6379> HSETEX user:1 EX 30 FIELDS 1 otp "1234"
(integer) 1

List:キューと「最新 N 件」

127.0.0.1:6379> RPUSH queue:mail "job1" "job2" "job3"
(integer) 3
127.0.0.1:6379> LPOP queue:mail
"job1"
127.0.0.1:6379> LRANGE queue:mail 0 -1
1) "job2"
2) "job3"
127.0.0.1:6379> BLPOP queue:mail 1
1) "queue:mail"
2) "job2"
127.0.0.1:6379> LPUSH recent:user:1 "item:10" "item:11" "item:12"
(integer) 3
127.0.0.1:6379> LTRIM recent:user:1 0 1
OK
127.0.0.1:6379> LRANGE recent:user:1 0 -1
1) "item:12"
2) "item:11"

RPUSH で後ろに入れて LPOP で前から取り出すと、先に入れたものから順に処理するキューになります。BLPOP key 秒 は要素が来るまで待つ取り出しで、ワーカーが空回りしません(何も来ないと (nil) と待った秒数が表示されます)。LPUSH と LTRIM の組み合わせで「最近見た商品を最新 2 件だけ残す」ように件数を一定に保てます。

Set:重複なしの集合

127.0.0.1:6379> SADD tags:article:1 "redis" "cache" "db"
(integer) 3
127.0.0.1:6379> SADD tags:article:2 "redis" "python"
(integer) 2
127.0.0.1:6379> SADD tags:article:1 "redis"
(integer) 0
127.0.0.1:6379> SISMEMBER tags:article:1 "cache"
(integer) 1
127.0.0.1:6379> SINTER tags:article:1 tags:article:2
1) "redis"
127.0.0.1:6379> SUNION tags:article:1 tags:article:2
1) "redis"
2) "cache"
3) "python"
4) "db"
127.0.0.1:6379> SDIFF tags:article:1 tags:article:2
1) "cache"
2) "db"
127.0.0.1:6379> SCARD tags:article:1
(integer) 3

同じ値を何度 SADD しても1つだけ保存され、2回目は (integer) 0(追加なし)です。「含まれているか」(SISMEMBER)と、共通部分・和・差(SINTER / SUNION / SDIFF)が得意です。順序は保証されません。

Sorted Set:ランキング

127.0.0.1:6379> ZADD ranking 120 "alice" 95 "bob" 150 "carol"
(integer) 3
127.0.0.1:6379> ZINCRBY ranking 40 "bob"
"135"
127.0.0.1:6379> ZREVRANGE ranking 0 2 WITHSCORES
1) "carol"
2) "150"
3) "bob"
4) "135"
5) "alice"
6) "120"
127.0.0.1:6379> ZREVRANK ranking "bob"
(integer) 1
127.0.0.1:6379> ZSCORE ranking "alice"
"120"
127.0.0.1:6379> ZRANGEBYSCORE ranking 100 140
1) "alice"
2) "bob"
127.0.0.1:6379> ZADD ranking 150 "dave"
(integer) 1
127.0.0.1:6379> ZREVRANGE ranking 0 -1 WITHSCORES
1) "dave"
2) "150"
3) "carol"
4) "150"
5) "bob"
6) "135"
7) "alice"
8) "120"

Sorted Set は、要素ごとにスコアを持ち、常にスコア順に並んだ状態で保存されます。 ZINCRBY で加算すると順位も入れ替わります。ZREVRANK は 0 から数えるので 1 は 2 位です。同点(carol と dave の 150)は要素名の辞書順で並び、ZREVRANGE ではその逆順(dave が先)になります。同点の扱いを決めたいときは、スコアに時刻などを混ぜます。6.2 以降は ZRANGE ranking 0 2 REV WITHSCORES という書き方もあり、結果は ZREVRANGE と同じでした。

Stream:イベントを記録し、処理済みを管理する

127.0.0.1:6379> XADD orders * item "book" qty 1
"1792144646424-0"
127.0.0.1:6379> XADD orders * item "pen" qty 3
"1792144646425-0"
127.0.0.1:6379> XGROUP CREATE orders workers 0
OK
127.0.0.1:6379> XREADGROUP GROUP workers w1 COUNT 1 STREAMS orders >
1) 1) "orders"
   2) 1) 1) "1792144646424-0"
         2) 1) "item"
            2) "book"
            3) "qty"
            4) "1"
127.0.0.1:6379> XREADGROUP GROUP workers w2 COUNT 1 STREAMS orders >
1) 1) "orders"
   2) 1) 1) "1792144646425-0"
         2) 1) "item"
            2) "pen"
            3) "qty"
            4) "3"
127.0.0.1:6379> XPENDING orders workers
1) (integer) 2
2) "1792144646424-0"
3) "1792144646425-0"
4) 1) 1) "w1"
      2) "1"
   2) 1) "w2"
      2) "1"
127.0.0.1:6379> XACK orders workers 1792144646424-0
(integer) 1
127.0.0.1:6379> XAUTOCLAIM orders workers w1 0 0 COUNT 10
1) "0-0"
2) 1) 1) "1792144646425-0"
      2) 1) "item"
         2) "pen"
         3) "qty"
         4) "3"
3) (empty array)

Stream は追記専用のログのようなデータ型です。 * で ID が自動採番され(「ミリ秒時刻-連番」)、コンシューマーグループを使うと 複数の担当(w1・w2)で分担して読み、処理が終わったら XACK で完了を記録できます。XPENDING は「読んだが完了していない」件数と担当で、途中で止まった担当の分は XAUTOCLAIM で別の担当(ここでは w1)に引き取らせました。XADD orders MAXLEN ~ 1000 * ... のように上限を付けると、増え続けません。

HyperLogLog・Bitmap・Geo

127.0.0.1:6379> PFADD visitors:2026-10-16 "u1" "u2" "u3" "u1"
(integer) 1
127.0.0.1:6379> PFCOUNT visitors:2026-10-16
(integer) 3
127.0.0.1:6379> PFMERGE visitors:week visitors:2026-10-16 visitors:2026-10-17
OK
127.0.0.1:6379> PFCOUNT visitors:week
(integer) 4
127.0.0.1:6379> SETBIT login:2026-10-16 42 1
(integer) 0
127.0.0.1:6379> GETBIT login:2026-10-16 42
(integer) 1
127.0.0.1:6379> BITCOUNT login:2026-10-16
(integer) 2
127.0.0.1:6379> GEOADD shops 139.7671 35.6812 "tokyo-st" 139.7005 35.6595 "shibuya" 135.4959 34.7025 "osaka"
(integer) 3
127.0.0.1:6379> GEODIST shops tokyo-st shibuya km
"6.4838"
127.0.0.1:6379> GEOSEARCH shops FROMLONLAT 139.7671 35.6812 BYRADIUS 10 km ASC WITHDIST
1) 1) "tokyo-st"
   2) "0.0002"
2) 1) "shibuya"
   2) "6.4839"
型 何に使うか 特徴
HyperLogLog ユニークユーザー数などの「種類の数」 誤差 0.81% 前後の概算(公式ドキュメント)。要素数が増えてもキーは最大 12KB
Bitmap 「ユーザー ID 42 は今日ログインしたか」のような 0/1 の記録 String の各ビットを使う。BITCOUNT で 1 の数
Geo 近くの店舗の検索・2点間の距離 内部は Sorted Set(TYPE shops は zset)

JSON・時系列・確率的データ構造(Redis 8 で標準)

Redis 8.0 の公式リリースノートでは、「Redis Search と 8 つの新しいデータ構造が Redis 8 の一部になった」とあり、JSON、時系列(Time series)、確率的データ構造(Bloom フィルター・Cuckoo フィルター・Count-min sketch・Top-k・t-digest)、ベクトルセット(プレビュー)が 「すべてのバイナリ配布物に含まれる」と書かれています。別モジュール(RedisJSON・RedisTimeSeries・RedisBloom)を入れる必要はなくなりました。

型 書き方(公式ドキュメントの構文) 用途
JSON JSON.SET user:2 $ '{"name":"Sato","age":30}' / JSON.GET user:2 $.name 入れ子のある文書をパスで部分更新する
時系列 TS.ADD temp:1 * 21.5 / TS.RANGE temp:1 - + AGGREGATION avg 60000 センサー値・メトリクスの集計
Bloom フィルター BF.ADD seen url1 / BF.EXISTS seen url1 「見たことがあるか」を省メモリで判定(偽陽性あり)

ソースをコアだけでビルドした場合(make のみ)には含まれず、JSON.SET は次のエラーになります。MODULE LIST で入っている型を確かめられます。

127.0.0.1:6379> JSON.SET user:2 $ "{\"name\":\"Sato\"}"
(error) ERR unknown command 'JSON.SET', with args beginning with: 'user:2' '$' '{"name":"Sato"}'
127.0.0.1:6379> MODULE LIST
1) 1) "name"
   2) "vectorset"
   3) "ver"
   4) (integer) 1

TTL(有効期限)の付け方と確認

この節では、キーに有効期限を付ける・確かめる・外す方法と、期限が消える落とし穴が分かります。

127.0.0.1:6379> SET otp:user:42 "839201" EX 60
OK
127.0.0.1:6379> TTL otp:user:42
(integer) 60
127.0.0.1:6379> TTL greeting
(integer) -1
127.0.0.1:6379> TTL no-such-key
(integer) -2
127.0.0.1:6379> EXPIRE greeting 30
(integer) 1
127.0.0.1:6379> TTL greeting
(integer) 30
127.0.0.1:6379> SET greeting "hello again"
OK
127.0.0.1:6379> TTL greeting
(integer) -1
127.0.0.1:6379> SET greeting "hello" KEEPTTL
OK
127.0.0.1:6379> EXPIRE greeting 30
(integer) 1
127.0.0.1:6379> EXPIRE greeting 10 GT
(integer) 0
127.0.0.1:6379> TTL greeting
(integer) 30
127.0.0.1:6379> EXPIRE greeting 60 GT
(integer) 1
127.0.0.1:6379> PTTL greeting
(integer) 60000
127.0.0.1:6379> PERSIST greeting
(integer) 1
127.0.0.1:6379> TTL greeting
(integer) -1
TTL の返り値 意味
正の数 残りの秒数(PTTL はミリ秒)
-1 キーはあるが、有効期限がない
-2 キーが存在しない(期限切れで消えた後も -2)
コマンド 動き
SET key value EX 秒 / PX ミリ秒 保存と同時に有効期限を付ける
SET key value KEEPTTL 値だけ上書きし、期限は残す(6.0 以降)
EXPIRE key 秒 / PEXPIRE key ミリ秒 あとから有効期限を付ける
EXPIRE key 秒 NX / XX / GT / LT 期限が「無いときだけ」「あるときだけ」「長くなるときだけ」「短くなるときだけ」付ける(7.0 以降)
EXPIREAT key UNIX秒 時刻で指定する
PERSIST key 有効期限を外す

SET で値を上書きすると、それまでの有効期限は消えます(上の TTL greeting が 30 → -1 になった行)。期限を残したいときは KEEPTTL を付けるか、上書きのたびに EX を付けます。EXPIRE greeting 10 GT が (integer) 0 なのは、残り 30 秒より短い 10 秒は「長くなるときだけ」の条件に合わず、何もしなかったためです。レート制限のように「最初の 1 回だけ期限を付けたい」場面では NX を使います。

stateDiagram-v2
    direction TB
    state "キーなし" as N
    state "期限なし TTL -1" as P
    state "期限あり" as T
    [*] --> N
    N --> P: SET
    N --> T: SET EX
    P --> T: EXPIRE
    T --> P: PERSIST・上書き
    T --> N: 期限切れ
    P --> N: DEL

期限切れのキーは、アクセス時に消えるほか、サーバーが定期的に少しずつ消します(公式ドキュメントの「expire」の説明)。期限切れの瞬間に全部が消えるわけではないため、メモリ使用量は少し遅れて減ります。DEL・UNLINK とメモリ上限による削除(eviction)は、期限の有無にかかわらずキーを消します。

Redisでキャッシュを作る(Cache-Aside)

この節では、Redis をキャッシュに使うときの基本の形と、TTL・更新時の消し方の決まりが、Python のコードと実行結果で分かります。

Cache-Aside は、まず Redis を見て、無ければ DB から読んで Redis に入れる、もっとも一般的なキャッシュの方式です。 Python からは redis-py を使います(pip install redis)。

%%{init: {'sequence': {'actorMargin': 30, 'width': 110}}}%%
sequenceDiagram
    participant A as アプリ
    participant R as Redis
    participant D as DB
    A->>R: GET product:1
    alt ヒット
        R-->>A: 値
    else ミス
        R-->>A: nil
        A->>D: SELECT
        D-->>A: 行
        A->>R: SET EX 300
    end
import json, random, time
import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)

def load_product_from_db(product_id: int) -> dict:
    time.sleep(0.2)  # 遅いDB問い合わせの代わり
    return {"id": product_id, "name": f"商品{product_id}", "price": 1200}

def get_product(product_id: int) -> dict:
    key = f"product:{product_id}"
    cached = r.get(key)
    if cached is not None:
        return json.loads(cached)                      # ヒット
    product = load_product_from_db(product_id)         # ミス → DBから読む
    ttl = 300 + random.randint(0, 60)                  # TTLにばらつきを持たせる
    r.set(key, json.dumps(product, ensure_ascii=False), ex=ttl)
    return product

def update_product(product_id: int, data: dict) -> None:
    # save_to_db(product_id, data) を実行したあと
    r.delete(f"product:{product_id}")                  # キャッシュを消す
1回目: 200.7 ms  {'id': 1, 'name': '商品1', 'price': 1200}
2回目: 0.2 ms  {'id': 1, 'name': '商品1', 'price': 1200}
3回目: 0.1 ms  {'id': 1, 'name': '商品1', 'price': 1200}
TTL: 355
更新後に残っているか: 0

1回目は DB を読むため約 200 ms、2回目以降は Redis から返るため 0.2 ms 以下になりました。本物の PostgreSQL の集計(18.55 ms → 0.14 ms)で測った結果は、「RedisとRDBの違い」の実測3にあります。

ポイント 理由
必ず TTL を付ける 更新漏れがあっても、いずれ正しい値に戻る。メモリも増え続けない
TTL にばらつきを持たせる 同時に作ったキャッシュが一斉に切れるのを防ぐ(「よくある失敗」の節で実測)
更新時は「DB を更新してからキャッシュを削除」 次の読み込みで新しい値がキャッシュされる。先に消すと、更新前の値が読み直されて戻ることがある
値は JSON 文字列か Hash 項目単位で更新するなら Hash、丸ごと差し替えるなら JSON 文字列

Cache-Aside 以外の方式(Read-Through・Write-Through・Write-Behind など)との違いと TTL の決め方は、関連記事「キャッシュ設計の戦略一覧:Cache-Asideなど5方式の違いとTTLの決め方」で扱っています。

用途別の実例:セッション・ランキング・キュー・Pub/Sub・ロック

この節では、キャッシュ以外の代表的な使い方を、Python(redis-py)と redis-cli の実行結果で見ていきます。

セッション

import uuid
sid = uuid.uuid4().hex
r.hset(f"session:{sid}", mapping={"user_id": "42", "role": "member"})
r.expire(f"session:{sid}", 1800)   # 30分
print(r.hgetall(f"session:{sid}"), r.ttl(f"session:{sid}"))
{'user_id': '42', 'role': 'member'} 1800

Redis にセッションを置くと、複数のアプリサーバーで同じログイン状態を共有できます。 アクセスのたびに EXPIRE で延長すれば「最後の操作から 30 分」で切れるセッションになります。Spring Boot で設定する方法は、関連記事「Spring Session Redisでセッション共有|設定と完成コード」で扱っています。

ランキング

r.zadd("ranking:2026-10", {"user:1": 120, "user:2": 95, "user:3": 150})
r.zincrby("ranking:2026-10", 40, "user:2")                  # スコア加算
top3 = r.zrevrange("ranking:2026-10", 0, 2, withscores=True)
rank = r.zrevrank("ranking:2026-10", "user:2")              # 0始まり
print(top3, rank)
[('user:3', 150.0), ('user:2', 135.0), ('user:1', 120.0)] 1

月ごとなど期間でキーを分け、終わった期間のキーには有効期限を付けると、データが増え続けません。

Pub/Sub(通知)

受け取る側が SUBSCRIBE で待ち、送る側が PUBLISH します。

# 送る側
127.0.0.1:6379> PUBLISH news "deploy finished"
(integer) 1
127.0.0.1:6379> PUBLISH nobody "no subscriber"
(integer) 0

# 受け取る側(redis-cli SUBSCRIBE news)
1) "subscribe"
2) "news"
3) (integer) 1
1) "message"
2) "news"
3) "deploy finished"

PUBLISH の返り値は「届いた相手の数」です。受け取る側がつながっていない間に送ったメッセージは届かず、保存もされません(nobody への送信は 0)。後から読み直す必要があるなら Stream を使います。

キュー:List・Stream・Pub/Sub の使い分け

方式 向いている場面 取り出し 失敗した処理のやり直し
List(RPUSH / BLPOP) シンプルなジョブキュー 1人が取り出すと他には見えない 取り出した瞬間に消えるので、自前で記録が要る
Stream(XADD / XREADGROUP / XACK) 複数の担当で分担し、処理済みを管理したい グループ内で分配 XPENDING で未完了を見つけ XAUTOCLAIM で引き取る
Pub/Sub(PUBLISH / SUBSCRIBE) 送った瞬間に受け取れる相手にだけ届けばよい通知 全員に配信 届かなかったものは消える

Python のタスクキュー Celery で Redis をブローカーにする方法は、関連記事「Celeryの使い方|Redis/Valkeyで非同期タスクを動かす」で扱っています。

レート制限(1分に5回まで)

import time

def allow(user_id: str, limit: int = 5, window: int = 60) -> bool:
    key = f"rate:{user_id}:{int(time.time() // window)}"
    pipe = r.pipeline()
    pipe.incr(key)
    pipe.expire(key, window, nx=True)   # 最初の1回だけ期限を付ける(7.0 以降の NX)
    count, _ = pipe.execute()
    return count <= limit

print([allow("u1") for _ in range(7)])
[True, True, True, True, True, False, False]

6回目から False(制限)になりました。 時間の区切り(1分)ごとにキーを変え、INCR で数える固定ウィンドウ方式です。INCR と EXPIRE はパイプライン(まとめ送り)で続けて送っています。区切りの前後にアクセスが集中すると短時間に上限の2倍近くを通すことがあるため、厳密にしたい場合は Sorted Set に時刻を記録するスライディングウィンドウ方式にします。

分散ロック(SET NX PX)

print(r.set("lock:rebuild:product:1", "worker-a", nx=True, px=10000))
print(r.set("lock:rebuild:product:1", "worker-b", nx=True, px=10000))

# 自分のロックだけを外す(値を確かめてから DEL。Lua で1操作にする)
release = r.register_script("""
if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('DEL', KEYS[1])
else
  return 0
end
""")
print(release(keys=["lock:rebuild:product:1"], args=["worker-b"]))
print(release(keys=["lock:rebuild:product:1"], args=["worker-a"]))
True
None
0
1

NX は「キーが無いときだけ保存する」、PX 10000 は「10 秒で自動的に外れる」指定です。 先に取った worker-a だけが True になり、worker-b は None(取れない)です。外すときは、値(自分の名前)を確かめてから DEL します。worker-b の解除は 0(外せない)、worker-a は 1 でした。単純な DEL にすると、処理が長引いて期限切れになった後に、他の担当が取ったロックを消してしまいます。Redis 8.4 以降は、同じことを 1 コマンドで行う DELEX key IFEQ 値 があります(redis-cli で確認)。

127.0.0.1:6379> SET lock:rebuild:product:1 worker-a NX PX 10000
OK
127.0.0.1:6379> SET lock:rebuild:product:1 worker-b NX PX 10000
(nil)
127.0.0.1:6379> DELEX lock:rebuild:product:1 IFEQ worker-b
(integer) 0
127.0.0.1:6379> DELEX lock:rebuild:product:1 IFEQ worker-a
(integer) 1

複数台の Redis で合意を取る Redlock などの方式は、公式ドキュメント「Distributed Locks with Redis」で説明されています。

トランザクション(MULTI・EXEC・WATCH)

この節では、複数のコマンドをまとめて実行する方法と、RDB のトランザクションとの大きな違い(取り消しが無いこと)が分かります。

MULTI から EXEC までのコマンドは、途中にほかのクライアントのコマンドを挟まずに、まとめて実行されます。 在庫を減らして注文を記録する、のような「続けて実行したい」処理に使います。

127.0.0.1:26379> MULTI
OK
127.0.0.1:26379> INCR stock:1
QUEUED
127.0.0.1:26379> DECRBY wallet:u1 300
QUEUED
127.0.0.1:26379> RPUSH orders:u1 item-1
QUEUED
127.0.0.1:26379> EXEC
1) (integer) 1
2) (integer) -300
3) (integer) 1

ただし、途中のコマンドが実行時にエラーになっても、ほかのコマンドは取り消されません。 公式ドキュメントも「Redis はトランザクションのロールバックに対応していない」と説明しています。一方、コマンド名の誤りのように EXEC の前に分かるエラーがあると、トランザクション全体が捨てられます。

127.0.0.1:26379> MULTI
OK
127.0.0.1:26379> SET a 1
QUEUED
127.0.0.1:26379> LPUSH a x
QUEUED
127.0.0.1:26379> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:26379> GET a
"1"
127.0.0.1:26379> MULTI
OK
127.0.0.1:26379> SET b 1
QUEUED
127.0.0.1:26379> NOSUCHCMD
(error) ERR unknown command 'NOSUCHCMD', with args beginning with: 
127.0.0.1:26379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:26379> GET b
(nil)
エラーの起き方 例 結果
実行時のエラー 文字列のキーに LPUSH(WRONGTYPE) ほかのコマンドは実行される(SET a 1 は残った)
積む時点のエラー コマンド名の誤り・引数の数の誤り EXECABORT で全体が捨てられる(b は作られない)

WATCH で「読んでから書く」の競合を防ぐ

読んだ値をもとに書く処理(在庫を見てから減らす等)は、WATCH で見張ります。見張ったキーが EXEC までにほかから変更されると、トランザクションは実行されず、EXEC が nil を返します(楽観ロック)。2つの接続で、A が在庫を読んだ直後に B が買った場合を再現しました。

pa = a.pipeline()
pa.watch("stock:item1")                 # A が在庫を見張る
stock = int(pa.get("stock:item1"))
b.decr("stock:item1")                   # その間に B が1つ買う
pa.multi()
pa.decr("stock:item1")
pa.execute()                            # redis.WatchError になる
A が読んだ在庫: 1
B が買った後の在庫: 0
A の購入: WatchError(取り消し)
最終の在庫: 0

A の DECR は実行されず、在庫はマイナスになりませんでした。 WatchError になったら、読み直しからやり直します。値を確かめてから書くだけなら、上のロック解除のように Lua スクリプト(EVAL)で1操作にする方法もあります。

クライアントからの使い方:Python・Node.js・Java

この節では、主な言語から Redis につなぐ最小のコードと、どの言語でも共通の注意点が分かります。

Node.js(node-redis・ioredis)

node-redis(npm の redis)6.3.0 で実行した結果です。

import { createClient } from "redis";

const client = createClient({ url: "redis://localhost:6379" });
client.on("error", (err) => console.error("Redis Client Error", err));
await client.connect();

await client.set("greeting", "hello", { EX: 60 });
console.log("GET greeting:", await client.get("greeting"));
console.log("TTL greeting:", await client.ttl("greeting"));
await client.hSet("user:1", { name: "Sato", plan: "free" });
console.log("HGETALL user:1:", await client.hGetAll("user:1"));
console.log("INCR page:views:", await client.incr("page:views"));

await client.close();
GET greeting: hello
TTL greeting: 60
HGETALL user:1: { name: 'Sato', plan: 'free' }
INCR page:views: 11

ioredis 6.0.0 では、コマンド名が小文字のメソッドになり、オプションは引数で渡します。

import Redis from "ioredis";
const redis = new Redis({ host: "localhost", port: 6379 });
await redis.set("greeting", "hello", "EX", 60);
console.log(await redis.zrevrange("ranking:2026-10", 0, 2, "WITHSCORES"));
redis.quit();
[ 'user:3', '150', 'user:2', '135', 'user:1', '120' ]

Java(Jedis・Lettuce)

Jedis は 7.0 で RedisClient クラスが導入され、8.0 で従来の JedisPooled などが削除されました(GitHub のリリースノート)。公式ドキュメントの書き方です。

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>8.0.1</version>
</dependency>
import redis.clients.jedis.RedisClient;

public class Main {
    public static void main(String[] args) {
        RedisClient jedis = new RedisClient("redis://localhost:6379");

        String res1 = jedis.set("bike:1", "Deimos");
        System.out.println(res1); // OK

        String res2 = jedis.get("bike:1");
        System.out.println(res2); // Deimos

        jedis.close();
    }
}

Lettuce(Spring Data Redis の既定のクライアント)は非同期・リアクティブにも対応しています。同期 API の最小形は次のとおりです。

import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

RedisURI uri = RedisURI.Builder.redis("localhost", 6379).build();
RedisClient client = RedisClient.create(uri);
StatefulRedisConnection<String, String> connection = client.connect();
RedisCommands<String, String> commands = connection.sync();
commands.set("foo", "bar");
System.out.println(commands.get("foo")); // bar
connection.close();
client.shutdown();

クライアント共通の注意

項目 内容
接続の使い回し 接続は使い回す(redis-py・Jedis は既定で接続プールを持ち、Lettuce は 1 本の接続を複数スレッドで共有できる)。リクエストごとに接続を作ると max number of clients reached の原因になる
タイムアウト 接続タイムアウトと読み取りタイムアウトを必ず設定する(redis-py は socket_connect_timeout / socket_timeout)
文字列の扱い redis-py は既定で bytes を返す。decode_responses=True で str になる
まとめ送り 複数コマンドはパイプライン(r.pipeline())で往復を減らす
接続 URL redis://[:password@]host:port/db。TLS は rediss://

メモリ上限と eviction(maxmemory-policy)

この節では、メモリがいっぱいになったときに Redis がどう動くかと、用途ごとの設定が分かります。

Redis のデータはメモリに置かれるため、上限(maxmemory)と、上限に達したときの動き(maxmemory-policy)を決めておきます。8.10.2 の既定値は次のとおりでした。

127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "0"
127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"

maxmemory 0 は「上限なし」(64bit 環境の既定。公式ドキュメントでは 32bit 環境は暗黙に 3GB)です。上限なしのまま TTL の無いキーを増やすと、OS のメモリを使い切るまで止まりません。

上限に達するとどうなるか(OOM エラーの再現)

maxmemory 1mb・noeviction にして 1KB の値を書き続けると、286 件目で書き込みがエラーになりました。読み取りと削除はできます。

127.0.0.1:16379> SET k "value"
(error) OOM command not allowed when used memory > 'maxmemory'.
127.0.0.1:16379> INCR counter
(error) OOM command not allowed when used memory > 'maxmemory'.
127.0.0.1:16379> GET fill:2
"xxxxxxxxxx…"(1000 文字の値。省略)
127.0.0.1:16379> CONFIG SET maxmemory-policy allkeys-lru
OK
127.0.0.1:16379> SET k "value"
OK
127.0.0.1:16379> DBSIZE
(integer) 171

allkeys-lru に切り替えると、最近使われていないキーから消して書き込みが通りました(INFO stats の evicted_keys:115、286 件 → 171 件)。

maxmemory-policy 上限に達したとき 向く場面
noeviction(既定) 新しいデータを書くコマンドがエラーになる 消えては困るデータ(キュー・セッション)
allkeys-lru 全キーから、最近使われていないものを消す キャッシュ専用(公式ドキュメントが「理由がなければこれ」と案内)
allkeys-lfu 全キーから、使われる回数が少ないものを消す 一部のキーだけ極端に読まれるキャッシュ
allkeys-lrm / volatile-lrm 最近「更新」されていないものを消す(読み取りでは更新扱いにしない。8.6 以降) 読まれ続けても更新されないキーを消したいとき
volatile-lru / volatile-lfu / volatile-ttl / volatile-random TTL のあるキーだけを対象にする キャッシュと永続データを同居させるとき(TTL 無しのキーは残る)
allkeys-random 無作為に消す ―

volatile-* は TTL のあるキーが無いと noeviction と同じ動きになります(公式ドキュメント)。LRU・LFU は厳密ではなく、maxmemory-samples(既定 5)件を抜き取って選ぶ近似です。メモリの使用量は INFO memory の used_memory_human、キーごとの大きさは MEMORY USAGE key で確かめられます(1万件の商品データを入れた Redis 7.0.15 は used_memory_human:2.69M でした)。

永続化(RDB・AOF)と再起動でどうなるか

この節では、Redis のデータがディスクに残る仕組みと、止め方によって再起動後に何が残るかが、実験の結果で分かります。

Redis はメモリ上のデータをディスクにも保存できます。方式は2つあり、既定は「RDB ファイルの自動保存だけ」です。

方式 仕組み 長所 気をつける点
RDB(スナップショット) 一定の条件でメモリの内容をまるごと dump.rdb に保存(save 3600 1 300 100 60 10000) ファイルが小さく、再起動が速い。バックアップに向く 最後の保存以降の変更は失われる。保存時に fork するので大きなデータでは一瞬止まる
AOF(追記ログ) 書き込みコマンドを順に appendonlydir/ に追記(appendfsync everysec が既定) 失うデータを最大 1 秒分にできる ファイルが大きくなりやすい(自動で書き直す)。再起動時の読み込みが RDB より遅い
RDB+AOF 両方を使う 公式ドキュメントが「PostgreSQL 並みの安全性が欲しければ両方」と案内 ディスクの使用量が増える
なし(save ""・appendonly no) 保存しない もっとも速い 再起動でデータが消える(純粋なキャッシュ向け)

save 3600 1 300 100 60 10000 は「3600 秒以内に 1 回以上」「300 秒以内に 100 回以上」「60 秒以内に 10000 回以上」の変更があったら RDB を保存する、という意味です(redis.conf の説明)。

実験:止め方と設定で、再起動後に何が残るか

同じ Redis 8.10.2 で、設定と止め方を変えて確かめました。

設定 止め方 再起動後の GET counter
既定(save 条件のみ、BGSAVE 前) kill -9(異常終了) (nil):消えた
既定で BGSAVE 済み kill -9 "100":残った
既定(save 条件あり) SHUTDOWN(正常停止) "200":残った(停止時に RDB を保存)
appendonly yes(everysec) kill -9 "301":残った
save ""・appendonly no SHUTDOWN (nil):消えた

既定のまま kill -9 で止めた場合(1行目)は、save の条件(1 時間以内に 1 回の変更など)に達する前だったため dump.rdb が無く、データが消えました。

127.0.0.1:16379> SET counter 100
OK
127.0.0.1:16379> BGSAVE
Background saving started
$ ls data
dump.rdb  redis.log
$ kill -9 <PID>
(再起動)
127.0.0.1:16379> GET counter
"100"
26297:M 16 Oct 2026 19:00:51.158 * Loading RDB produced by version 8.10.2
26297:M 16 Oct 2026 19:00:51.158 * DB loaded from disk: 0.000 seconds

正常停止では、ログに Saving the final RDB snapshot before exiting. と DB saved on disk が出て保存されました。AOF を有効にすると appendonlydir/ に 3 つのファイルができ、増分ファイルには実行したコマンドがそのまま記録されます。

$ ls appendonlydir
appendonly.aof.1.base.rdb  appendonly.aof.1.incr.aof  appendonly.aof.manifest
$ cat appendonlydir/appendonly.aof.1.incr.aof
*2
$6
SELECT
$1
0
*3
$3
SET
$7
counter
$3
300
*2
$4
INCR
$7
counter
(再起動)
26695:M 16 Oct 2026 19:01:12.576 * DB loaded from append only file: 0.000 seconds
flowchart TD
    Q1{SHUTDOWN<br>save あり} -- はい --> K1[残る]
    Q1 -- いいえ --> Q3{AOF}
    Q3 -- あり --> K2[1秒分以外<br>残る]
    Q3 -- なし --> Q4{dump.rdb}
    Q4 -- あり --> K3[保存時点<br>まで残る]
    Q4 -- なし --> L[消える]
用途 目安の設定
消えても作り直せるキャッシュ 永続化なし(save "")か RDB のみ。maxmemory-policy allkeys-lru
セッション・キュー・カウンター appendonly yes(everysec)+ RDB。noeviction
失ってはいけない業務データ RDB(PostgreSQL など)に保存し、Redis は写しとして使う

永続化の状態は INFO persistence の aof_enabled、rdb_last_save_time、rdb_changes_since_last_save で確かめられます。

レプリケーション・Sentinel・Cluster(止まらない構成)

この節では、Redis を複数台で動かす3つの仕組みの違いと、レプリケーションの動きが実行結果で分かります。

仕組み 何をするか 使う場面
レプリケーション 1台(プライマリ)の内容を、ほかの台(レプリカ)に写し続ける 読み取りの分散、障害に備えた写し
Sentinel プライマリを見張り、止まったらレプリカを昇格させてクライアントに伝える 1台に収まるデータを止めずに動かす
Cluster キーを 16384 個のスロットに分けて、複数のプライマリに振り分ける 1台のメモリに収まらない・書き込みを分散したい

レプリケーションを手元で動かす

127.0.0.1 の 26379 番(プライマリ)と 26380 番(レプリカ)で、2つの Redis 7.0.15 を動かしました。REPLICAOF でレプリカにすると、プライマリの書き込みがレプリカで読めるようになり、レプリカへの書き込みは拒否されます。

$ redis-cli -p 26380 REPLICAOF 127.0.0.1 26379
OK
$ redis-cli -p 26379 SET user:3 Tanaka
OK
$ redis-cli -p 26380 GET user:3
"Tanaka"
$ redis-cli -p 26380 SET user:4 Ito
(error) READONLY You can't write against a read only replica.
$ redis-cli -p 26380 INFO replication
role:slave
master_host:127.0.0.1
master_port:26379
master_link_status:up
slave_read_only:1
$ redis-cli -p 26379 INFO replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=26380,state=online,offset=86,lag=0

プライマリのログには、ディスクを使わずに RDB を送る「ディスクレス同期」の記録が残りました(repl-diskless-sync yes、送り始めまで既定で 5 秒待つ repl-diskless-sync-delay 5)。

* Starting BGSAVE for SYNC with target: replicas sockets
* Streamed RDB transfer with replica 127.0.0.1:26380 succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming
* Synchronization with replica 127.0.0.1:26380 succeeded

レプリケーションは既定で非同期です。 プライマリに書いてからレプリカで読めるまでを 1,000 回測ると、p50 が 0.073 ms、最大 4.710 ms でした。書いた直後にレプリカから読むと、まだ更新前の値が返ることがあります。WAIT 1 100(1台のレプリカが受け取るまで最大 100 ミリ秒待つ)は 1 を返しました。公式ドキュメントは、WAIT を使っても障害の切り替え時に書き込みが失われる可能性はあると説明しています。

Cluster のキーとスロット

Cluster モードで起動した Redis で、キーがどのスロットに入るかを確かめました。{} で囲んだ部分(ハッシュタグ)が同じキーは、同じスロットに入ります。

$ redis-cli -p 26382 CLUSTER KEYSLOT user:1
(integer) 10778
$ redis-cli -p 26382 CLUSTER KEYSLOT user:2
(integer) 6777
$ redis-cli -p 26382 CLUSTER KEYSLOT user:{123}:profile
(integer) 5970
$ redis-cli -p 26382 CLUSTER KEYSLOT user:{123}:account
(integer) 5970
$ redis-cli -p 26382 SET user:1 x
(error) CLUSTERDOWN Hash slot not served

公式ドキュメントでは、スロットは「キーの CRC16 を 16384 で割った余り」で決まり、1つのコマンド・トランザクション・Lua スクリプトで扱う複数のキーは同じスロットに入っている必要があります。最小構成はプライマリ3台で、プライマリ3台とレプリカ3台の6台が推奨されています。最後の CLUSTERDOWN は、スロットをどの台にも割り当てていない1台だけの Cluster に書いたためです。クラウドのマネージドサービスで作る方法は、関連記事「ElastiCacheの使い方|Valkey/Redisの作り方と接続コード」で扱っています。

よくある失敗とエラー文(原文)

この節では、Redis でよく出るエラー文の意味と直し方、性能を落とす4つの失敗が実測で分かります。

エラー文(原文) 原因 直し方
Could not connect to Redis at 127.0.0.1:6379: Connection refused Redis が起動していない、ポートが違う、bind が別アドレス ss -ltnp | grep 6379 で待ち受けを確認し、起動する
(error) NOAUTH Authentication required. パスワード(requirepass / ACL)が設定されているのに未認証 AUTH パスワード、または接続 URL に redis://:pass@host:6379
(error) WRONGPASS invalid username-password pair or user is disabled. パスワードかユーザー名が違う 設定値を確かめる。ACL ユーザーなら --user も指定
DENIED Redis is running in protected mode ... パスワード無しで、ループバック以外のアドレスから接続した パスワードを設定する(requirepass)。公開範囲は bind で絞る
(error) OOM command not allowed when used memory > 'maxmemory'. maxmemory に達し、noeviction TTL を付ける、キャッシュなら allkeys-lru、上限を見直す
(error) WRONGTYPE Operation against a key holding the wrong kind of value String のキーに LPUSH など、型の違うコマンド TYPE key で型を確かめる。用途ごとにキー名の接頭辞を分ける
(error) READONLY You can't write against a read only replica. レプリカに書き込んだ 書き込みはプライマリへ送る。Sentinel 対応のクライアントで接続先を自動で切り替える
(error) EXECABORT Transaction discarded because of previous errors. MULTI の中でコマンド名・引数の誤り 誤りを直して、MULTI からやり直す
(error) CLUSTERDOWN Hash slot not served Cluster でスロットが割り当てられていない・担当の台が止まった CLUSTER INFO でスロットの割り当てと状態を確かめる
ERR max number of clients reached 接続数が maxclients(既定 10000)に達した 接続を使い回す(プール)、閉じ忘れを直す。CLIENT LIST で接続元を見る
(error) ERR unknown command 'JSON.SET', ... JSON などの追加データ型が入っていない版・構成 MODULE LIST で確認し、8 系の公式配布物を使う
(error) NOPERM No permissions to access a key ACL でそのキーに権限がない ACL SETUSER のキーパターン(~cache:*)を見直す
(error) ERR syntax error オプションの組み合わせが不正(例:SET k v EX 120 KEEPTTL) EX と KEEPTTL は同時に指定できない。コマンドの書式を確かめる
ERR Background save already in progress BGSAVE 中にもう一度 SAVE/BGSAVE INFO persistence の rdb_bgsave_in_progress が 0 になってから

接続数と認証のエラーは、maxclients 3・requirepass を設定した Redis で再現したものです。

1 本目: 接続できた(CLIENT LIST の行数 1)
2 本目: 接続できた(CLIENT LIST の行数 2)
3 本目: 接続できた(CLIENT LIST の行数 3)
4 本目: ConnectionError: max number of clients reached

127.0.0.1:16379> GET k
(error) NOAUTH Authentication required.
127.0.0.1:16379> AUTH wrongpass
(error) WRONGPASS invalid username-password pair or user is disabled.
127.0.0.1:16379> AUTH s3cret
OK
127.0.0.1:16379> GET k
"value"

クライアント側のエラーは、同じ「接続拒否」でも表示が変わります(redis-py は Error 111 connecting to localhost:16379. Connection refused.、node-redis は connect ECONNREFUSED 127.0.0.1:16379)。Connection refused と Read timed out の違いは、関連記事「Connection refused・Read timed outの違いと直し方」で扱っています。

1. 本番で KEYS * を実行する

Redis は 1 つのコマンドを順番に処理するため、時間のかかるコマンドの間は他のリクエストが待たされます。公式ドキュメントの KEYS のページにも「本番環境では細心の注意を。通常のアプリケーションコードで使わない」と書かれています。20 万件のキーがある状態で、別の接続から PING を送り続け、待ち時間の最大値を 2 回測りました。

実行したもの 所要時間(2回) PING の最大待ち時間(2回)
KEYS tmp:*(1 回で全部取得) 440 ms / 336 ms 79.9 ms / 122.1 ms
SCAN ... COUNT 1000(1,000 件ずつ) 579 ms / 590 ms 10.4 ms / 9.2 ms

サーバー側の遅いコマンドの記録(SLOWLOG GET)でも、KEYS tmp:* 1 回に 81,346 マイクロ秒と 103,309 マイクロ秒(約 0.1 秒)かかっていました。全体の所要時間は SCAN のほうが長くなりますが、SCAN は少しずつ取り出すため、ほかのリクエストが間に入れます。 キーが数百万件になると KEYS の停止時間はさらに伸びます。

# 対策:SCAN で少しずつ取り出す
for key in r.scan_iter(match="tmp:*", count=1000):
    ...

2. 大きなキー(ビッグキー)を作る

1 つの List に 50 万件の要素を入れて確かめました。

big:list 要素数: 500000  MEMORY USAGE: 6425719 bytes
user:1(Hash 3 項目) MEMORY USAGE: 94 bytes
LRANGE big:list 0 -1 : 1115.1 ms
LRANGE big:list 0 99 : 0.60 ms

1 つのキーで約 6.4MB、全件取得(LRANGE 0 -1)に 1.1 秒かかり、その間ほかのリクエストは待たされます。 大きなキーは redis-cli --bigkeys で見つけられます。

$ redis-cli --bigkeys
-------- summary -------
Sampled 200018 keys in the keyspace!
Biggest   list found "big:list" has 500000 items
Biggest   hash found "user:1" has 3 fields
対策 内容
分割する user:1:follows:0、user:1:follows:1 のように分ける
少しずつ読む LRANGE の範囲指定、HSCAN・SSCAN・ZSCAN を使う
削除は UNLINK 解放を別スレッドで行う(4.0 以降)。lazyfree-lazy-user-del yes にすると DEL も同じ動きになる
定期的に調べる redis-cli --bigkeys、MEMORY USAGE key、8.6 以降は HOTKEYS START METRICS 1 CPU でよく使われるキーを集計

3. TTL を付けずに保存し続ける

有効期限のないキャッシュは、使われなくなってもメモリに残り続けます。 既定は maxmemory 0(上限なし)・noeviction なので、上限を決めなければ OS のメモリを使い切るまで増え、上限を決めても noeviction のままでは上の OOM エラーで書き込みが止まります。キャッシュには必ず TTL を付け、キャッシュ専用の Redis なら maxmemory と allkeys-lru を設定します。

4. キャッシュが一斉に切れて、DB に問い合わせが集中する

人気のあるキャッシュの有効期限が切れた瞬間に、多くのリクエストが同時に DB へ向かう現象です(キャッシュスタンピード)。キャッシュが空の状態で 50 件のリクエストを同時に送り、DB への問い合わせ回数を数えました。

方式 DB への問い合わせ回数(2回) 全員が結果を得るまで
対策なし(Cache-Aside のみ) 50 回 / 50 回 229 ms / 221 ms
ロックあり(1 人だけが DB を読む) 1 回 / 1 回 256 ms / 253 ms
def get_with_lock(pid):
    key = f"product:{pid}"
    while True:
        v = r.get(key)
        if v:
            return json.loads(v)
        if r.set(f"lock:{key}", "1", nx=True, ex=5):   # 1人だけDBを読む
            try:
                p = load_from_db(pid)
                r.set(key, json.dumps(p), ex=300)
                return p
            finally:
                r.delete(f"lock:{key}")
        time.sleep(0.05)                               # ほかの人は少し待って読み直す
対策 内容
TTL にばらつきを持たせる 同時に作ったキャッシュが、同時に切れないようにする
ロックで 1 人だけ作り直す 上の例。ほかのリクエストは少し待ってキャッシュを読む
期限切れの前に更新する 人気のデータはバックグラウンドで先に作り直す

やってはいけない操作と、戻し方

この節では、データを失う・全体を止める操作と、消してしまったときの戻し方が分かります。

操作 起きること 代わりに・戻し方
FLUSHALL / FLUSHDB を本番で 全キーが消える。永続化していなければ戻らない ACL で禁止する(下の例)。AOF があれば該当行を消して起動(下の例)
KEYS * を本番で 全体が止まる(上の実測) SCAN / --scan
大きなキーを DEL 解放の間止まる UNLINK
CONFIG SET だけで設定を変える 再起動で元に戻る CONFIG REWRITE で設定ファイルに書き戻す
bind 0.0.0.0 にして protected-mode no・パスワード無し インターネットから読み書きされる パスワード(requirepass)か ACL、bind と FW で範囲を絞る。TLS
SHUTDOWN NOSAVE 最後の保存以降のデータが消える 通常の SHUTDOWN(save 条件があれば保存してから止まる)
SAVE を本番で実行 保存が終わるまで全リクエストが止まる BGSAVE(子プロセスで保存)
大事なデータを Redis だけに保存 障害・設定ミスで消える RDB に正式に保存し、Redis には写しを置く

戻し方 1:AOF から FLUSHALL だけを取り消す

AOF にはコマンドがそのまま記録されるため、公式ドキュメントのとおり 書き直し(rewrite)が起きる前なら、FLUSHALL の行を消して起動すると戻せます。

127.0.0.1:16379> SET user:1 "Sato"
OK
127.0.0.1:16379> SET user:2 "Suzuki"
OK
127.0.0.1:16379> FLUSHALL
OK
127.0.0.1:16379> DBSIZE
(integer) 0
$ redis-cli -p 16379 SHUTDOWN
$ tail -3 appendonlydir/appendonly.aof.1.incr.aof
*1
$8
FLUSHALL
(この 3 行を消して保存)
$ redis-check-aof appendonlydir/appendonly.aof.manifest
AOF analyzed: filename=appendonly.aof.1.incr.aof, size=95, ok_up_to=95, ok_up_to_line=20, diff=0
INCR AOF appendonly.aof.1.incr.aof is valid
All AOF files and manifest are valid
(起動)
127.0.0.1:16379> DBSIZE
(integer) 2
127.0.0.1:16379> GET user:1
"Sato"

戻し方 2:RDB のバックアップから戻す

redis-cli --rdb ファイル名 で、動いている Redis からスナップショットを取り出せます。戻すときは Redis を止め、dir の dump.rdb を差し替えて起動します(appendonly yes のときは AOF から読み込むので、先に AOF を無効にするか、appendonlydir を退避します)。

$ redis-cli -p 16379 --rdb backup.rdb
sending REPLCONF rdb-only 1
SYNC sent to master, writing bytes of bulk transfer until EOF marker to 'backup.rdb'
Transfer finished with success after 205 bytes

防ぎ方:ACL で危険なコマンドを禁止する

アプリ用のユーザーに、必要なキーとコマンドだけを許可した例です。FLUSHALL は NOPERM で止まります。

$ redis-cli ACL SETUSER app on '>apppass' '~cache:*' '+get' '+set' '+del'
OK
$ redis-cli --user app --pass apppass --no-auth-warning SET cache:1 v
OK
$ redis-cli --user app --pass apppass --no-auth-warning SET other:1 v
NOPERM No permissions to access a key
$ redis-cli --user app --pass apppass --no-auth-warning FLUSHALL
NOPERM User app has no permissions to run the 'flushall' command

既定の設定(bind 127.0.0.1 -::1・protected-mode yes)では、同じマシン以外から接続できません。パスワード無しのまま bind 0.0.0.0 にして別のアドレスから接続すると、次の DENIED が返り、接続は受け付けられません。

$ redis-cli -h 192.0.2.2 -p 16379 ping
DENIED Redis is running in protected mode because protected mode is enabled and no password is set for the default user. In this mode connections are only accepted from the loopback interface. ...

本番に出す前に確かめる項目です。

  • 1外から直接つながらないbind・ファイアウォールで範囲を絞り、パスワードか ACL を設定した
  • 2maxmemory と maxmemory-policy を決めたキャッシュ専用なら allkeys-lru、消えては困るデータがあるなら noeviction
  • 3キャッシュに TTL を付けたTTL にばらつきを持たせ、一斉に切れないようにした
  • 4永続化の方針を決めたキャッシュは保存なしか RDB ファイル、セッション・キューは AOF も
  • 5アプリのコードに KEYS・FLUSHALL が無いSCAN を使い、ACL で FLUSHALL を禁止した
  • 6メモリと遅いコマンドを見張っているINFO memory の used_memory と SLOWLOG GET

Redisのライセンスと Valkey との関係

この節では、2024年からのライセンスの変化と、Redis から分かれた Valkey の関係が、公式の発表と手元の実行結果で分かります。

2009年

公開
Salvatore Sanfilippo 氏が BSD ライセンスで公開

2024年3月

変更
Redis 7.4 から RSALv2 / SSPLv1 のデュアルライセンスに。7.2.x までは BSD-3-Clause のまま

2024年3月28日

分岐
Linux Foundation が、Redis 7.2.4 から開発を引き継ぐ Valkey(BSD-3-Clause)を発表

2025年5月1日

追加
Redis 8 から AGPLv3 を加え、RSALv2・SSPLv1・AGPLv3 から選べる形に。無償版の名前は Redis Open Source

2026年10月24日時点

最新
Redis 8.10.2(9月17日。同じ日に 8.8.3・8.6.7・8.4.7・8.2.10 も公開)。Valkey 9.1.2(9月1日)

Redis 公式のライセンスページには「Redis 8 以降の Redis Open Source は、RSALv2・SSPLv1・AGPLv3 の 3 つから 1 つを選んで使える」「7.2.x 以前は BSD3 のまま」とあり、AGPLv3 は OSI が認定したオープンソースライセンスです。Redis 公式ブログ(2025年5月1日)は、Redis 8 で AGPLv3 を加えたこと、JSON・時系列・確率的データ構造・クエリエンジンをコアに統合したことを発表しています。Valkey は Linux Foundation の発表(2024年3月28日)で「Redis 7.2.4 の開発を引き継ぐ」とされたプロジェクトで、支援企業として AWS・Google Cloud・Oracle・Ericsson・Snap Inc. が挙げられています。

Valkey は Redis のクライアントでそのまま使える

Ubuntu 24.04 の apt で入る Valkey 7.2.13 を 26381 番で起動し、Redis の redis-cli 7.0.15 と redis-py 8.1.0 から接続しました。INFO server は互換のために redis_version:7.2.4 も返し、コマンドも応答も同じでした。

$ redis-cli -p 26381 INFO server
redis_version:7.2.4
server_name:valkey
valkey_version:7.2.13
redis_mode:standalone
$ redis-cli -p 26381 ZREVRANGE ranking 0 -1 WITHSCORES
1) "alice"
2) "120"
3) "bob"
4) "95"
$ python3 -c "..."      # redis-py 8.1.0 で SET k v EX 60 → GET・TTL
redis-py 8.1.0 から Valkey: v 60

Valkey の README では、make install で redis-server・redis-cli の名前のシンボリックリンクも作ると説明されています。

主な追加は公式リリースノートと COMMAND DOCS の since の値で確かめたものです。

版 主な追加
7.0(2022年) multi-part AOF(appendonlydir/)、EXPIRE の NX/XX/GT/LT
7.4(2024年) Hash の項目単位の TTL(HEXPIRE など)。ライセンス変更
8.0(2025年5月) JSON・時系列・確率的データ構造・Search・ベクトルセットを同梱。HSETEX/HGETEX/HGETDEL、新しい I/O スレッド実装、AGPLv3
8.4(2025年11月) DELEX・DIGEST、SET の IFEQ などの条件付きオプション(COMMAND DOCS SET の履歴は 8.4.0)
8.6(2026年2月) allkeys-lrm/volatile-lrm、HOTKEYS、XADD の重複防止(IDMP)、Hash と Sorted Set のメモリ削減
8.10(2026年7月) compact hashes(同じ項目構成の Hash で項目名を1回だけ持つ)、BACKUP、LMOVEM/BLMOVEM、SUNIONCARD/SDIFFCARD
選ぶとき Redis 8 Valkey
ライセンス RSALv2・SSPLv1・AGPLv3 から選ぶ BSD-3-Clause
7.2 までのコマンド・プロトコル 共通 共通(redis-cli・redis-py で接続できた)
マネージドサービスの例 ElastiCache for Redis OSS、Azure Managed Redis ElastiCache for Valkey

クラウドのマネージドサービスでは、Amazon ElastiCache が「ElastiCache for Valkey」と「ElastiCache for Redis OSS」の 2 つのエンジン名を持ち、Google Cloud Memorystore にも Valkey と Redis の両方があります。Azure は Azure Managed Redis の名前で提供しています。Ubuntu 24.04 の標準リポジトリには redis-server(7.0.15)と valkey-server(7.2.13)の両方のパッケージがあります。

よくある質問

Q. redisとは何ですか?簡単に言うと? A. データをメモリ上に置き、キーと値の形で読み書きする高速なデータベース(インメモリ KVS)です。1 件の読み書きが 1 ミリ秒未満で、キャッシュ・セッション・ランキング・カウンター・キューに使われます。「Redisとは」の節で扱っています。

Q. Redis の読み方は? A. 「レディス」です。名前は REmote DIctionary Server の略です。

Q. Redis は何に使うのですか? A. キャッシュ、セッションストア、ランキング、カウンター・レート制限、ジョブキュー・イベント処理、通知(Pub/Sub)、分散ロック、ユニーク数の概算、位置情報検索です。冒頭の早見表と「用途一覧」の節に、向く理由と向かないものをまとめています。

Q. Redis はなぜ速いのですか? A. データがメモリにあること、1本のスレッドが順に処理するのでロックの待ち合わせが無いこと、書き込みのたびにディスクを待たないことが理由です。実測では、同じ値の更新が PostgreSQL の 8.5 倍(1接続)〜54 倍(50接続)でした。主キーで1行読むだけなら差は 1.2〜2.0 倍でした。

Q. Redis と RDB(MySQL・PostgreSQL)の違いは? A. Redis はメモリ上のキーと値、RDB はディスク上の表と SQL です。Redis は速さと便利なデータ型、RDB は自由な検索・結合とロールバックできるトランザクションが強みで、正式なデータは RDB、よく読む写しと一時データは Redis、と組み合わせます。

Q. redis をキャッシュとして使うには? A. Cache-Aside(まず Redis を見て、無ければ DB から読んで SET ... EX で入れる)が基本です。TTL を必ず付け、更新時は DB を更新してからキャッシュを削除します。実測では PostgreSQL の集計 18.55 ms がキャッシュ後 0.14〜0.24 ms になりました。キャッシュ専用の Redis は maxmemory と allkeys-lru を設定します。

Q. redis の使い方は? A. インストールして redis-cli ping が PONG を返せば使えます。SET key value → GET key が基本で、用途に合わせて Hash・List・Set・Sorted Set・Stream を使い分けます。アプリからは redis-py(Python)、node-redis / ioredis(Node.js)、Jedis / Lettuce(Java)で接続します。

Q. Redis は無料で使えますか? A. Redis Open Source(8 以降)は RSALv2・SSPLv1・AGPLv3 から選んで無償で使えます。利用形態(SaaS として提供するか、など)でライセンスの条件が変わるため、公式のライセンスページで確かめます。BSD ライセンスのままの選択肢としては Valkey があります。クラウドのマネージドサービスは各社の料金がかかります。

Q. Redis と Memcached の違いは? A. Memcached は文字列のキャッシュに特化し、永続化を持ちません。Redis は Hash・List・Sorted Set・Stream などのデータ型、永続化(RDB ファイル・AOF)、レプリケーションを持ち、キャッシュ以外にも使えます。比較表は「RedisとRDBの違い」の節にあります。

Q. Redis のデータは再起動で消えますか? A. 設定と止め方によります。既定(RDB の save 条件のみ)で kill -9 のように異常終了すると、最後のスナップショット以降は消えました。SHUTDOWN なら停止時に保存され、appendonly yes なら異常終了でも最後の 1 秒分を除いて残りました。「永続化」の節の表に 5 通りの結果があります。

Q. Redis のトランザクションは途中で失敗したら取り消されますか? A. 取り消されません。EXEC のあとで1つのコマンドがエラーになっても、ほかのコマンドは実行されたままです(実行結果で SET a 1 が残りました)。読んでから書く処理は WATCH で競合を検出します。

Q. Redis と Valkey はどちらを使えばよいですか? A. Valkey は Redis 7.2.4 を引き継いでいるため、7.2 までのコマンドは共通で、クライアントも同じものが使えました。Redis 8 で1つの配布物に同梱された JSON・時系列・Search などを使うなら Redis、BSD ライセンスを重視するなら Valkey が候補です。マネージドサービス(ElastiCache など)では両方のエンジンが選べます。

Q. 本番で使うときに最低限やることは? A. 外部から直接接続できないようにする(bind・ファイアウォール・パスワードか ACL)、maxmemory と maxmemory-policy の設定、キャッシュへの TTL、KEYS など重いコマンドを使わない、永続化の方針(RDB ファイル / AOF)を決める、メモリ使用量と SLOWLOG の監視です。「やってはいけない操作」の節のチェックリストにまとめています。

まとめ

  • Redis は メモリ上で動く高速なキーと値のデータベース。RDB と組み合わせ、正式なデータは RDB に置く
  • 速さの差が大きいのは 書き込み。同じ値の更新は PostgreSQL の 8.5〜54 倍、主キーの読みは 1.2〜2.0 倍(実測)
  • キャッシュは Cache-Aside + 必ず TTL。集計 18.55 ms が 0.14 ms に。SET で上書きすると TTL が消える(KEEPTTL)
  • ランキングは Sorted Set、レート制限は INCR + EXPIRE NX、ロックは SET NX PX(解除は値を確かめてから)
  • MULTI/EXEC は 取り消しが無い。競合は WATCH で検出する
  • 既定は maxmemory 0・noeviction。上限に達すると OOM command not allowed ...。キャッシュ専用なら allkeys-lru
  • 再起動で残るかは設定と止め方で決まる。既定で kill -9 だと消え、SHUTDOWN か BGSAVE 済みか AOF なら残る
  • レプリケーションは 非同期。レプリカへの書き込みは READONLY。大きなデータは Cluster(16384 スロット)
  • 2024 年のライセンス変更後、2025 年 5 月の Redis 8 で AGPLv3 が加わり、BSD のフォークとして Valkey がある

参考・出典

確認日はいずれも 2026年10月24日です。

  • Redis 公式ドキュメント「Get started」:https://redis.io/docs/latest/develop/get-started/
  • Redis 公式ドキュメント「Understand Redis data types」:https://redis.io/docs/latest/develop/data-types/
  • Redis 公式ドキュメント「Redis hashes」(HEXPIRE・HSETEX の版):https://redis.io/docs/latest/develop/data-types/hashes/
  • Redis 公式ドキュメント「Transactions」:https://redis.io/docs/latest/develop/using-commands/transactions/
  • Redis 公式ドキュメント「Redis replication」:https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
  • Redis 公式ドキュメント「Scale with Redis Cluster」:https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/
  • Redis 公式ドキュメント「KEYS」:https://redis.io/docs/latest/commands/keys/
  • Redis 公式ドキュメント「Key eviction」:https://redis.io/docs/latest/develop/reference/eviction/
  • Redis 公式ドキュメント「Redis persistence」:https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/
  • Redis 公式ドキュメント「Diagnosing latency issues」:https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/latency/
  • Redis 公式ドキュメント「Install Redis on Linux」:https://redis.io/docs/latest/operate/oss_and_stack/install/archive/install-redis/install-redis-on-linux/
  • Redis 公式ドキュメント「Install Redis on macOS」:https://redis.io/docs/latest/operate/oss_and_stack/install/archive/install-redis/install-redis-on-mac-os/
  • Redis 公式ドキュメント「Install Redis on Windows」:https://redis.io/docs/latest/operate/oss_and_stack/install/archive/install-redis/install-redis-on-windows/
  • Redis 公式ドキュメント「Jedis guide (Java)」:https://redis.io/docs/latest/develop/clients/jedis/
  • Redis 公式ドキュメント「Lettuce guide (Java)」:https://redis.io/docs/latest/develop/clients/lettuce/
  • Redis 公式ドキュメント「Distributed Locks with Redis」:https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/
  • Redis 公式ドキュメント「HyperLogLog」(12 KB・誤差 0.81%):https://redis.io/docs/latest/develop/data-types/probabilistic/hyperloglogs/
  • Redis 公式ドキュメント「EXPIRE」(期限切れの消し方・NX/XX/GT/LT):https://redis.io/docs/latest/commands/expire/
  • Redis Open Source 8.0 リリースノート:https://redis.io/docs/latest/operate/oss_and_stack/stack-with-enterprise/release-notes/redisce/redisos-8.0-release-notes/
  • Redis Open Source 8.4 リリースノート:https://redis.io/docs/latest/operate/oss_and_stack/stack-with-enterprise/release-notes/redisce/redisos-8.4-release-notes/
  • Redis Open Source 8.6 リリースノート:https://redis.io/docs/latest/operate/oss_and_stack/stack-with-enterprise/release-notes/redisce/redisos-8.6-release-notes/
  • Redis Open Source 8.10 リリースノート:https://redis.io/docs/latest/operate/oss_and_stack/stack-with-enterprise/release-notes/redisce/redisos-8.10-release-notes/
  • Redis 公式「Licenses」:https://redis.io/legal/licenses/
  • Redis 公式ブログ「Redis is now available under the AGPLv3 open source license」(2025年5月1日):https://redis.io/blog/agplv3/
  • redis/redis GitHub リリース一覧(8.10.2 など):https://github.com/redis/redis/releases
  • redis/redis 8.10.2 README(ビルド手順)と redis.conf:https://github.com/redis/redis/tree/8.10.2
  • valkey-io/valkey GitHub リリース一覧(9.1.2 など):https://github.com/valkey-io/valkey/releases
  • valkey-io/valkey README(Redis 名のシンボリックリンク):https://github.com/valkey-io/valkey/blob/unstable/README.md
  • Linux Foundation「Linux Foundation Launches Open Source Valkey Community」(2024年3月28日):https://www.linuxfoundation.org/press/linux-foundation-launches-open-source-valkey-community
  • redis/jedis GitHub リリース一覧:https://github.com/redis/jedis/releases
  • redis/lettuce GitHub リリース一覧:https://github.com/redis/lettuce/releases
  • redis/redis-py GitHub リリース一覧:https://github.com/redis/redis-py/releases
  • PostgreSQL 16 ドキュメント「pgbench」:https://www.postgresql.org/docs/16/pgbench.html
  • PostgreSQL 16 ドキュメント「synchronous_commit」(WAL の設定):https://www.postgresql.org/docs/16/runtime-config-wal.html
  • Amazon ElastiCache「Supported versions」:https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/supported-engine-versions.html
  • memcached 公式ドキュメント(Configuring a Server・Protocols):https://docs.memcached.org/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Redisとは|何に使う・なぜ速いかをRDBとの実測で解説【用途早見表】」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?