Redis Sets(パート2)

このレッスンでは、セットの積集合、和集合、差集合の操作について解説します。これらはセットの最も強力な機能です。

集合演算の概要

Redisは3つの集合演算をサポートしています:

操作 説明 コマンド
積集合 すべてのセットに存在する要素 SINTER
和集合 いずれかのセットに存在する要素 SUNION
差集合 最初のセットにあり、他にはない要素 SDIFF
Set A: {1, 2, 3, 4}
Set B: {3, 4, 5, 6}

積集合 A∩B: {3, 4}
和集合 A∪B: {1, 2, 3, 4, 5, 6}
差集合 A-B:  {1, 2}

SINTER:積集合

積集合は、指定されたすべてのセットに存在する要素を返します。

基本的な使用方法

REDIS
# 2つのセットを作成
SADD set1 "a" "b" "c" "d"
SADD set2 "c" "d" "e" "f"

# 積集合を計算
SINTER set1 set2
1) "c"
2) "d"

複数セットの積集合

REDIS
SADD set3 "c" "d" "g"

# 3つのセットの積集合
SINTER set1 set2 set3
1) "c"
2) "d"

存在しないセット

REDIS
# セットが存在しない場合、空として扱われる
SINTER set1 notexist
(empty array)

SINTERCARD:積集合のカーディナリティ(Redis 7.0+)

REDIS
SINTERCARD 2 set1 set2
(integer) 2  # 積集合には2つの要素がある

SUNION:和集合

和集合は、いずれかのセットに存在する要素を返します。

基本的な使用方法

REDIS
SADD set1 "a" "b" "c"
SADD set2 "c" "d" "e"

# 和集合を計算
SUNION set1 set2
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"

複数セットの和集合

REDIS
SADD set3 "e" "f" "g"

# 3つのセットの和集合
SUNION set1 set2 set3
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
7) "g"

SDIFF:差集合

差集合は、最初のセットにあり、他のセットのいずれにもない要素を返します。

基本的な使用方法

REDIS
SADD set1 "a" "b" "c" "d"
SADD set2 "c" "d" "e" "f"

# set1 - set2
SDIFF set1 set2
1) "a"
2) "b"

複数セットの差集合

REDIS
SADD set3 "a" "g"

# set1 - set2 - set3
SDIFF set1 set2 set3
1) "b"  # aはset3にあるので除外
⚠️ 補足: 差集合は可換ではありません。SDIFF A BSDIFF B A です。

集合演算結果の保存

SINTERSTORE:積集合結果を保存

REDIS
SADD set1 "a" "b" "c" "d"
SADD set2 "c" "d" "e" "f"

# 積集合結果を新しいセットに保存
SINTERSTORE result set1 set2
(integer) 2  # 結果のセットには2つの要素

SMEMBERS result
1) "c"
2) "d"

SUNIONSTORE:和集合結果を保存

REDIS
SUNIONSTORE result set1 set2
(integer) 6

SMEMBERS result
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"

SDIFFSTORE:差集合結果を保存

REDIS
SDIFFSTORE result set1 set2
(integer) 2

SMEMBERS result
1) "a"
2) "b"
💡 使用例: *STOREコマンドは結果を新しいセットに保存し、後続の操作やキャッシュに使用できます。

集合演算のユースケース

ユースケース1:相互フォロー

REDIS
# ユーザー1の友達
SADD user:1:friends "user:2" "user:3" "user:4" "user:5"

# ユーザー2の友達
SADD user:2:friends "user:3" "user:4" "user:6" "user:7"

# 相互フォロー
SINTER user:1:friends user:2:friends
1) "user:3"
2) "user:4"

ユースケース2:知り合いかもしれません

REDIS
# 自分の友達
SADD user:1:friends "user:2" "user:3"

# 友達の友達
SADD user:2:friends "user:3" "user:4" "user:5"
SADD user:3:friends "user:4" "user:6"

# 友達の友達の和集合
SUNIONSTORE friends:friends user:2:friends user:3:friends

# おすすめの友達 = 友達の友達 - 自分の友達 - 自分
SADD me "user:1"
SUNIONSTORE exclude me user:1:friends
SDIFF friends:friends exclude

ユースケース3:記事のおすすめ

REDIS
# ユーザーがいいねしたタグ
SADD user:1:liked:tags "redis" "database" "cache"

# 記事のタグ
SADD article:123:tags "redis" "nosql"
SADD article:456:tags "mysql" "database"
SADD article:789:tags "redis" "cache" "performance"

# ユーザーの興味に合う記事を推薦(タグの積集合)
SINTER user:1:liked:tags article:123:tags
1) "redis"  # article:123は1つのタグに一致

SINTER user:1:liked:tags article:789:tags
1) "redis"
2) "cache"  # article:789は2つのタグに一致 — より強い推薦

ユースケース4:商品フィルタリング

REDIS
# 商品カテゴリ
SADD category:phone "product:1" "product:2" "product:3"
SADD category:laptop "product:4" "product:5"

# 商品ブランド
SADD brand:apple "product:1" "product:4"
SADD brand:samsung "product:2" "product:5"

# フィルター:電話 + Appleブランド
SINTER category:phone brand:apple
1) "product:1"

ユースケース5:権限チェック

REDIS
# ユーザーの権限
SADD user:1:permissions "read" "write" "delete"

# リソースに必要な権限
SADD resource:123:required "read" "write"

# ユーザーが必要な権限をすべて持っているか確認
SINTER user:1:permissions resource:123:required
1) "read"
2) "write"

# 結果の数が必要な権限の数と等しければ、ユーザーは承認されている

ユースケース6:タグの集約

REDIS
# 記事のタグ
SADD article:1:tags "redis" "database"
SADD article:2:tags "redis" "cache"
SADD article:3:tags "mysql" "database"

# すべてのタグ
SUNIONSTORE all:tags article:1:tags article:2:tags article:3:tags
SMEMBERS all:tags
1) "redis"
2) "database"
3) "cache"
4) "mysql"

集合演算のパフォーマンス

時間複雑度

コマンド 時間複雑度 説明
SINTER O(N*M) N = 最小のセットサイズ、M = セット数
SUNION O(N) N = すべてのセットの合計要素数
SDIFF O(N) N = すべてのセットの合計要素数
⚠️ パフォーマンスのヒント: 集合演算はすべての要素を走査します。大きなセットは低速になる可能性があります。推奨事項:

  • カウントのみが必要な場合はSINTERの代わりにSINTERCARDを使用
  • 結果をキャッシュ(*STOREコマンドを使用)
  • セットサイズを制限

SSCAN:大きなセットの反復処理

大きなセットの場合は、SSCANを使用してバッチで要素を反復処理します。

REDIS
# 大きなセットを作成
SADD large:set value1 value2 ... value10000

# 反復処理を開始
SSCAN large:set 0
1) "127"          # 次の反復処理のカーソル
2) 1) "value1"
   2) "value2"
   ...

# 反復処理を続行
SSCAN large:set 127
1) "0"            # カーソルが0の場合は反復処理完了
2) ...

パターンマッチ

REDIS
# パターンに一致する要素のみを取得
SSCAN large:set 0 MATCH user:*

Setの制限

1. 順序なし

REDIS
# 特定の順序で要素を取得できない
SMEMBERS myset  # 返される順序は未定義

順序付きデータにはSorted Setを使用してください。

2. 文字列のみ

REDIS
# Setの要素は文字列のみ
SADD myset 123  # 文字列"123"として保存

3. ネスト構造なし

Setはネストされたセットやオブジェクトをサポートしていません。

❓ よくある質問

Q SINTERとSINTERSTOREの違いは何ですか?
A SINTERは積集合の結果を返します。SINTERSTOREは結果を新しいセットに保存します。
Q 差集合は可換ですか?
A いいえ。SDIFF A BSDIFF B A です。順序が重要です。
Q 集合演算は元のセットを変更しますか? *A:いいえ。SINTER/SUNION/SDIFFは読み取り専用です。STOREコマンドは宛先セットを上書きします。

Q:要素を取得せずに積集合のサイズをカウントするにはどうすればよいですか?

A SINTERCARD(Redis 7.0+)を使用するか、SINTERの結果をカウントします。
Q 集合演算の速度は?
A セットサイズに依存します。大きなセットの演算は低速になる可能性があるため、セットサイズを制限するかキャッシュを使用してください。

📖 まとめ

  • SINTER:すべてのセットに存在する要素
  • SUNION:いずれかのセットに存在する要素
  • SDIFF:最初のセットにあり、他にはない要素
  • *STOREコマンドで演算結果を新しいセットに保存
  • 集合演算の用途:相互フォロー、おすすめ、フィルタリング、権限チェック
  • パフォーマンス:大きなセットの演算は低速 — キャッシュするかサイズを制限
  • SSCANで大きなセットを反復処理し、ブロックを回避

📝 練習問題

  1. 相互フォロー: 2人のユーザーの友達リストを作成し、相互フォローを見つけましょう
  2. レコメンデーションシステム: ユーザーの興味タグに基づいて、シンプルな記事推薦システムを実装しましょう
  3. 商品フィルタリング: セットを使用して、複数条件(カテゴリ+ブランド)の商品フィルタリングを実装しましょう
  4. 権限チェック: 集合演算を使用して、ユーザーがリソースに必要なすべての権限を持っているか確認しましょう

次のレッスン

次のレッスンでは、Redis Sorted Sets(パート1)について学びます。基本的なソート済みセット操作を解説します。

100%