Redis入門|キャッシュ・セッション・キューで使うインメモリDBの基本

kento_morota 20分で読めます

「APIのレスポンスが遅い」「セッション管理をどうすればいい?」「非同期タスクを処理したい」――こうした課題を解決するツールとして、多くの開発者が活用しているのがRedisです。

Redisはインメモリデータストアで、データをメモリ上に保持するため非常に高速に動作します。本記事では、Redisのインストールから基本的なデータ型の操作、実際のアプリケーションでの活用方法まで、初心者向けにわかりやすく解説します。

Redisとは?特徴と主な用途

Redis(Remote Dictionary Server)は、オープンソースのインメモリデータ構造ストアです。2009年にSalvatore Sanfilippo氏によって開発され、現在では世界中の企業で採用されています。

Redisの主な特徴

  • 超高速:データをメモリ上に保持するため、読み書きのレイテンシが1ミリ秒以下
  • 豊富なデータ構造:文字列、リスト、セット、ハッシュ、ソート済みセットなど多様なデータ型
  • 永続化:RDB(スナップショット)とAOF(追記型ログ)によるデータ永続化
  • Pub/Sub:パブリッシュ/サブスクライブ型のメッセージング
  • TTL(有効期限):キーに有効期限を設定でき、キャッシュに最適
  • シングルスレッド:アトミックな操作が保証される

Redisの代表的な用途

Redisは「キーバリューストア」として分類されますが、実際にはさまざまな用途で活用されています。

キャッシュ:データベースクエリの結果やAPIレスポンスをキャッシュし、応答速度を向上させます。最も一般的な用途です。

セッションストア:Webアプリケーションのセッション情報を保存します。複数サーバー間でのセッション共有にも適しています。

メッセージキュー:非同期タスクの処理やマイクロサービス間の通信に使用します。

リアルタイムランキング:ソート済みセットを使って、リーダーボードやランキングを効率的に管理します。

レート制限:APIのリクエスト数を制限し、サービスを保護します。

Redisのインストールと接続

各環境でのインストール

# macOS(Homebrew)
brew install redis
brew services start redis

# Ubuntu/Debian
sudo apt update
sudo apt install redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-server

# Docker(推奨)
docker run --name my-redis \
  -p 6379:6379 \
  -v redis-data:/data \
  -d redis:7-alpine \
  redis-server --appendonly yes

redis-cliでの接続と基本操作

# Redisに接続
redis-cli

# リモートサーバーに接続
redis-cli -h hostname -p 6379 -a password

# 接続テスト
127.0.0.1:6379> PING
PONG

# サーバー情報の確認
127.0.0.1:6379> INFO server

# データベースの統計情報
127.0.0.1:6379> INFO keyspace

基本的なデータ型と操作コマンド

Redisは5つの基本的なデータ型を提供します。それぞれの特徴と使い方を見ていきましょう。

String(文字列)

最も基本的なデータ型で、テキスト、数値、バイナリデータなどを保存できます。

# 基本的なセット/ゲット
SET user:1:name "田中太郎"
GET user:1:name
# "田中太郎"

# 有効期限付きのセット(60秒後に自動削除)
SET session:abc123 "{\"user_id\": 1}" EX 60

# SETEX(SET + EXPIREの短縮形)
SETEX cache:homepage 300 "<html>...</html>"

# 存在しない場合のみセット(ロック機構に利用)
SETNX lock:order:123 "processing"

# 数値のインクリメント/デクリメント
SET page:views 0
INCR page:views      # 1
INCR page:views      # 2
INCRBY page:views 10 # 12
DECR page:views      # 11

# 複数キーを一度にセット/ゲット
MSET user:1:name "田中" user:1:email "tanaka@example.com"
MGET user:1:name user:1:email

Hash(ハッシュ)

フィールドと値のペアを持つデータ構造で、オブジェクトの表現に適しています。

# ハッシュにフィールドをセット
HSET user:1 name "田中太郎" email "tanaka@example.com" age 30

# 特定のフィールドを取得
HGET user:1 name
# "田中太郎"

# すべてのフィールドを取得
HGETALL user:1
# 1) "name"
# 2) "田中太郎"
# 3) "email"
# 4) "tanaka@example.com"
# 5) "age"
# 6) "30"

# フィールドの存在確認
HEXISTS user:1 name
# (integer) 1

# 数値フィールドのインクリメント
HINCRBY user:1 age 1
# (integer) 31

# フィールドの削除
HDEL user:1 age

List(リスト)

順序付きの文字列リストで、スタック(LIFO)やキュー(FIFO)として使えます。

# 左側に追加(先頭に挿入)
LPUSH notifications:user:1 "新しいメッセージがあります"
LPUSH notifications:user:1 "注文が確認されました"

# 右側に追加(末尾に追加)
RPUSH queue:emails "email:1" "email:2" "email:3"

# 範囲指定で取得(0始まり、-1は末尾)
LRANGE notifications:user:1 0 -1
# 1) "注文が確認されました"
# 2) "新しいメッセージがあります"

# 左側から取り出し(キュー:FIFO)
LPOP queue:emails
# "email:1"

# 右側から取り出し(スタック:LIFO)
RPOP queue:emails

# リストの長さ
LLEN queue:emails

# ブロッキングポップ(キューが空の場合、タイムアウトまで待機)
BLPOP queue:emails 30

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

# Set:重複しないコレクション
SADD tags:post:1 "redis" "database" "cache"
SADD tags:post:2 "redis" "performance" "tutorial"

# メンバーの確認
SMEMBERS tags:post:1
# 1) "redis"  2) "database"  3) "cache"

# 共通のタグ(積集合)
SINTER tags:post:1 tags:post:2
# 1) "redis"

# Sorted Set:スコア付きのセット(ランキングに最適)
ZADD leaderboard 100 "player:1"
ZADD leaderboard 250 "player:2"
ZADD leaderboard 180 "player:3"
ZADD leaderboard 320 "player:4"

# スコアの高い順にトップ3を取得
ZREVRANGE leaderboard 0 2 WITHSCORES
# 1) "player:4"  2) "320"
# 3) "player:2"  4) "250"
# 5) "player:3"  6) "180"

# 特定メンバーの順位を取得(0始まり、高い順)
ZREVRANK leaderboard "player:2"
# (integer) 1

# スコアの加算
ZINCRBY leaderboard 50 "player:1"
# "150"

キャッシュの実装パターン

Redisの最も一般的な用途であるキャッシュの実装パターンを、Node.jsのコード例で解説します。

Cache Aside(キャッシュアサイド)パターン

// Node.js + ioredis
import Redis from 'ioredis';

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  maxRetriesPerRequest: 3,
});

// Cache Asideパターン
async function getProduct(productId: string) {
  const cacheKey = `product:${productId}`;

  // 1. キャッシュから取得を試みる
  const cached = await redis.get(cacheKey);
  if (cached) {
    console.log('Cache HIT');
    return JSON.parse(cached);
  }

  // 2. キャッシュミス:DBから取得
  console.log('Cache MISS');
  const product = await db.query(
    'SELECT * FROM products WHERE id = $1',
    [productId]
  );

  // 3. キャッシュに保存(TTL: 10分)
  if (product) {
    await redis.set(cacheKey, JSON.stringify(product), 'EX', 600);
  }

  return product;
}

// データ更新時にキャッシュを無効化
async function updateProduct(productId: string, data: any) {
  await db.query(
    'UPDATE products SET name = $1, price = $2 WHERE id = $3',
    [data.name, data.price, productId]
  );

  // キャッシュを削除(次のリクエストで再取得される)
  await redis.del(`product:${productId}`);
}

キャッシュの一括無効化(パターンマッチ)

// 特定パターンのキャッシュを一括削除
async function invalidateProductCache() {
  let cursor = '0';
  do {
    const [nextCursor, keys] = await redis.scan(
      cursor,
      'MATCH', 'product:*',
      'COUNT', 100
    );
    cursor = nextCursor;

    if (keys.length > 0) {
      await redis.del(...keys);
    }
  } while (cursor !== '0');
}

キャッシュスタンピード対策

キャッシュの有効期限が切れた瞬間に大量のリクエストがDBに集中する「キャッシュスタンピード」を防ぐ方法です。

// ミューテックスロックを使った対策
async function getProductWithLock(productId: string) {
  const cacheKey = `product:${productId}`;
  const lockKey = `lock:product:${productId}`;

  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // ロックの取得を試みる(10秒のTTL)
  const acquired = await redis.set(lockKey, '1', 'EX', 10, 'NX');

  if (acquired) {
    try {
      // ロック取得成功:DBから取得してキャッシュに保存
      const product = await db.query(
        'SELECT * FROM products WHERE id = $1',
        [productId]
      );
      await redis.set(cacheKey, JSON.stringify(product), 'EX', 600);
      return product;
    } finally {
      await redis.del(lockKey);
    }
  } else {
    // ロック取得失敗:少し待ってからキャッシュを再取得
    await new Promise(resolve => setTimeout(resolve, 100));
    return getProductWithLock(productId);
  }
}

セッション管理の実装

Redisはセッションストアとして広く使われています。Express.jsでの実装例を見てみましょう。

Express.js + connect-redis

import express from 'express';
import session from 'express-session';
import { RedisStore } from 'connect-redis';
import Redis from 'ioredis';

const app = express();
const redis = new Redis();

// セッションストアの設定
app.use(session({
  store: new RedisStore({
    client: redis,
    prefix: 'sess:',     // キーのプレフィックス
    ttl: 86400,           // セッションの有効期間(秒)
  }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 86400000,     // クッキーの有効期間(ミリ秒)
    sameSite: 'lax',
  },
}));

// セッションの利用
app.post('/login', async (req, res) => {
  const user = await authenticateUser(req.body.email, req.body.password);
  if (user) {
    req.session.userId = user.id;
    req.session.role = user.role;
    res.json({ success: true });
  } else {
    res.status(401).json({ error: '認証失敗' });
  }
});

app.get('/profile', (req, res) => {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'ログインが必要です' });
  }
  res.json({ userId: req.session.userId });
});

app.post('/logout', (req, res) => {
  req.session.destroy((err) => {
    res.json({ success: true });
  });
});

メッセージキューの実装

RedisのList型を使って、シンプルなメッセージキューを実装できます。

プロデューサー/コンシューマーパターン

// プロデューサー:ジョブをキューに追加
async function enqueueJob(queueName: string, jobData: any) {
  const job = {
    id: crypto.randomUUID(),
    data: jobData,
    createdAt: new Date().toISOString(),
  };

  await redis.rpush(queueName, JSON.stringify(job));
  console.log(`Job ${job.id} enqueued`);
  return job.id;
}

// 使用例:メール送信ジョブをキューに追加
await enqueueJob('queue:emails', {
  to: 'user@example.com',
  subject: '注文確認',
  body: 'ご注文ありがとうございます。',
});
// コンシューマー:キューからジョブを取得して処理
async function processJobs(queueName: string) {
  console.log(`Waiting for jobs on ${queueName}...`);

  while (true) {
    // BLPOPで新しいジョブが来るまでブロック(タイムアウト30秒)
    const result = await redis.blpop(queueName, 30);

    if (result) {
      const [, jobJson] = result;
      const job = JSON.parse(jobJson);

      try {
        console.log(`Processing job ${job.id}`);
        await sendEmail(job.data);
        console.log(`Job ${job.id} completed`);
      } catch (error) {
        console.error(`Job ${job.id} failed:`, error);
        // 失敗したジョブをリトライキューに入れる
        await redis.rpush(`${queueName}:failed`, jobJson);
      }
    }
  }
}

// コンシューマーの起動
processJobs('queue:emails');

BullMQを使った本格的なキュー

本番環境では、Redis上に構築されたキューライブラリ「BullMQ」の利用を推奨します。

import { Queue, Worker } from 'bullmq';

const connection = { host: 'localhost', port: 6379 };

// キューの作成
const emailQueue = new Queue('email', { connection });

// ジョブの追加
await emailQueue.add('send-welcome', {
  to: 'user@example.com',
  template: 'welcome',
}, {
  attempts: 3,           // 最大リトライ回数
  backoff: {
    type: 'exponential',
    delay: 1000,          // 初回リトライまでの待機時間
  },
  removeOnComplete: 1000, // 完了済みジョブを直近1000件だけ保持
  removeOnFail: 5000,     // 失敗ジョブを直近5000件保持
});

// ワーカーの作成
const worker = new Worker('email', async (job) => {
  console.log(`Processing ${job.name} (${job.id})`);
  await sendEmail(job.data);
}, {
  connection,
  concurrency: 5,  // 同時処理数
});

worker.on('completed', (job) => {
  console.log(`Job ${job.id} completed`);
});

worker.on('failed', (job, err) => {
  console.log(`Job ${job?.id} failed: ${err.message}`);
});

Redisの運用で知っておくべきこと

メモリ管理とEvictionポリシー

Redisはメモリ上にデータを保持するため、メモリ使用量の管理が重要です。

# メモリ上限の設定(redis.conf)
maxmemory 256mb

# Evictionポリシー(メモリ上限に達した時の動作)
maxmemory-policy allkeys-lru

# 主なEvictionポリシー
# noeviction      : 書き込みエラーを返す(デフォルト)
# allkeys-lru     : すべてのキーからLRUで削除(キャッシュ用途に推奨)
# volatile-lru    : TTL付きのキーからLRUで削除
# allkeys-random  : ランダムに削除
# volatile-ttl    : TTLが短いキーから削除
# メモリ使用量の確認
redis-cli INFO memory

# 特定キーのメモリ使用量
redis-cli MEMORY USAGE user:1

永続化の設定

# RDB(スナップショット)の設定
# 900秒以内に1回以上の変更があればスナップショット作成
save 900 1
# 300秒以内に10回以上の変更があればスナップショット作成
save 300 10

# AOF(Append Only File)の設定
appendonly yes
appendfsync everysec  # 毎秒同期(推奨:パフォーマンスと耐久性のバランス)

セキュリティの基本設定

# パスワードの設定(redis.conf)
requirepass your_strong_password

# 危険なコマンドの無効化
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""

# 接続時にパスワード認証
redis-cli -a your_strong_password

まとめ

本記事では、Redisの基本から実践的な活用方法まで解説しました。

  • 基本データ型:String、Hash、List、Set、Sorted Setの操作方法
  • キャッシュ:Cache Asideパターン、キャッシュスタンピード対策
  • セッション管理:Express.js + connect-redisでの実装
  • メッセージキュー:List型を使ったシンプルなキュー、BullMQを使った本格的なキュー
  • 運用:メモリ管理、永続化、セキュリティ設定

Redisは「単なるキャッシュ」にとどまらず、セッション管理、メッセージキュー、リアルタイムランキングなど、さまざまな場面で活躍する万能ツールです。まずはDockerでRedisを起動し、redis-cliでコマンドを試してみるところから始めてみてください。

#Redis#キャッシュ#データベース
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

起業準備に役立つ情報、もっとありますよ。

まずは話だけ聞いてもらう