【Redis】Redisのデータ構造(List・Hash・Set・Sorted Set)の使い分け

Redis

こんにちは、かつコーチです。

前回までの記事で、Redisの基本コマンドSETGETEXPIREを扱いました。

これらは1つのキーに1つの値(文字列)を対応させる、最もシンプルな使い方でした。

ですがRedisの本当の強みは、実はここから先にあります。

Redisは1つのキーに対して、ListHashSetSorted 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:nameuser: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」という対応関係を覚えておくと、データ構造選びで迷いにくくなります。

データ構造の使い分け早見表

データ構造特徴向いている用途
String1キーに1つの値シンプルなキャッシュ、カウンタ
List順序付きの複数値通知一覧、タイムライン、キュー
Hash1キーに複数フィールドユーザー情報、商品情報
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, 中級者向け, データ構造

タイトルとURLをコピーしました