開発者のためのRedis実践ガイド — キャッシュからメッセージキューまで


Redisは高速なインメモリデータストアで、キャッシュ、セッション管理、メッセージキューなど幅広い用途で使われています。この記事では、開発者が実践で使えるRedisの活用方法を解説します。

Redisとは

Redisは「Remote Dictionary Server」の略で、キーバリューストア型のNoSQLデータベースです。

主な特徴:

  • 高速: すべてのデータをメモリに保持
  • 多様なデータ型: 文字列、リスト、セット、ソート済みセット、ハッシュなど
  • 永続化: スナップショット、AOF(Append Only File)
  • Pub/Sub: リアルタイムメッセージング
  • Lua スクリプト: アトミックな複合操作

セットアップ

Node.jsでRedisを使用するには、ioredisまたはredisパッケージを使用します。

npm install ioredis
import Redis from 'ioredis';

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  password: process.env.REDIS_PASSWORD,
});

// 接続確認
await redis.ping(); // "PONG"

基本データ型と操作

文字列(String)

最も基本的なデータ型です。

// セット
await redis.set('user:1:name', 'Alice');

// ゲット
const name = await redis.get('user:1:name'); // "Alice"

// 有効期限付きセット(秒)
await redis.setex('session:abc123', 3600, 'user-data');

// インクリメント
await redis.incr('page:views'); // 1
await redis.incr('page:views'); // 2

// 複数操作
await redis.mset('key1', 'value1', 'key2', 'value2');
const values = await redis.mget('key1', 'key2'); // ["value1", "value2"]

ハッシュ(Hash)

オブジェクトのようなデータ構造です。

// ユーザー情報をハッシュで保存
await redis.hset('user:1', {
  name: 'Alice',
  email: 'alice@example.com',
  age: '25',
});

// 特定フィールドを取得
const email = await redis.hget('user:1', 'email');

// 全フィールドを取得
const user = await redis.hgetall('user:1');
// { name: 'Alice', email: 'alice@example.com', age: '25' }

// フィールドの存在確認
const exists = await redis.hexists('user:1', 'name'); // 1

// 数値フィールドのインクリメント
await redis.hincrby('user:1', 'age', 1); // 26

リスト(List)

順序付きのコレクションです。

// 左から追加
await redis.lpush('queue:tasks', 'task1', 'task2');

// 右から追加
await redis.rpush('queue:tasks', 'task3');

// 左から取得
const task = await redis.lpop('queue:tasks'); // "task2"

// 範囲取得
const tasks = await redis.lrange('queue:tasks', 0, -1);

// ブロッキング pop(タイムアウト秒)
const item = await redis.blpop('queue:tasks', 0);

セット(Set)

重複のない集合です。

// メンバー追加
await redis.sadd('tags:post:1', 'javascript', 'typescript', 'react');

// メンバー存在確認
const isMember = await redis.sismember('tags:post:1', 'typescript'); // 1

// 全メンバー取得
const tags = await redis.smembers('tags:post:1');

// 集合演算
await redis.sadd('tags:post:2', 'typescript', 'node', 'express');
const common = await redis.sinter('tags:post:1', 'tags:post:2'); // ["typescript"]
const all = await redis.sunion('tags:post:1', 'tags:post:2');

ソート済みセット(Sorted Set)

スコア付きの順序集合です。

// ランキング
await redis.zadd('leaderboard', 100, 'player1');
await redis.zadd('leaderboard', 200, 'player2');
await redis.zadd('leaderboard', 150, 'player3');

// スコア順に取得(降順)
const top3 = await redis.zrevrange('leaderboard', 0, 2, 'WITHSCORES');
// ["player2", "200", "player3", "150", "player1", "100"]

// ランク取得(0始まり)
const rank = await redis.zrevrank('leaderboard', 'player1'); // 2

// スコア範囲検索
const players = await redis.zrangebyscore('leaderboard', 100, 180);

キャッシュパターン

Cache-Aside(Lazy Loading)

最も一般的なパターンです。

async function getUser(userId: string) {
  const cacheKey = `user:${userId}`;

  // キャッシュを確認
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }

  // DBから取得
  const user = await db.user.findUnique({ where: { id: userId } });

  // キャッシュに保存(1時間)
  await redis.setex(cacheKey, 3600, JSON.stringify(user));

  return user;
}

Write-Through

データを書き込む際に同時にキャッシュも更新します。

async function updateUser(userId: string, data: UpdateData) {
  // DBを更新
  const user = await db.user.update({
    where: { id: userId },
    data,
  });

  // キャッシュを更新
  const cacheKey = `user:${userId}`;
  await redis.setex(cacheKey, 3600, JSON.stringify(user));

  return user;
}

Cache Invalidation

関連キャッシュを削除します。

async function deletePost(postId: string) {
  await db.post.delete({ where: { id: postId } });

  // 関連キャッシュを削除
  const keys = await redis.keys(`post:${postId}:*`);
  if (keys.length > 0) {
    await redis.del(...keys);
  }

  // リストキャッシュも削除
  await redis.del('posts:list');
}

セッション管理

Redisは高速なセッションストアとして最適です。

import { randomUUID } from 'crypto';

class SessionStore {
  private redis: Redis;
  private prefix = 'session:';
  private ttl = 86400; // 24時間

  async create(userId: string, data: any) {
    const sessionId = randomUUID();
    const key = this.prefix + sessionId;

    await this.redis.setex(
      key,
      this.ttl,
      JSON.stringify({ userId, ...data })
    );

    return sessionId;
  }

  async get(sessionId: string) {
    const data = await this.redis.get(this.prefix + sessionId);
    return data ? JSON.parse(data) : null;
  }

  async refresh(sessionId: string) {
    await this.redis.expire(this.prefix + sessionId, this.ttl);
  }

  async destroy(sessionId: string) {
    await this.redis.del(this.prefix + sessionId);
  }
}

Pub/Sub(メッセージング)

リアルタイム通信に使用できます。

// Publisher
const publisher = new Redis();
await publisher.publish('notifications', JSON.stringify({
  type: 'NEW_MESSAGE',
  userId: '123',
  message: 'Hello!',
}));

// Subscriber
const subscriber = new Redis();
await subscriber.subscribe('notifications');

subscriber.on('message', (channel, message) => {
  const data = JSON.parse(message);
  console.log(`Channel: ${channel}, Data:`, data);
});

Redis Streams

より高度なメッセージキューです。

// メッセージ追加
await redis.xadd('events', '*', 'type', 'USER_REGISTERED', 'userId', '123');

// 消費者グループ作成
await redis.xgroup('CREATE', 'events', 'processors', '0', 'MKSTREAM');

// メッセージ読み取り
const messages = await redis.xreadgroup(
  'GROUP', 'processors', 'worker1',
  'COUNT', 10,
  'BLOCK', 5000,
  'STREAMS', 'events', '>'
);

// ACK(処理完了を通知)
await redis.xack('events', 'processors', messageId);

Upstashでサーバーレス利用

Upstashは、サーバーレス環境で使いやすいHTTPベースのRedisです。

import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

// Edge Functionsでも動作
export async function GET(request: Request) {
  const views = await redis.incr('page:views');
  return new Response(JSON.stringify({ views }));
}

パフォーマンス最適化

パイプライン

複数コマンドを一度に送信します。

const pipeline = redis.pipeline();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.get('key1');
const results = await pipeline.exec();

Lua スクリプト

アトミックな複合操作を実行します。

const script = `
  local current = redis.call('GET', KEYS[1])
  if current == false then
    redis.call('SET', KEYS[1], ARGV[1])
    return 1
  end
  return 0
`;

const result = await redis.eval(script, 1, 'lock:resource', 'locked');

まとめ

Redisは単なるキャッシュ以上の機能を持つ強力なツールです。

使い分けの目安:

  • キャッシュ: 頻繁にアクセスされるデータ
  • セッション: ユーザーのログイン状態
  • ランキング: リーダーボード、人気記事
  • リアルタイム: チャット、通知
  • キュー: 非同期タスク処理

適切に活用することで、アプリケーションのパフォーマンスを大幅に向上させることができます。