こんにちは、かつコーチです。
前回までの記事で、Redisの基本コマンドSET・GET・EXPIREを扱いました。
これらは1つのキーに1つの値(文字列)を対応させる、最もシンプルな使い方でした。
ですがRedisの本当の強みは、実はここから先にあります。
Redisは1つのキーに対して、List・Hash・Set・Sorted Setという、目的に応じた複数のデータ構造を持たせることができます。
この記事では、それぞれのデータ構造の特徴と、どんな場面で使い分けるべきかを解説します。
Redisにおけるデータ構造とは
なぜ文字列だけでは足りないのか
前回扱ったSET/GETは、String(文字列)型と呼ばれるデータ構造でした。
Stringだけでも多くの用途はカバーできますが、「1人のユーザーに複数の投稿履歴を持たせたい」「複数の値をランキング順に並べたい」といった要件になると、Stringだけでは表現が難しくなります。
そこでRedisには、用途に応じた複数のデータ構造が用意されています。
Redis公式の分類に沿うと、代表的なものはString・List・Hash・Set・Sorted Setの5種類です。
このシリーズでは、String以外の4種類(List・Hash・Set・Sorted Set)を中心に見ていきます。
RDBMSのテーブル設計との発想の違い
MySQLでテーブル設計をする感覚のまま、Redisのデータ構造を「テーブルの代わり」として捉えようとすると混乱します。
Redisのデータ構造は、あくまで1つのキーに紐づく値の形を表しているだけで、RDBMSのようなリレーション(テーブル間の関連付け)の概念はありません。
「このキーには、どんな形のデータを持たせたいか」という視点で考えるのが、Redisのデータ構造選びのコツです。
List:順序付きのデータを扱う
List(順序を保持したまま複数の値を格納できるデータ構造)は、配列のようなイメージで使えます。
先頭・末尾への追加や取り出しが高速なのが特徴です。
# 左端(先頭)に要素を追加する
127.0.0.1:6379> LPUSH notifications:user1 "新着コメントがあります"
(integer) 1
# 右端(末尾)に要素を追加する
127.0.0.1:6379> RPUSH notifications:user1 "フォロワーが増えました"
(integer) 2
# 全要素を取得する(0番目から-1番目=最後まで)
127.0.0.1:6379> LRANGE notifications:user1 0 -1
1) "新着コメントがあります"
2) "フォロワーが増えました"
# 末尾から1件取り出して削除する
127.0.0.1:6379> RPOP notifications:user1
"フォロワーが増えました"
Listは、通知一覧・タイムライン・タスクキューのように「順序が意味を持つデータ」を扱う場面に向いています。
LPUSH/RPOPを組み合わせれば、簡易的なキュー(先入れ先出しの待ち行列)としても使えます。
Hash:1つのキーに複数フィールドを持たせる
Hash(1つのキーの中に、複数のフィールドと値のペアを持たせられるデータ構造)は、ミニチュア版のテーブル1行のようなイメージです。
# ユーザー情報をHashとして保存する
127.0.0.1:6379> HSET user:1001 name "katsu" age "32" plan "premium"
(integer) 3
# 特定のフィールドだけ取得する
127.0.0.1:6379> HGET user:1001 name
"katsu"
# 全フィールドと値を取得する
127.0.0.1:6379> HGETALL user:1001
1) "name"
2) "katsu"
3) "age"
4) "32"
5) "plan"
6) "premium"
# 特定フィールドだけ更新する
127.0.0.1:6379> HSET user:1001 age "33"
(integer) 0
Stringでuser:1001:name・user:1001:ageのようにキーを分けて保存することもできますが、キーの数が増えるほど管理が煩雑になります。
Hashを使えば、「ユーザー1人の情報」を1つのキーにまとめて管理でき、関連するデータを一括で取得・更新できます。
ユーザープロフィールや商品情報のように、「複数の属性を持つ1つのオブジェクト」を表現したい場合はHashが第一候補になります。
Set:重複のない集合を扱う
Set(重複を許さない、順序を持たない値の集合)は、数学の集合演算(和集合・積集合・差集合)が使えるのが最大の特徴です。
# ユーザー1がフォローしているユーザーIDをSetで管理
127.0.0.1:6379> SADD following:user1 "user2" "user3" "user4"
(integer) 3
# 同じ値を追加しようとしても増えない(重複しない)
127.0.0.1:6379> SADD following:user1 "user2"
(integer) 0
# 特定の値が含まれているか確認する
127.0.0.1:6379> SISMEMBER following:user1 "user3"
(integer) 1
# 2人のユーザーが共通してフォローしている相手を調べる(積集合)
127.0.0.1:6379> SADD following:user5 "user3" "user6"
(integer) 2
127.0.0.1:6379> SINTER following:user1 following:user5
1) "user3"
SINTER(積集合)を使えば「共通の友達」、SUNION(和集合)を使えば「フォロー中ユーザーの合計」のような集計が、アプリケーション側でループ処理を書かずに一発で求まります。
タグ管理・フォロー関係・重複排除が必要な一覧など、「同じものが2つ以上あってはいけないデータ」を扱う場面でSetが活躍します。
Sorted Set:スコア付きで順位を管理する
Sorted Set(各要素にスコアという数値を持たせ、そのスコア順に自動で並び替えられる集合)は、ランキング機能の実装で真価を発揮します。
# ゲームのスコアランキングをSorted Setで管理する
127.0.0.1:6379> ZADD ranking:game1 1500 "player_a"
(integer) 1
127.0.0.1:6379> ZADD ranking:game1 2800 "player_b"
(integer) 1
127.0.0.1:6379> ZADD ranking:game1 2100 "player_c"
(integer) 1
# スコアの低い順に全員を取得する
127.0.0.1:6379> ZRANGE ranking:game1 0 -1 WITHSCORES
1) "player_a"
2) "1500"
3) "player_c"
4) "2100"
5) "player_b"
6) "2800"
# スコアの高い順(降順)で上位2件を取得する
127.0.0.1:6379> ZREVRANGE ranking:game1 0 1 WITHSCORES
1) "player_b"
2) "2800"
3) "player_c"
4) "2100"
# 特定プレイヤーの現在の順位を取得する(0始まり、降順)
127.0.0.1:6379> ZREVRANK ranking:game1 "player_c"
(integer) 1
Sorted Setを使えば、「常に順位が並び替えられた状態のランキング」を、アプリケーション側でソート処理を書かずに維持できます。
ランキング機能はもちろん、「スコア」を「タイムスタンプ」に置き換えれば、時系列順のフィード(新着順一覧)の実装にも応用できます。
つまずきやすいポイント:どのデータ構造を選ぶか
❌ Before:何でもStringで無理やり表現する
# ユーザーのフォロー中リストを、カンマ区切り文字列として保存してしまう
127.0.0.1:6379> SET following:user1 "user2,user3,user4"
OK
一見動きますが、「1件だけ削除したい」「重複を防ぎたい」「含まれているか確認したい」といった操作のたびに、アプリケーション側で文字列を分解・結合するロジックが必要になります。
パフォーマンスの面でも、Redisが提供する専用コマンド(SADD/SREM/SISMEMBER)に比べて非効率です。
✅ After:目的に合ったデータ構造を選ぶ
# フォロー関係はSetで表現し、専用コマンドを使う
127.0.0.1:6379> SADD following:user1 "user2" "user3" "user4"
(integer) 3
127.0.0.1:6379> SREM following:user1 "user3"
(integer) 1
「重複を防ぎたい→Set」「順序を保ちたい→List」「複数の属性をまとめたい→Hash」「順位付けをしたい→Sorted Set」という対応関係を覚えておくと、データ構造選びで迷いにくくなります。
データ構造の使い分け早見表
| データ構造 | 特徴 | 向いている用途 |
|---|---|---|
| String | 1キーに1つの値 | シンプルなキャッシュ、カウンタ |
| List | 順序付きの複数値 | 通知一覧、タイムライン、キュー |
| Hash | 1キーに複数フィールド | ユーザー情報、商品情報 |
| Set | 重複なしの集合 | フォロー関係、タグ、重複排除 |
| Sorted Set | スコア順に並ぶ集合 | ランキング、時系列フィード |
まとめ
この記事のポイント
- RedisにはString以外にList・Hash・Set・Sorted Setという4種類のデータ構造がある
- Listは順序が意味を持つデータ、Hashは複数属性を持つオブジェクトに向いている
- Setは重複を許さない集合演算、Sorted Setはスコア順のランキングに強い
- 「何でもStringで表現する」のではなく、目的に合ったデータ構造を選ぶことが重要
次に読むべき記事
- Redisのインストールと基本コマンド(GET・SET・EXPIRE)
- Redisをキャッシュとして使う:TTL戦略の基本
- Redisをセッション管理・Pub/Subに使う
タグ: Redis, 中級者向け, データ構造