ストーリー
高橋アーキテクトがモニタリング画面を示した。
キャッシュとは
キャッシュとは、アクセス頻度の高いデータを高速なストレージに一時保存し、次回以降のアクセスを高速化する仕組みです。
// キャッシュの基本構造
class SimpleCache<T> {
private store = new Map<string, { data: T; expiresAt: number }>();
get(key: string): T | null {
const entry = this.store.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
this.store.delete(key);
return null; // 期限切れ
}
return entry.data;
}
set(key: string, data: T, ttlMs: number): void {
this.store.set(key, {
data,
expiresAt: Date.now() + ttlMs,
});
}
}
3つの基本戦略
1. Cache-Aside(キャッシュアサイド / Lazy Loading)
最も一般的な戦略。アプリケーションがキャッシュを直接管理します。
class CacheAsideRepository {
constructor(
private cache: CacheClient,
private db: Database,
) {}
async getProduct(id: string): Promise<Product> {
// 1. キャッシュを確認
const cached = await this.cache.get(`product:${id}`);
if (cached) {
return JSON.parse(cached); // キャッシュヒット
}
// 2. キャッシュミス → DBから取得
const product = await this.db.findProduct(id);
// 3. キャッシュに保存
await this.cache.set(
`product:${id}`,
JSON.stringify(product),
3600 // TTL: 1時間
);
return product;
}
}
| メリット | デメリット |
|---|---|
| 実装がシンプル | キャッシュミス時にレイテンシが増加 |
| 必要なデータだけキャッシュ | データの不整合が発生し得る |
| 障害時にDBフォールバック可能 | 初回アクセスは常にDB |
適したユースケース: 読み取りが多い一般的なアプリケーション
2. Write-Through(ライトスルー)
書き込み時にキャッシュとDBを同時に更新します。
class WriteThroughRepository {
constructor(
private cache: CacheClient,
private db: Database,
) {}
async updateProduct(id: string, data: ProductUpdate): Promise<Product> {
// 1. DBに書き込み
const product = await this.db.updateProduct(id, data);
// 2. キャッシュも即座に更新
await this.cache.set(
`product:${id}`,
JSON.stringify(product),
3600
);
return product;
}
async getProduct(id: string): Promise<Product> {
// キャッシュは常に最新なので安心して読める
const cached = await this.cache.get(`product:${id}`);
if (cached) {
return JSON.parse(cached);
}
// キャッシュにない場合はDBから取得してキャッシュに保存
const product = await this.db.findProduct(id);
await this.cache.set(`product:${id}`, JSON.stringify(product), 3600);
return product;
}
}
| メリット | デメリット |
|---|---|
| キャッシュが常に最新 | 書き込みのレイテンシが増加 |
| データの整合性が高い | 読まれないデータもキャッシュに載る |
適したユースケース: データ整合性が重要なシステム
3. Write-Behind(ライトビハインド / Write-Back)
キャッシュに先に書き込み、DBへの反映は非同期で行います。
class WriteBehindRepository {
private writeQueue: WriteOperation[] = [];
async updateProduct(id: string, data: ProductUpdate): Promise<Product> {
const product = { ...data, id, updatedAt: new Date() } as Product;
// 1. キャッシュに即座に書き込み(高速)
await this.cache.set(`product:${id}`, JSON.stringify(product), 3600);
// 2. 書き込みキューに追加(非同期でDBに反映)
this.writeQueue.push({
operation: 'update',
entity: 'product',
id,
data: product,
timestamp: Date.now(),
});
return product; // すぐにレスポンスを返せる
}
// バックグラウンドでキューを処理
async processWriteQueue(): Promise<void> {
while (this.writeQueue.length > 0) {
const op = this.writeQueue.shift()!;
try {
await this.db.execute(op);
} catch (error) {
// リトライキューに入れる
this.retryQueue.push(op);
}
}
}
}
| メリット | デメリット |
|---|---|
| 書き込みが非常に高速 | データ損失のリスクがある |
| DBへの書き込み負荷を平準化 | 実装が複雑 |
適したユースケース: 書き込みが大量で多少のデータ損失が許容できるシステム(ログ、分析データなど)
戦略の比較と選択
| 基準 | Cache-Aside | Write-Through | Write-Behind |
|---|---|---|---|
| 読み取り速度 | ミス時遅い | 常に高速 | 常に高速 |
| 書き込み速度 | 標準 | やや遅い | 非常に高速 |
| データ整合性 | 中 | 高 | 低 |
| 実装複雑度 | 低 | 中 | 高 |
| データ損失リスク | なし | なし | あり |
まとめ
| ポイント | 内容 |
|---|---|
| Cache-Aside | 読み取り時に遅延読み込み。最も一般的 |
| Write-Through | 書き込み時に同期更新。整合性重視 |
| Write-Behind | 非同期書き込み。スピード重視 |
| 選択基準 | 読み書き比率、整合性要件、許容レイテンシで判断 |
チェックリスト
- 3つのキャッシュ戦略の違いを説明できる
- それぞれのメリット・デメリットを理解した
- ユースケースに応じた戦略選択ができる
次のステップへ
次は「多層キャッシュアーキテクチャ」を学びます。ブラウザからデータベースまで、各層でのキャッシュの役割を理解しましょう。
推定読了時間: 30分