Redis キーネーミング規約チートシート:階層・区切り・TTL ルール
Redis を使ったアプリケーション開発では、キーの命名規則が非常に重要です。適切な命名規則がないと、データの管理が煩雑になり、バグの温床となってしまいます。本記事では、Redis のキーネーミングにおける階層構造、区切り文字の選択、そして TTL(Time To Live)の設定ルールについて、実践的なチートシートとして詳しく解説いたします。
これらのルールを理解し実践することで、保守性の高い Redis 運用が可能になるでしょう。
早見表:Redis キーネーミング規約の基本ルール
| # | 項目 | 推奨ルール | 例 | 理由 |
|---|---|---|---|---|
| 1 | 区切り文字 | コロン : | user:1000:profile | Redis 公式推奨、可読性が高い |
| 2 | 階層の深さ | 2〜4 階層 | app:session:user:abc123 | 管理しやすく検索効率が良い |
| 3 | 命名規則 | スネークケース | user_session | 統一性、区切り文字との区別 |
| 4 | 名前空間 | アプリ名を先頭に | myapp:cache:data | 複数アプリでの衝突回避 |
| 5 | TTL の設定 | 用途別に明確化 | 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 を使い始めた開発者が直面する典型的な課題として、以下のようなものがあります。
キーの乱立と管理の困難さが第一の課題です。命名規則がないと、user1、user_data_1000、userData: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 のキーネーミングは、アプリケーション全体のアーキテクチャに影響を与える重要な設計要素です。本記事のチートシートを参考に、プロジェクトに最適な規約を確立していただければ幸いです。
関連リンク
articleRedis キーネーミング規約チートシート:階層・区切り・TTL ルール
articleRedis Docker Compose 構築:永続化・監視・TLS まで 1 ファイルで
articleRedis Pub/Sub vs Redis Streams:配信保証とスケーラビリティ比較
articleRedis 遅延の原因を特定:Latency Monitor と Slowlog の読み方
articleRedis 7 の新機能まとめ:ACL v2/I/O Threads/RESP3 を一気に把握
articleRedis 監視と可観測性:Prometheus Exporter と Grafana の実践ダッシュボード
articleKubernetes で WebSocket:Ingress(NGINX/ALB) 設定とスティッキーセッションの実装手順
articleStorybook × Design Tokens 設計:Style Dictionary とテーマ切替の連携
articleWebRTC Simulcast 設計ベストプラクティス:レイヤ数・ターゲットビットレート・切替条件
articleSolidJS コンポーネント間通信チート:Context・イベント・store の選択早見
articleWebLLM 中心のクライアントサイド RAG 設計:IndexedDB とベクトル検索の組み立て
articleShell Script の set -e が招く事故を回避:pipefail・サブシェル・条件分岐の落とし穴
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来