はじめに
Webサービスのユーザー数が増えると、アプリケーションサーバーやデータベースへの負荷も増加します。
特に、
- 同じデータを何度も取得する
- 人気ページへアクセスが集中する
- APIレスポンスが遅くなる
- データベースクエリが増える
といった問題が発生すると、ユーザー体験の低下につながります。
こうした課題を改善する代表的な方法が キャッシュ です。
本記事では、Redisを使ったWebアプリケーションのキャッシュ設計について、基本的な考え方から実装例まで紹介します。
KRCLUBでも、安定したWebサービスを構築するうえで、キャッシュ設計やバックエンドパフォーマンスは重要な技術テーマのひとつとして注目しています。
1. キャッシュとは
キャッシュとは、頻繁に利用するデータを高速な場所へ一時保存する仕組みです。
通常のデータ取得:
User Request
↓
Application
↓
Database
↓
Response
キャッシュを利用する場合:
User Request
↓
Application
↓
Cache
↓
Response
キャッシュにデータが存在すれば、データベースへアクセスせずに結果を返すことができます。
これによって、
- レスポンス高速化
- データベース負荷軽減
- サーバーコスト削減
が期待できます。
2. Redisとは
Redisは、高速なインメモリデータストアです。
データを主にメモリ上で扱うため、非常に高速な読み書きが可能です。
代表的な利用用途:
- APIキャッシュ
- セッション管理
- ランキング
- 一時データ保存
- Rate Limiting
- Queue管理
Redisでは、
- String
- Hash
- List
- Set
- Sorted Set
など複数のデータ型を利用できます。
3. Node.jsからRedisを利用する
Node.jsアプリケーションからRedisへ接続する例です。
import { createClient } from "redis";
const client = createClient({
url: process.env.REDIS_URL
});
client.on("error", (err) => {
console.error("Redis Error:", err);
});
await client.connect();
データを保存する場合:
await client.set("user:1001", "KRCLUB User");
データを取得する場合:
const user = await client.get("user:1001");
console.log(user);
基本的なKey-Value形式であれば、非常にシンプルに利用できます。
4. APIレスポンスをキャッシュする
例えば、ニュース一覧を返すAPIを考えます。
通常:
app.get("/api/news", async (req, res) => {
const news = await database.getNews();
res.json(news);
});
この場合、リクエストが来るたびにデータベースへアクセスします。
Redisを追加します。
app.get("/api/news", async (req, res) => {
const cached = await client.get("news:list");
if (cached) {
return res.json(JSON.parse(cached));
}
const news = await database.getNews();
await client.set(
"news:list",
JSON.stringify(news),
{
EX: 60
}
);
res.json(news);
});
この例では、データを60秒間キャッシュします。
5. TTLを設定する
キャッシュでは、古いデータが残り続けることを避ける必要があります。
そこで利用するのがTTLです。
TTLは、
Time To Live
の略です。
例えば、
await client.set(
"content:popular",
JSON.stringify(data),
{
EX: 300
}
);
とすると、300秒後にキャッシュが自動削除されます。
用途によってTTLを変えることが重要です。
| データ | TTL例 |
|---|---|
| 人気コンテンツ | 60秒 |
| カテゴリ一覧 | 10分 |
| サイト設定 | 1時間 |
| ユーザー固有情報 | 短時間 |
すべてのデータへ同じTTLを設定する必要はありません。
6. Cache Asideパターン
Webアプリケーションでよく利用されるのがCache Asideです。
処理の流れ:
Request
↓
Check Cache
↓
Cache Hit?
↓
YES → Return Cache
↓
NO
↓
Read Database
↓
Save Cache
↓
Return Data
アプリケーション側が、
「まずキャッシュを確認する」
という方式です。
シンプルで導入しやすいため、多くのWebサービスで利用されています。
7. Cache HitとCache Miss
キャッシュ運用では、Cache Hit Rateも重要です。
Cache Hit
必要なデータがキャッシュに存在する状態。
Cache Hit
→ Fast Response
Cache Miss
キャッシュにデータがない状態。
Cache Miss
→ Database Access
例えば、
Total Requests = 10,000
Cache Hit = 8,500
なら、
Cache Hit Rate = 85%
です。
キャッシュヒット率を監視することで、キャッシュ設計が効果的かどうか判断できます。
8. キャッシュキーを設計する
キャッシュキーの命名も重要です。
例えば、
user:1001
article:205
category:technology
ranking:daily
のように、
type:id
という形式で統一すると管理しやすくなります。
さらに環境を分ける場合:
prod:user:1001
dev:user:1001
のようにPrefixを付ける方法もあります。
9. キャッシュ無効化
キャッシュで最も難しい問題のひとつが、データ更新時の扱いです。
例えば、データベースの記事内容を更新したのに、Redisに古いデータが残っている場合があります。
このとき、
await client.del("article:205");
のようにキャッシュを削除できます。
一般的な処理:
Update Database
↓
Delete Cache
↓
Next Request
↓
Read Latest Data
↓
Create New Cache
これによって、古いキャッシュを保持し続ける問題を防げます。
10. Cache Stampede問題
人気コンテンツのキャッシュが切れた瞬間に大量アクセスが来ると、多数のリクエストが同時にデータベースへ流れる場合があります。
Cache Expired
↓
1000 Requests
↓
Database
これをCache Stampedeと呼びます。
対策としては、
- TTLを少しランダム化する
- Lockを利用する
- バックグラウンド更新する
- 古いキャッシュを短時間利用する
などがあります。
例えばTTLを完全に同じ時間にしない方法です。
const ttl =
300 + Math.floor(Math.random() * 60);
これにより、一斉にキャッシュが切れる可能性を下げられます。
11. Redisをセッション管理に使う
Redisはキャッシュ以外にも、セッション管理に利用できます。
例えば、
session:abc123
というキーにログイン状態を保存します。
メリット:
- 複数サーバーから共有できる
- 高速にアクセスできる
- TTLを設定できる
複数のアプリケーションサーバーを利用する構成では特に便利です。
12. ランキングにもRedisを使える
RedisのSorted Setを使うと、ランキング機能も実装できます。
例えば、
await client.zAdd(
"popular:articles",
[
{
score: 1200,
value: "article:101"
},
{
score: 950,
value: "article:102"
}
]
);
スコア順に取得:
const ranking =
await client.zRange(
"popular:articles",
0,
9,
{
REV: true
}
);
これを利用すれば、
- 人気記事
- 人気カテゴリ
- トレンドコンテンツ
などを高速に表示できます。
13. キャッシュすべきではないデータ
すべてのデータをキャッシュすればよいわけではありません。
例えば、
- 常に最新状態が必要なデータ
- 頻繁に更新されるデータ
- 非常に個別性が高いデータ
- セキュリティ上慎重に扱う必要がある情報
については、キャッシュ方法を慎重に考える必要があります。
キャッシュ導入前には、
Read Frequency
Update Frequency
Data Freshness
Memory Cost
を確認することが重要です。
14. キャッシュの監視
Redisを導入したら、運用状況を確認する必要があります。
代表的な監視項目:
- メモリ使用量
- Cache Hit Rate
- 接続数
- コマンド実行時間
- Eviction回数
- Redisエラー
キャッシュによってWebサービスを高速化しても、Redis自体がボトルネックになれば意味がありません。
そのため、アプリケーションと同様に監視が必要です。
15. KRCLUBが注目するバックエンド最適化
KRCLUBでは、Webサービスのユーザー体験を考える際、フロントエンドだけでなくバックエンドの処理速度や安定性も重要だと考えています。
特に、
- API高速化
- キャッシュ設計
- データベース負荷軽減
- リアルタイムデータ処理
- インフラ監視
といった技術分野に注目しています。
ページデザインが高速でも、APIレスポンスが遅ければ、ユーザーはサービス全体を遅いと感じます。
そのため、Webパフォーマンスはシステム全体で考える必要があります。
16. RedisとAIサービス
生成AIを組み込んだWebサービスでもRedisは活用できます。
例えば、
AIレスポンスキャッシュ
同じ質問に対する処理結果を一時保存。
Rate Limiting
AI APIへのアクセス回数を制御。
Session Memory
短期間の会話状態を保持。
Queue
時間のかかるAI処理を非同期化。
構成例:
User
↓
Web App
↓
Redis
↓
AI API
キャッシュできる結果をRedisから返せば、外部AI APIへの不要なリクエストを減らせます。
まとめ
Redisを利用したキャッシュ設計は、Webサービスの高速化と負荷軽減に有効な方法です。
特に重要なのは、
- Cache Aside
- TTL設計
- Cache Hit Rate
- キャッシュキー設計
- キャッシュ無効化
- Cache Stampede対策
- Redis監視
です。
ただし、キャッシュは単純に導入するだけではなく、「どのデータを、どのくらいの期間保存するのか」を考える必要があります。
KRCLUBでも、より安定したデジタルサービスを実現するため、Redisやキャッシュ技術を含むバックエンドパフォーマンス改善に注目しています。
Webサービスが成長するほど、キャッシュは単なる高速化技術ではなく、システム全体のスケーラビリティを支える重要な設計要素になっていくでしょう。