Redis 入門と演習(データ構造・キー設計・Python からの操作)

概要

Redis(レディス)の基本的なデータ構造、キー設計パターン、キャッシュ戦略を学び、Pythonと redis-py(Redis の Python クライアント)を用いた具体的な操作を演習する。2026 年時点の最新安定版は Redis 8 系であり、検索機能(Redis Search)などが本体に統合されている。本資料の内容は Redis 7 系以降で動作する。

目次

関連する外部ページ

【サイト内の関連ページ】

Redis 活用ガイド

Redis 入門

  1. Redis の基本的なデータ構造と活用例
    • Strings(文字列型):セッションID(一時的なユーザー識別子)の保存 SET session:123 "user_data"
    • Hashes(ハッシュ型):ユーザープロフィール HSET user:1000 name "田中" age "30"
    • Sets(集合型):グループメンバーシップ(所属関係)SADD team:dev "user:1000" "user:1001"
    • Sorted Sets(順序付き集合型):ランキング機能 ZADD scores 100 "user:1000"
  2. SQL ライクな操作の実現方法
    • テーブル相当:users:1000 のようなキー設計でエンティティ(実体)を表現することができる
    • インデックス:department:営業部 のようなセットで検索用インデックス(索引)を実現することができる
    • 結合操作:パイプライン(複数操作の一括実行機能)を使用して複数のハッシュを取得し、アプリケーション層で結合することができる
  3. 効率的なキー設計パターン
    • エンティティ分類:<エンティティ名>:<ID>:<属性> の形式で一意性を確保する
    • インデックス用:<インデックス名>:<値> の形式で検索効率を向上させる
    • 有効期限付きデータ:temp:<データ種別>:<ID> の形式で一時データを管理する
  4. 実用的なキャッシュ戦略
    # Cache-Aside Pattern(キャッシュサイドパターン:データの効率的な読み書き方式)
    def get_data(key):
        cache = redis.get(key)
        if cache:
            return cache
        data = db.query(key)          # メインDBから取得
        redis.setex(key, 3600, data)  # 1時間のTTL(Time To Live:生存時間)で保存
        return data
  5. データ整合性を保つための実装パターン
    # トランザクション(不可分な処理のまとまり)を使用した更新
    pipe = redis.pipeline()
    pipe.hset("users:1000", "department", "技術部")  # ユーザーの部署を更新
    pipe.srem("department:営業部", "1000")           # 旧所属からの削除
    pipe.sadd("department:技術部", "1000")           # 新所属への追加
    pipe.execute()                                   # トランザクションの実行
  6. メモリ管理のベストプラクティス
    • TTL(Time To Live:生存時間)の設定:EXPIRE users:1000 3600
    • 大きなハッシュの分割:フィールド数が多いハッシュは別キーに分割することでメモリ効率を向上させる
    • 期限切れキーの走査:SCAN コマンドを使用してキーを反復的に走査する
  7. 高度な検索機能(Redis Search)の活用

    Redis 8 以降では、検索機能(Redis Search)が本体に統合されている。

    # インデックス作成
    FT.CREATE users:index ON HASH PREFIX 1 users:
        SCHEMA name TEXT age NUMERIC department TEXT
    
    # 複合条件での検索(部署が技術部かつ年齢が20から30の範囲)
    FT.SEARCH users:index "@department:技術部 @age:[20 30]"
  8. スケーラビリティ対策
    • Redis Cluster(分散構成)の利用:データを複数のノードに分散して格納する
    • キーの分散配置:ハッシュスロットによるデータ分散を実現する
    • 読み取り専用レプリカの活用:読み取り処理の負荷分散を実現する

これらの実装パターンは、アプリケーションの要件に応じて組み合わせることで、高速で信頼性の高いシステムを構築できる。

Redis 演習

  1. 学習目標と内容
    Redis でのデータ操作方法を習得し、データ構造とインデックスの実装技術を理解する。

  2. 環境設定と準備作業
    Python から利用するため、redis-py を導入する。

    pip install -U --no-user redis
  3. Redis コマンドラインの起動と基本操作
    起動手順:redis-cli コマンドを実行することで Redis のコマンドラインインタフェースを起動する。

    ヘルプ機能の利用方法:help コマンドを実行すると、利用可能なコマンドの案内が表示される。

    終了手順:exit コマンドを実行することで、セッションを終了する。

  4. Python からの接続

    Python から Redis を操作する前に、接続オブジェクトを作成する。decode_responses=True を指定すると、応答がバイト列ではなく文字列で返る(初学者はこの指定を付けておくと扱いやすい)。以降のコードは、この redis オブジェクトが作成済みであることを前提とする。

    import redis
    from datetime import datetime
    
    redis = redis.Redis(host='localhost', port=6379, decode_responses=True)
  5. データの挿入手順

    ここでは order(注文記録)データを登録する。Hash(ハッシュ型)を使用して構造化データを格納する。

    【説明】Hash データモデルでレコードを保存する。hsetmapping 引数でフィールドと値のペアを一括設定する(HMSET は Redis 4.0.0 以降で非推奨のため、HSET を使用する)。作成時刻には ISO 8601 形式のタイムスタンプを用いる。データの一貫性はアプリケーション層で担保する。

    【補足:基本的なデータ構造】

    • Strings(文字列型):セッションID の保存 SET session:123 "user_data"
    • Hashes(ハッシュ型):ユーザープロフィール HSET user:1000 name "田中" age "30"
    • Sets(集合型):グループメンバーシップ SADD team:dev "user:1000" "user:1001"
    • Sorted Sets(順序付き集合型):ランキング機能 ZADD scores 100 "user:1000"
    # データ挿入
    redis.hset('order:1', mapping={
        'year': 2022,
        'month': 10,
        'day': 26,
        'customer_name': 'kaneko',
        'product_name': 'orange A',
        'unit_price': 1.2,
        'qty': 10,
        'created_at': datetime.now().isoformat(),
        'updated_at': '',
    })
    
    redis.hset('order:2', mapping={
        'year': 2022,
        'month': 10,
        'day': 26,
        'customer_name': 'miyamoto',
        'product_name': 'Apple M',
        'unit_price': 2.5,
        'qty': 2,
        'created_at': datetime.now().isoformat(),
        'updated_at': '',
    })
    
    redis.hset('order:3', mapping={
        'year': 2022,
        'month': 10,
        'day': 27,
        'customer_name': 'kaneko',
        'product_name': 'orange B',
        'unit_price': 1.2,
        'qty': 8,
        'created_at': datetime.now().isoformat(),
        'updated_at': '',
    })
    
    redis.hset('order:4', mapping={
        'year': 2022,
        'month': 10,
        'day': 28,
        'customer_name': 'miyamoto',
        'product_name': 'Apple L',
        'unit_price': 3.0,
        'qty': 1,
        'created_at': datetime.now().isoformat(),
        'updated_at': '',
    })
  6. 問い合わせの実行と結果確認

    【説明】scan_iterSCAN コマンドをラップしたイテレータ)で効率的に反復し、hgetall でハッシュ全体を取得する。パターンマッチングによるキーの走査と条件フィルタリングを組み合わせることで、目的のデータを検索する。SCANカーソルベースの反復子であり、大量のキーがあってもサーバをブロックしにくい。

    【補足:キー設計パターン】

    • エンティティ分類:<エンティティ名>:<ID>:<属性>
    • インデックス用:<インデックス名>:<値>
    • 有効期限付きデータ:temp:<データ種別>:<ID>
    # 全データの取得
    for key in redis.scan_iter("order:*"):
        print(redis.hgetall(key))
    
    # 特定の日付のデータ取得
    for key in redis.scan_iter("order:*"):
        order = redis.hgetall(key)
        if order['day'] == '26':
            print(order)
    
    # 特定の顧客のデータ取得
    for key in redis.scan_iter("order:*"):
        order = redis.hgetall(key)
        if order['customer_name'] == 'kaneko':
            print(order)
    
    # 特定の単価以上のデータ取得
    for key in redis.scan_iter("order:*"):
        order = redis.hgetall(key)
        if float(order['unit_price']) > 2:
            print(order)
  7. データ更新の実践的手法

    【説明】hset で複数フィールドを更新する。更新時刻をあわせて記録し、フィールド単位の部分更新を行う。必要に応じてトランザクション制御と組み合わせることができる。

    【補足:データ整合性を保つための実装パターン】

    # トランザクションを使用した更新
    pipe = redis.pipeline()
    pipe.hset("users:1000", "department", "技術部")
    pipe.srem("department:営業部", "1000")
    pipe.sadd("department:技術部", "1000")
    pipe.execute()
    # 数量と更新時刻の更新
    redis.hset('order:1', mapping={
        'qty': 12,
        'updated_at': datetime.now().isoformat(),
    })
  8. データ削除の方法

    【説明】delete コマンドでキー単位に削除する。関連するインデックスの整合性を保つため、トランザクション処理と組み合わせて、セカンダリインデックスも同時に更新する。

    # 特定のデータの削除
    redis.delete('order:2')
  9. インデックスの活用

    【説明】Set(集合型)を使ってセカンダリインデックス(副次的な索引)を実装する。sadd でメンバーを追加し、smembers で逆引き検索を行う。キー設計パターンに基づき、目的のデータへ効率的にアクセスできる。

    # 顧客名によるインデックス作成
    redis.sadd('customer:kaneko', 'order:1')
    redis.sadd('customer:kaneko', 'order:3')
    redis.sadd('customer:miyamoto', 'order:2')
    redis.sadd('customer:miyamoto', 'order:4')
    
    # インデックスを使用した検索
    customer_orders = redis.smembers('customer:kaneko')
    for order_id in customer_orders:
        print(redis.hgetall(order_id))
    
    # 日付によるインデックス作成
    redis.sadd('day:26', 'order:1')
    redis.sadd('day:26', 'order:2')
    redis.sadd('day:27', 'order:3')
    redis.sadd('day:28', 'order:4')
    
    # 日付インデックスを使用した検索
    day_orders = redis.smembers('day:26')
    for order_id in day_orders:
        print(redis.hgetall(order_id))
  10. データのバックアップとリストア

    【説明】Redis の永続化機能を活用する。SAVE コマンドはスナップショットを同期的に作成し、BGSAVE(Background SAVE)はバックグラウンドで非同期に作成する。SAVE は処理が終わるまでサーバの他の処理をブロックするため、通常は BGSAVE を用いる。AOF(Append Only File:追記専用ファイル)を有効にすると、書き込み操作のログを永続化できる。

    【補足:Cache-Aside Pattern 実装例】

    def get_data(key):
        cache = redis.get(key)
        if cache:
            return cache
        data = db.query(key)          # メインDBから取得
        redis.setex(key, 3600, data)  # 1時間のTTLで保存
        return data

    【補足:Redis Search 活用例】

    # インデックス作成
    FT.CREATE users:index ON HASH PREFIX 1 users:
        SCHEMA name TEXT age NUMERIC department TEXT
    
    # 複合条件での検索
    FT.SEARCH users:index "@department:技術部 @age:[20 30]"

    Redis では SAVE コマンドでデータベースのスナップショットを作成できる。BGSAVE を使用すると、バックグラウンドでスナップショットを作成する。

    注意点:

    • Redis では一貫性制約はアプリケーション層で実装する必要がある
    • インデックスは必要に応じて明示的に作成・維持する必要がある
    • トランザクションは MULTI/EXEC を使用して実現する
    • メモリ使用量を考慮したキー設計が重要である
    • バックアップ戦略は、アプリケーションの要件に応じて選択する

これらの操作は、アプリケーションのニーズに応じて組み合わせることで、効率的なデータ管理が可能となる。