ストーリー
ミッション概要
| ミッション | テーマ | 目安時間 |
|---|---|---|
| Mission 1 | 2PCフローの設計と限界分析 | 15分 |
| Mission 2 | Saga + 補償の実装 | 15分 |
| Mission 3 | Outboxパターンの実装 | 15分 |
| Mission 4 | 障害シナリオのテスト設計 | 15分 |
前提シナリオ
ShopMasterの注文フロー:注文作成 → 在庫引当 → 決済処理 → 配送手配
Mission 1: 2PCフローの設計と限界分析(15分)
要件
上記フローを2PCで実装した場合のシーケンスを描き、発生しうる問題を3つ挙げてください。
解答例
sequenceDiagram
participant C as Coordinator
participant O as Order DB
participant I as Inventory DB
participant P as Payment DB
participant S as Shipping DB
C->>O: PREPARE
O-->>C: OK
C->>I: PREPARE
I-->>C: OK
C->>P: PREPARE
P-->>C: OK
C->>S: PREPARE
S--xC: TIMEOUT(問題発生)
Note over C,S: 全DBがPREPARE状態でロック保持中<br/>Coordinatorがロールバック指示を出すまで全DBがブロック
問題点:
- Prepare後のCoordinator障害: 全参加者がロック保持したまま待機。手動復旧が必要
- ネットワーク分断: 一部参加者にCommit到達、他にRollback到達の可能性(ヒューリスティック判断が必要)
- レイテンシ: 4つのDBの同期的ロック取得で応答時間が数秒に。ピーク時にスループット低下
結論: マイクロサービス(特にDB分離後)では2PCは非推奨。Sagaを採用すべき。
Mission 2: Saga + 補償の実装(15分)
要件
注文フローのOrchestration Sagaを設計し、各ステップの補償アクションを定義してください。
解答例
const orderSaga: SagaDefinition = {
name: 'OrderSaga',
steps: [
{
name: 'createOrder',
type: 'COMPENSATABLE',
execute: { service: 'order', action: 'create', data: '${orderData}' },
compensate: { service: 'order', action: 'cancel', data: '${orderId}' },
},
{
name: 'reserveStock',
type: 'COMPENSATABLE',
execute: { service: 'inventory', action: 'reserve', data: '${items}' },
compensate: { service: 'inventory', action: 'release', data: '${reservationId}' },
},
{
name: 'chargePayment',
type: 'PIVOT', // 補償不可(返金は別フロー)
execute: { service: 'payment', action: 'charge', data: '${paymentData}' },
compensate: null, // Pivot以降は補償不要
},
{
name: 'arrangeShipment',
type: 'RETRIABLE', // 必ず成功するまでリトライ
execute: { service: 'shipping', action: 'arrange', data: '${shippingData}' },
compensate: null,
retryPolicy: { maxRetries: 10, backoffMs: 1000 },
},
],
};
Mission 3: Outboxパターンの実装(15分)
要件
注文サービスのOutboxテーブル設計とCDCによる転送フローを実装してください。
解答例
// Outbox テーブルスキーマ(Drizzle ORM)
const outboxTable = pgTable('outbox', {
id: uuid('id').primaryKey().defaultRandom(),
aggregateType: varchar('aggregate_type', { length: 255 }).notNull(),
aggregateId: varchar('aggregate_id', { length: 255 }).notNull(),
eventType: varchar('event_type', { length: 255 }).notNull(),
payload: jsonb('payload').notNull(),
createdAt: timestamp('created_at').defaultNow(),
publishedAt: timestamp('published_at'),
});
// アトミックな書き込み
class OrderService {
async confirmOrder(orderId: string): Promise<void> {
await this.db.transaction(async (tx) => {
// ビジネスロジック
await tx.update(orders)
.set({ status: 'CONFIRMED' })
.where(eq(orders.id, orderId));
// Outboxに同時記録
await tx.insert(outboxTable).values({
aggregateType: 'Order',
aggregateId: orderId,
eventType: 'order.confirmed',
payload: { orderId, confirmedAt: new Date().toISOString() },
});
});
}
}
Mission 4: 障害シナリオのテスト設計(15分)
要件
以下の障害シナリオのテストケースを設計してください。
- 在庫引当成功後、決済が失敗
- Outbox relay がダウン中にイベントが蓄積
- Consumer が同じイベントを2回受信
解答例
describe('OrderSaga 障害テスト', () => {
test('決済失敗時に在庫引当が補償される', async () => {
// Arrange
paymentService.charge.mockRejectedValue(new Error('Card declined'));
// Act
await expect(saga.execute(orderContext)).rejects.toThrow('Card declined');
// Assert: 補償が逆順に実行された
expect(inventoryService.release).toHaveBeenCalledWith(reservationId);
expect(orderService.cancel).toHaveBeenCalledWith(orderId);
expect(sagaState.status).toBe('COMPENSATED');
});
test('Outbox relay停止中もイベントが失われない', async () => {
// Arrange: Relayを停止
await outboxRelay.stop();
// Act: 注文を3件作成
await orderService.confirmOrder('ord-1');
await orderService.confirmOrder('ord-2');
await orderService.confirmOrder('ord-3');
// Assert: Outboxに3件蓄積
const pending = await db.select().from(outbox).where(isNull(outbox.publishedAt));
expect(pending).toHaveLength(3);
// Relay再開後に全件発行
await outboxRelay.start();
await waitFor(() => expect(kafka.published).toHaveLength(3));
});
test('同一イベントの重複処理が冪等に動作する', async () => {
const event = { id: 'evt-001', type: 'order.confirmed', data: { orderId: 'ord-1' } };
// Act: 同じイベントを2回処理
await inboxHandler.handle(event);
await inboxHandler.handle(event); // 2回目
// Assert: ビジネスロジックは1回のみ実行
expect(processOrder).toHaveBeenCalledTimes(1);
});
});
まとめ
| ポイント | 内容 |
|---|---|
| 2PC の限界 | ブロッキングとスケーラビリティの問題 |
| Saga 設計 | ステップ分類と補償の定義 |
| Outbox | DB + イベントの原子的保証 |
| テスト | 障害シナリオを体系的にテスト |
チェックリスト
- 2PC の問題点を具体的に示せた
- Saga の各ステップと補償を設計できた
- Outbox パターンを実装できた
- 障害テストケースを設計できた
次のステップへ
次はチェックポイントクイズで分散トランザクションの理解度を確認します。
推定読了時間: 60分