T-CREATOR

Redis キーネーミング規約チートシート:階層・区切り・TTL ルール

Redis キーネーミング規約チートシート:階層・区切り・TTL ルール

Redis を使ったアプリケーション開発では、キーの命名規則が非常に重要です。適切な命名規則がないと、データの管理が煩雑になり、バグの温床となってしまいます。本記事では、Redis のキーネーミングにおける階層構造、区切り文字の選択、そして TTL(Time To Live)の設定ルールについて、実践的なチートシートとして詳しく解説いたします。

これらのルールを理解し実践することで、保守性の高い Redis 運用が可能になるでしょう。

早見表:Redis キーネーミング規約の基本ルール

#項目推奨ルール理由
1区切り文字コロン :user:1000:profileRedis 公式推奨、可読性が高い
2階層の深さ2〜4 階層app:session:user:abc123管理しやすく検索効率が良い
3命名規則スネークケースuser_session統一性、区切り文字との区別
4名前空間アプリ名を先頭にmyapp:cache:data複数アプリでの衝突回避
5TTL の設定用途別に明確化cache:* → 3600 秒メモリ効率、データ整合性
6キー長50 文字以内推奨prod:usr:1234:prfメモリ使用量削減、処理速度向上
7環境分離プレフィックスで分離prod:, dev:, test:環境間のデータ混在防止
8バージョン管理バージョン番号を含むapi:v2:user:cacheスキーマ変更時の移行容易化

キーネーミングパターン早見表

#データ種別パターンTTL 目安用途
1セッションsession:{user_id}:{token}1800〜3600 秒ユーザーセッション管理
2キャッシュcache:{resource}:{id}300〜3600 秒API レスポンス、DB クエリ結果
3一時データtemp:{process}:{uuid}60〜300 秒処理中の一時保存
4カウンターcounter:{metric}:{date}86400 秒アクセス数、いいね数
5ランキングrank:{category}:{period}3600〜86400 秒リーダーボード
6ロックlock:{resource}:{id}10〜30 秒排他制御
7キューqueue:{job_type}:{priority}なしジョブキュー
8設定config:{service}:{key}なしアプリケーション設定

背景

Redis は高速な Key-Value ストアとして広く利用されていますが、RDB のようなスキーマやテーブル構造を持ちません。すべてのデータはキーと値のペアとして保存されるため、キーの命名方法がデータ管理の要となります。

Redis のキーは単なる文字列ですが、適切な命名規則を設けることで、以下のような利点が得られます。

まず、開発チーム内での認識統一が図れます。誰が見てもキーの用途や内容が理解できるため、引き継ぎやレビューがスムーズになるでしょう。

次に、運用時のトラブルシューティングが容易になります。キーのパターンから問題箇所を特定しやすくなり、障害対応時間を大幅に短縮できますね。

以下の図は、Redis における基本的なデータ構造とキーの関係を示しています。

mermaidflowchart TB
  app["アプリケーション"] -->|書き込み| redis[("Redis<br/>Key-Value Store")]
  redis -->|読み取り| app

  subgraph keys["キー空間"]
    k1["user:1000:profile"]
    k2["session:abc123"]
    k3["cache:api:posts"]
    k4["temp:upload:xyz"]
  end

  redis -.->|管理| keys
  keys -.->|命名規則| rules["階層・区切り・TTL"]

図で理解できる要点:

  • Redis はすべてのデータをキーで管理します
  • キー空間には様々な種類のキーが混在します
  • 命名規則によってキーを体系的に管理できます

さらに、メモリ使用量の最適化にもつながります。キーの長さを適切に保つことで、大量のデータを扱う際のメモリ効率が向上します。

課題

Redis を使い始めた開発者が直面する典型的な課題として、以下のようなものがあります。

キーの乱立と管理の困難さが第一の課題です。命名規則がないと、user1user_data_1000userData:1000 など、バラバラなキー名が生まれてしまいます。これではパターンマッチングでのキー検索が困難になるでしょう。

名前空間の衝突も深刻な問題です。複数のアプリケーションやマイクロサービスが同じ Redis インスタンスを共有する場合、キーが重複してデータが上書きされるリスクがあります。

メモリリークの発生は特に注意が必要です。TTL を設定せずにキャッシュデータを保存し続けると、メモリが枯渇してしまいます。

以下の図は、キーネーミング規約がない場合の問題を可視化しています。

mermaidflowchart LR
  subgraph problem["問題のあるキー管理"]
    direction TB
    k1["user1"]
    k2["user_1000"]
    k3["userData:1000"]
    k4["1000_user_profile"]
    k5["cache_user_1000"]
  end

  problem -->|結果| issues["・検索困難<br/>・保守性低下<br/>・衝突リスク"]

  subgraph good["規約に従ったキー管理"]
    direction TB
    g1["myapp:user:1000:profile"]
    g2["myapp:user:1000:session"]
    g3["myapp:cache:user:1000"]
  end

  good -->|結果| benefits["・一貫性<br/>・検索容易<br/>・保守性向上"]

図で理解できる要点:

  • 規約がないと同じデータでもキー名がバラバラになります
  • 統一された規約により管理が容易になります

環境ごとのデータ分離も課題の一つです。開発環境、ステージング環境、本番環境で同じ Redis を使う場合、キーの区別ができないとデータが混在してしまいますね。

スキーマ変更への対応も考慮すべきポイントです。アプリケーションの仕様変更に伴い、キーの構造を変更する必要が生じた際、バージョン管理がされていないと移行が困難になります。

これらの課題を解決するには、体系的なキーネーミング規約の確立が不可欠です。

解決策

Redis のキーネーミング規約は、3 つの主要な要素から構成されます。それは「階層構造」「区切り文字」「TTL ルール」です。

階層構造の設計

キーは階層的に設計することで、論理的なグループ化が可能になります。一般的な階層構造は以下のようになります。

typescript// 基本パターン
// {namespace}:{entity}:{id}:{attribute}

// 実例
'myapp:user:1000:profile';
'myapp:user:1000:settings';
'myapp:session:abc123';

階層は 2〜4 階層 を目安とすることをお勧めします。深すぎると冗長になり、浅すぎると区別が困難になるためです。

各階層の役割を明確にしましょう。

typescript// 推奨される階層構成

// 第1階層:名前空間(アプリ名や環境)
const namespace = 'myapp';

// 第2階層:エンティティ種別
const entity = 'user';

// 第3階層:識別子(ID)
const id = '1000';

// 第4階層:属性
const attribute = 'profile';

// 結合
const key = `${namespace}:${entity}:${id}:${attribute}`;
// => "myapp:user:1000:profile"

環境分離のために、環境プレフィックスを追加する方法もあります。

typescript// 環境別の名前空間

// 本番環境
const prodKey = 'prod:myapp:user:1000:profile';

// 開発環境
const devKey = 'dev:myapp:user:1000:profile';

// テスト環境
const testKey = 'test:myapp:user:1000:profile';

区切り文字の選択

Redis 公式ドキュメントでは コロン : の使用 が推奨されています。これにはいくつかの理由があります。

typescript// 各種区切り文字の比較

// ✓ 推奨:コロン
const good1 = 'user:1000:profile';

// △ 可能だが推奨されない:ハイフン
const ok1 = 'user-1000-profile';

// △ 可能だが推奨されない:ドット
const ok2 = 'user.1000.profile';

// × 非推奨:スラッシュ(URL と混同)
const bad1 = 'user/1000/profile';

// × 非推奨:アンダースコア(単語区切りと混同)
const bad2 = 'user_1000_profile';

コロンを使うことで、Redis の SCAN コマンドでパターンマッチングがしやすくなります。

javascript// Redis でのパターンマッチング例

// 特定のユーザーに関するすべてのキーを検索
// SCAN 0 MATCH user:1000:*

// 特定エンティティのすべてのキーを検索
// SCAN 0 MATCH myapp:session:*

単語内の区切りにはスネークケースを使用します。

typescript// 複合語の扱い

// ✓ 推奨
const good = 'user:1000:login_history';

// × 非推奨(キャメルケース)
const bad = 'user:1000:loginHistory';

TTL ルールの設定

TTL(Time To Live)は、キーの用途に応じて適切に設定する必要があります。以下はデータ種別ごとの TTL 設定ガイドラインです。

typescript// セッションデータ:30分〜1時間
const sessionTTL = 1800; // 30分(秒単位)

// キャッシュデータ:5分〜1時間
const cacheTTL = 3600; // 1時間

// 一時データ:1分〜5分
const tempTTL = 300; // 5分

// 日次集計データ:24時間〜48時間
const dailyTTL = 86400; // 24時間

TTL の設定方法は、Redis コマンドまたはクライアントライブラリを使用します。

javascript// Node.js(ioredis)での TTL 設定例

import Redis from 'ioredis';
const redis = new Redis();

// SET と同時に TTL を設定
async function setCacheWithTTL(key, value, ttl) {
  await redis.set(key, value, 'EX', ttl);
  console.log(`キー "${key}" を TTL ${ttl} 秒で設定`);
}

// 使用例
await setCacheWithTTL(
  'cache:api:posts',
  JSON.stringify(posts),
  3600
);

既存のキーに TTL を設定することもできます。

javascript// 既存キーへの TTL 設定

async function setExpire(key, ttl) {
  const result = await redis.expire(key, ttl);

  if (result === 1) {
    console.log(
      `キー "${key}" に ${ttl} 秒の TTL を設定しました`
    );
  } else {
    console.log(`キー "${key}" が存在しません`);
  }
}

// 使用例
await setExpire('session:abc123', 1800);

TTL の確認も重要です。

javascript// TTL の確認

async function checkTTL(key) {
  const ttl = await redis.ttl(key);

  if (ttl === -1) {
    console.log(
      `キー "${key}" には TTL が設定されていません`
    );
  } else if (ttl === -2) {
    console.log(`キー "${key}" は存在しません`);
  } else {
    console.log(`キー "${key}" の残り時間: ${ttl} 秒`);
  }

  return ttl;
}

// 使用例
await checkTTL('cache:api:posts');

以下は、TTL 設定のフローを示した図です。

mermaidstateDiagram-v2
  [*] --> CheckKeyType: キー作成

  CheckKeyType --> SetSessionTTL: セッションデータ
  CheckKeyType --> SetCacheTTL: キャッシュデータ
  CheckKeyType --> SetTempTTL: 一時データ
  CheckKeyType --> NoTTL: 永続データ

  SetSessionTTL --> Active: 1800秒
  SetCacheTTL --> Active: 3600秒
  SetTempTTL --> Active: 300秒
  NoTTL --> Persistent: TTL なし

  Active --> Expired: 時間経過
  Expired --> [*]: 自動削除

  Persistent --> ManualDelete: 手動削除
  ManualDelete --> [*]

図で理解できる要点:

  • データ種別により適切な TTL を設定します
  • TTL ありのキーは自動的に削除されます
  • 永続データは手動削除が必要です

具体例

実際のアプリケーション開発で使用するキーネーミングの具体例を見ていきましょう。

ユーザー管理システムの例

ユーザー情報を扱う際のキー設計です。

typescript// ユーザープロフィール情報
const userProfileKey = (userId: number) =>
  `myapp:user:${userId}:profile`;

// 使用例
// "myapp:user:1000:profile"
typescript// ユーザー設定情報
const userSettingsKey = (userId: number) =>
  `myapp:user:${userId}:settings`;

// "myapp:user:1000:settings"
typescript// ユーザーのセッション情報
const userSessionKey = (
  userId: number,
  sessionId: string
) => `myapp:session:${userId}:${sessionId}`;

// "myapp:session:1000:abc123def456"

実装例を見てみましょう。

typescript// TypeScript での実装例

interface UserProfile {
  name: string;
  email: string;
  created_at: string;
}

class UserService {
  private redis: Redis;
  private readonly PROFILE_TTL = 3600; // 1時間

  constructor(redis: Redis) {
    this.redis = redis;
  }

  // プロフィール情報の保存
  async saveProfile(
    userId: number,
    profile: UserProfile
  ): Promise<void> {
    const key = `myapp:user:${userId}:profile`;
    await this.redis.set(
      key,
      JSON.stringify(profile),
      'EX',
      this.PROFILE_TTL
    );
  }
}
typescript  // プロフィール情報の取得
  async getProfile(userId: number): Promise<UserProfile | null> {
    const key = `myapp:user:${userId}:profile`;
    const data = await this.redis.get(key);

    if (!data) {
      return null;
    }

    return JSON.parse(data) as UserProfile;
  }

キャッシュシステムの例

API レスポンスをキャッシュする際のキー設計です。

typescript// API レスポンスキャッシュのキー生成

const apiCacheKey = (
  endpoint: string,
  params?: Record<string, any>
) => {
  const baseKey = `myapp:cache:api:${endpoint.replace(
    /\//g,
    ':'
  )}`;

  if (!params || Object.keys(params).length === 0) {
    return baseKey;
  }

  // パラメータをソートして一貫したキーを生成
  const sortedParams = Object.keys(params)
    .sort()
    .map((key) => `${key}:${params[key]}`)
    .join(':');

  return `${baseKey}:${sortedParams}`;
};

使用例を見ていきましょう。

typescript// 使用例

// "/api/posts" のキャッシュ
const key1 = apiCacheKey('/api/posts');
// => "myapp:cache:api:api:posts"

// "/api/posts?page=1&limit=10" のキャッシュ
const key2 = apiCacheKey('/api/posts', {
  page: 1,
  limit: 10,
});
// => "myapp:cache:api:api:posts:limit:10:page:1"

実装例です。

typescript// キャッシュ機能の実装

class CacheService {
  private redis: Redis;
  private readonly DEFAULT_TTL = 300; // 5分

  constructor(redis: Redis) {
    this.redis = redis;
  }

  async cache<T>(
    key: string,
    fetcher: () => Promise<T>,
    ttl: number = this.DEFAULT_TTL
  ): Promise<T> {
    // キャッシュの確認
    const cached = await this.redis.get(key);

    if (cached) {
      console.log(`キャッシュヒット: ${key}`);
      return JSON.parse(cached) as T;
    }

    // データの取得
    console.log(`キャッシュミス: ${key}`);
    const data = await fetcher();

    // キャッシュへの保存
    await this.redis.set(
      key,
      JSON.stringify(data),
      'EX',
      ttl
    );

    return data;
  }
}

ランキングシステムの例

Sorted Set を使ったランキングのキー設計です。

typescript// ランキングキーの生成

const rankingKey = (category: string, period: string) =>
  `myapp:rank:${category}:${period}`;

// 使用例
const dailyUserRanking = rankingKey('users', '2025-01-15');
// => "myapp:rank:users:2025-01-15"

const weeklyPostRanking = rankingKey('posts', '2025-W03');
// => "myapp:rank:posts:2025-W03"

実装例を見てみましょう。

typescript// ランキング機能の実装

class RankingService {
  private redis: Redis;
  private readonly DAILY_TTL = 86400 * 2; // 2日間

  constructor(redis: Redis) {
    this.redis = redis;
  }

  // スコアの追加
  async addScore(
    category: string,
    period: string,
    memberId: string,
    score: number
  ): Promise<void> {
    const key = rankingKey(category, period);
    await this.redis.zincrby(key, score, memberId);

    // TTL の設定(初回のみ)
    await this.redis.expire(key, this.DAILY_TTL);
  }
}
typescript  // トップランキングの取得
  async getTopRanking(
    category: string,
    period: string,
    limit: number = 10
  ): Promise<Array<{ member: string; score: number }>> {
    const key = rankingKey(category, period);

    // スコアの高い順に取得
    const results = await this.redis.zrevrange(
      key,
      0,
      limit - 1,
      'WITHSCORES'
    );

    // 結果の整形
    const ranking = [];
    for (let i = 0; i < results.length; i += 2) {
      ranking.push({
        member: results[i],
        score: parseFloat(results[i + 1])
      });
    }

    return ranking;
  }

ロック機構の例

分散ロックを実装する際のキー設計です。

typescript// ロックキーの生成

const lockKey = (resource: string, identifier: string) =>
  `myapp:lock:${resource}:${identifier}`;

// 使用例
const orderLock = lockKey('order', '12345');
// => "myapp:lock:order:12345"

実装例です。

typescript// 分散ロックの実装

class LockService {
  private redis: Redis;
  private readonly LOCK_TTL = 30; // 30秒

  constructor(redis: Redis) {
    this.redis = redis;
  }

  // ロックの取得
  async acquireLock(
    resource: string,
    identifier: string,
    ttl: number = this.LOCK_TTL
  ): Promise<boolean> {
    const key = lockKey(resource, identifier);
    const lockId = this.generateLockId();

    // NX(存在しない場合のみ設定)オプションでロックを取得
    const result = await this.redis.set(
      key,
      lockId,
      'EX',
      ttl,
      'NX'
    );

    return result === 'OK';
  }

  private generateLockId(): string {
    return `${Date.now()}-${Math.random()
      .toString(36)
      .substr(2, 9)}`;
  }
}
typescript  // ロックの解放
  async releaseLock(resource: string, identifier: string): Promise<void> {
    const key = lockKey(resource, identifier);
    await this.redis.del(key);
  }

  // ロック付き処理の実行
  async executeWithLock<T>(
    resource: string,
    identifier: string,
    callback: () => Promise<T>
  ): Promise<T | null> {
    const acquired = await this.acquireLock(resource, identifier);

    if (!acquired) {
      console.log(`ロック取得失敗: ${resource}:${identifier}`);
      return null;
    }

    try {
      return await callback();
    } finally {
      await this.releaseLock(resource, identifier);
    }
  }

バージョン管理の例

スキーマ変更に対応するためのバージョン管理です。

typescript// バージョン付きキーの生成

const versionedKey = (
  namespace: string,
  entity: string,
  id: string | number,
  version: string = 'v1'
) => `${namespace}:${version}:${entity}:${id}`;

// 使用例
const v1Key = versionedKey('myapp', 'user', 1000, 'v1');
// => "myapp:v1:user:1000"

const v2Key = versionedKey('myapp', 'user', 1000, 'v2');
// => "myapp:v2:user:1000"

バージョン移行の実装例です。

typescript// バージョン移行の実装

class MigrationService {
  private redis: Redis;

  constructor(redis: Redis) {
    this.redis = redis;
  }

  // v1 から v2 へのデータ移行
  async migrateUserData(userId: number): Promise<void> {
    const v1Key = versionedKey(
      'myapp',
      'user',
      userId,
      'v1'
    );
    const v2Key = versionedKey(
      'myapp',
      'user',
      userId,
      'v2'
    );

    // v1 データの取得
    const v1Data = await this.redis.get(v1Key);

    if (!v1Data) {
      console.log(`v1 データが存在しません: ${v1Key}`);
      return;
    }

    // データの変換
    const oldData = JSON.parse(v1Data);
    const newData = this.transformV1ToV2(oldData);

    // v2 データの保存
    await this.redis.set(v2Key, JSON.stringify(newData));

    console.log(`移行完了: ${userId}`);
  }
}
typescript  private transformV1ToV2(v1Data: any): any {
    // v1 から v2 へのデータ構造変換ロジック
    return {
      ...v1Data,
      schema_version: 2,
      migrated_at: new Date().toISOString()
    };
  }

以下は、実際のアプリケーションにおけるキー管理の全体像を示した図です。

mermaidflowchart TB
  app["アプリケーション"] --> decision{"データ種別"}

  decision -->|ユーザーデータ| user["user:{id}:*<br/>TTL: 3600秒"]
  decision -->|セッション| session["session:{token}<br/>TTL: 1800秒"]
  decision -->|キャッシュ| cache["cache:*<br/>TTL: 300秒"]
  decision -->|ランキング| rank["rank:{category}:{date}<br/>TTL: 86400秒"]
  decision -->|ロック| lock["lock:{resource}<br/>TTL: 30秒"]

  user --> redis[("Redis")]
  session --> redis
  cache --> redis
  rank --> redis
  lock --> redis

  redis --> monitor["監視・管理"]
  monitor --> cleanup["期限切れ自動削除"]

図で理解できる要点:

  • データ種別ごとに適切なキーパターンと TTL を設定します
  • Redis が自動的に期限切れデータを削除します

まとめ

Redis のキーネーミング規約について、階層構造、区切り文字、TTL ルールの 3 つの観点から解説してきました。

適切なキーネーミング規約を確立することで、以下のメリットが得られます。

保守性の向上が最も大きな利点でしょう。統一されたルールにより、チーム全体でコードの可読性が高まり、引き継ぎやレビューがスムーズになります。

運用の効率化も実現できます。パターンマッチングによるキー検索が容易になり、トラブルシューティングの時間を大幅に短縮できますね。

メモリ管理の最適化にもつながります。適切な TTL 設定により、不要なデータが自動的に削除され、メモリリークを防止できます。

スケーラビリティの確保も重要です。バージョン管理やネームスペース分離により、アプリケーションの成長に柔軟に対応できるでしょう。

実践のポイントとしては、まずプロジェクト開始時に命名規約を文書化し、チーム全体で共有することをお勧めします。

次に、キー生成用のヘルパー関数を作成し、手動でのキー文字列作成を避けましょう。これによりタイポや不整合を防げます。

さらに、定期的にキーの使用状況を監視し、TTL が適切に設定されているか確認することも大切です。

最後に、新しいデータ種別を追加する際は、既存の命名規則との整合性を保つよう心がけてください。

Redis のキーネーミングは、アプリケーション全体のアーキテクチャに影響を与える重要な設計要素です。本記事のチートシートを参考に、プロジェクトに最適な規約を確立していただければ幸いです。

関連リンク