カレンダーシステム アーキテクチャ設計¶
エグゼクティブサマリー¶
本ドキュメントでは、エンタープライズグレードのカレンダーシステムの技術アーキテクチャを定義します。マイクロサービスアーキテクチャ、イベント駆動設計、およびクラウドネイティブな原則に基づき、高可用性、スケーラビリティ、セキュリティを実現します。
主要な技術選定: - バックエンド: Go (gRPC services), Node.js (BFF layer) - フロントエンド: React + TypeScript, React Native - データストア: PostgreSQL (primary), Redis (cache), Elasticsearch (search) - メッセージング: Apache Kafka - インフラ: AWS (EKS, RDS, ElastiCache, MSK)
1. アーキテクチャ原則¶
1.1 コアプリンシプル¶
スケーラビリティ¶
- 水平スケーリング: すべてのサービスをステートレスに設計し、必要に応じてスケールアウト可能
- データパーティショニング: ユーザーIDベースのシャーディングにより、データベース層のスケーラビリティを確保
- CDN活用: 静的リソースとグローバルに分散したエッジキャッシュ
可用性と信頼性¶
- マルチAZ展開: すべてのコンポーネントを複数のアベイラビリティゾーンに配置
- サーキットブレーカー: 障害の伝播を防ぐため、全ての外部呼び出しにサーキットブレーカーを実装
- 優雅な劣化: 部分的な障害時でも基本機能を提供できる設計
- 目標: 99.95% の可用性(年間ダウンタイム 4.38時間)
パフォーマンス¶
- レスポンス時間: P95 で 200ms 以内、P99 で 500ms 以内
- スループット: 10,000 リクエスト/秒を処理可能
- データベースクエリ: インデックス最適化により、すべてのクエリを 50ms 以内に実行
- リアルタイム更新: WebSocket による双方向通信で 100ms 以内の更新通知
セキュリティ¶
- ゼロトラスト原則: すべての通信を認証・認可
- データ暗号化: 転送中(TLS 1.3)および保存時(AES-256)の暗号化
- 最小権限の原則: IAM ポリシーとRBACによる厳密なアクセス制御
- コンプライアンス: GDPR, SOC 2 Type II, ISO 27001 準拠
保守性¶
- モジュラー設計: 疎結合なマイクロサービスによる独立したデプロイとスケーリング
- オブザーバビリティ: 分散トレーシング、メトリクス、ログの統合監視
- Infrastructure as Code: Terraform によるインフラ管理
- 自動化: CI/CD パイプラインによる継続的デリバリー
2. 高レベルアーキテクチャ¶
2.1 システム概要図¶
┌─────────────────────────────────────────────────────────────────┐
│ Client Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Web App │ │ Mobile App │ │ API Client │ │
│ │(React + TS) │ │(React Native)│ │(External) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ API Gateway Layer │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ AWS API Gateway + CloudFront (CDN) │ │
│ │ - Rate Limiting - DDoS Protection - WAF │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ BFF (Backend for Frontend) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ GraphQL API Layer (Node.js + Apollo Server) │ │
│ │ - Request Aggregation - Response Caching │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Microservices Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ Calendar │ │ Event │ │ Resource │ │ User │ │
│ │ Service │ │ Service │ │ Service │ │ Service │ │
│ │ (Go) │ │ (Go) │ │ (Go) │ │ (Go) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └──────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ Notification│ │ Sync │ │ Analytics │ │ Search │ │
│ │ Service │ │ Service │ │ Service │ │ Service │ │
│ │ (Go) │ │ (Go) │ │ (Go) │ │ (Go) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Event Streaming Layer │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Apache Kafka (AWS MSK) │ │
│ │ Topics: calendar.events, notifications, sync.requests │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Data Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ PostgreSQL │ │ Redis │ │Elasticsearch│ │ S3 │ │
│ │ (RDS) │ │(ElastiCache)│ │ (ES) │ │(Storage) │ │
│ │ Primary DB │ │ Cache │ │ Search │ │ Files │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Observability & Operations │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ Prometheus │ │ Grafana │ │ Jaeger │ │ ELK │ │
│ │ Metrics │ │ Dashboards │ │ Tracing │ │ Logs │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 レイヤー責務¶
| レイヤー | 責務 | 技術スタック |
|---|---|---|
| Client | ユーザーインターフェース、クライアントサイドロジック | React, React Native, TypeScript |
| API Gateway | ルーティング、認証、レート制限、DDoS防御 | AWS API Gateway, CloudFront, WAF |
| BFF | リクエスト集約、データ変換、キャッシング | Node.js, Apollo GraphQL |
| Microservices | ビジネスロジック、データ管理 | Go, gRPC, Protocol Buffers |
| Event Streaming | 非同期処理、イベントソーシング | Apache Kafka (MSK) |
| Data | データ永続化、検索、キャッシング | PostgreSQL, Redis, Elasticsearch |
| Observability | 監視、ログ、トレーシング | Prometheus, Grafana, Jaeger, ELK |
3. マイクロサービス設計¶
3.1 Calendar Service¶
責務: カレンダーの作成、管理、共有機能
主要API:
service CalendarService {
rpc CreateCalendar(CreateCalendarRequest) returns (Calendar);
rpc GetCalendar(GetCalendarRequest) returns (Calendar);
rpc UpdateCalendar(UpdateCalendarRequest) returns (Calendar);
rpc DeleteCalendar(DeleteCalendarRequest) returns (Empty);
rpc ListCalendars(ListCalendarsRequest) returns (ListCalendarsResponse);
rpc ShareCalendar(ShareCalendarRequest) returns (ShareResponse);
}
データモデル:
type Calendar struct {
ID string
OwnerID string
Name string
Description string
Color string
TimeZone string
IsPublic bool
Settings CalendarSettings
CreatedAt time.Time
UpdatedAt time.Time
}
スケーリング戦略: - ユーザーIDベースのパーティショニング - 読み取りレプリカによる負荷分散 - Redis キャッシング(TTL: 5分)
3.2 Event Service¶
責務: イベント(ミーティング、予定)のCRUD操作、リカレンス処理
主要API:
service EventService {
rpc CreateEvent(CreateEventRequest) returns (Event);
rpc GetEvent(GetEventRequest) returns (Event);
rpc UpdateEvent(UpdateEventRequest) returns (Event);
rpc DeleteEvent(DeleteEventRequest) returns (Empty);
rpc ListEvents(ListEventsRequest) returns (ListEventsResponse);
rpc SearchEvents(SearchEventsRequest) returns (SearchEventsResponse);
rpc GetEventOccurrences(GetOccurrencesRequest) returns (OccurrencesResponse);
}
データモデル:
type Event struct {
ID string
CalendarID string
OrganizerID string
Title string
Description string
Location string
StartTime time.Time
EndTime time.Time
TimeZone string
IsAllDay bool
RecurrenceRule *RecurrenceRule
Attendees []Attendee
Reminders []Reminder
Status EventStatus // CONFIRMED, TENTATIVE, CANCELLED
Visibility Visibility // PUBLIC, PRIVATE, CONFIDENTIAL
Attachments []Attachment
ConferenceData *ConferenceData
CreatedAt time.Time
UpdatedAt time.Time
}
type RecurrenceRule struct {
Frequency Frequency // DAILY, WEEKLY, MONTHLY, YEARLY
Interval int
Count int
Until *time.Time
ByDay []Weekday
ByMonth []int
ByMonthDay []int
}
複雑なロジック: 1. リカレンス計算: RFC 5545 (iCalendar) 準拠の繰り返しイベント生成 2. コンフリクト検出: 時間帯の重複をリアルタイムでチェック 3. タイムゾーン変換: 複数タイムゾーン間の正確な時刻変換
パフォーマンス最適化: - イベント検索に Elasticsearch を使用(フルテキスト検索、ファジーマッチング) - 頻繁にアクセスされるイベントは Redis にキャッシュ - リカレンスイベントは事前計算してマテリアライズドビューに格納
3.3 Resource Service¶
責務: 会議室、設備などのリソース管理と予約
主要API:
service ResourceService {
rpc CreateResource(CreateResourceRequest) returns (Resource);
rpc GetResource(GetResourceRequest) returns (Resource);
rpc UpdateResource(UpdateResourceRequest) returns (Resource);
rpc DeleteResource(DeleteResourceRequest) returns (Empty);
rpc ListResources(ListResourcesRequest) returns (ListResourcesResponse);
rpc CheckAvailability(AvailabilityRequest) returns (AvailabilityResponse);
rpc ReserveResource(ReserveRequest) returns (Reservation);
rpc ReleaseResource(ReleaseRequest) returns (Empty);
}
データモデル:
type Resource struct {
ID string
Name string
Type ResourceType // ROOM, EQUIPMENT, PARKING
Location Location
Capacity int
Features []Feature // PROJECTOR, WHITEBOARD, VIDEO_CONF
IsAvailable bool
Metadata map[string]string
CreatedAt time.Time
UpdatedAt time.Time
}
type Reservation struct {
ID string
ResourceID string
EventID string
UserID string
StartTime time.Time
EndTime time.Time
Status ReservationStatus
CreatedAt time.Time
}
ビジネスロジック: - 競合制御: 楽観的ロック(バージョニング)による同時予約の防止 - 自動解放: 使用されていない予約の自動キャンセル(15分ルール) - スマート推奨: 空き状況、設備、参加者の位置を考慮した会議室提案
3.4 Notification Service¶
責務: リマインダー、通知の管理と送信
主要API:
service NotificationService {
rpc CreateNotification(CreateNotificationRequest) returns (Notification);
rpc SendNotification(SendNotificationRequest) returns (SendResponse);
rpc ScheduleNotification(ScheduleRequest) returns (ScheduledNotification);
rpc GetUserNotifications(GetNotificationsRequest) returns (NotificationsResponse);
rpc MarkAsRead(MarkAsReadRequest) returns (Empty);
rpc UpdatePreferences(UpdatePreferencesRequest) returns (Preferences);
}
通知チャネル: - Email: Amazon SES(送信レート: 50,000通/日) - Push: Firebase Cloud Messaging, APNs - SMS: Amazon SNS(重要通知のみ) - In-App: WebSocket によるリアルタイム通知
配信戦略: - バッチ処理: 定期リマインダーはバッチで処理(5分間隔) - 優先度キュー: 緊急通知は即座に送信 - レート制限: ユーザーあたりの通知数を制限(スパム防止) - リトライロジック: 指数バックオフによる再送(最大3回)
3.5 Sync Service¶
責務: 外部カレンダーシステムとの同期
対応システム: - Google Calendar (CalDAV/CardDAV) - Microsoft Outlook (Exchange Web Services) - Apple Calendar (CalDAV) - iCal (RFC 5545 format)
主要API:
service SyncService {
rpc ConnectExternalCalendar(ConnectRequest) returns (Connection);
rpc SyncCalendar(SyncRequest) returns (SyncResponse);
rpc GetSyncStatus(GetSyncStatusRequest) returns (SyncStatus);
rpc DisconnectCalendar(DisconnectRequest) returns (Empty);
rpc ResolveConflict(ConflictResolutionRequest) returns (Resolution);
}
同期戦略: - 増分同期: 変更差分のみを同期(フルシンクは初回のみ) - 双方向同期: 変更を両方向に反映(コンフリクト解決ロジックあり) - 同期頻度: 5分間隔(ユーザー設定で変更可能) - コンフリクト解決: Last-Write-Wins(タイムスタンプベース)、ユーザー選択オプション
データ整合性: - ベクタークロック: 分散システム間の因果関係追跡 - イベントソーシング: すべての変更をイベントログに記録 - 監査ログ: 同期履歴の完全な追跡
4. データアーキテクチャ¶
4.1 データベース設計¶
PostgreSQL スキーマ:
-- Calendars Table
CREATE TABLE calendars (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID NOT NULL REFERENCES users(id),
name VARCHAR(255) NOT NULL,
description TEXT,
color VARCHAR(7),
timezone VARCHAR(50) NOT NULL,
is_public BOOLEAN DEFAULT FALSE,
settings JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT valid_color CHECK (color ~ '^#[0-9A-Fa-f]{6}$')
);
CREATE INDEX idx_calendars_owner_id ON calendars(owner_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_calendars_updated_at ON calendars(updated_at DESC);
-- Events Table (Partitioned by month)
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
calendar_id UUID NOT NULL REFERENCES calendars(id),
organizer_id UUID NOT NULL REFERENCES users(id),
title VARCHAR(500) NOT NULL,
description TEXT,
location VARCHAR(500),
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
timezone VARCHAR(50) NOT NULL,
is_all_day BOOLEAN DEFAULT FALSE,
recurrence_rule JSONB,
status VARCHAR(20) NOT NULL DEFAULT 'CONFIRMED',
visibility VARCHAR(20) NOT NULL DEFAULT 'PUBLIC',
attachments JSONB,
conference_data JSONB,
metadata JSONB,
version INTEGER DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT valid_status CHECK (status IN ('CONFIRMED', 'TENTATIVE', 'CANCELLED')),
CONSTRAINT valid_visibility CHECK (visibility IN ('PUBLIC', 'PRIVATE', 'CONFIDENTIAL')),
CONSTRAINT valid_time_range CHECK (end_time > start_time)
) PARTITION BY RANGE (start_time);
-- Create partitions for each month
CREATE TABLE events_2025_01 PARTITION OF events
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
-- ... (additional partitions)
CREATE INDEX idx_events_calendar_id ON events(calendar_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_events_organizer_id ON events(organizer_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_events_time_range ON events(start_time, end_time) WHERE deleted_at IS NULL;
CREATE INDEX idx_events_search ON events USING gin(to_tsvector('english', title || ' ' || description));
-- Attendees Table
CREATE TABLE attendees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES events(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id),
email VARCHAR(255) NOT NULL,
name VARCHAR(255),
response_status VARCHAR(20) DEFAULT 'NEEDS_ACTION',
is_optional BOOLEAN DEFAULT FALSE,
comment TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT valid_response_status CHECK (response_status IN ('NEEDS_ACTION', 'ACCEPTED', 'DECLINED', 'TENTATIVE')),
CONSTRAINT unique_event_attendee UNIQUE (event_id, email)
);
CREATE INDEX idx_attendees_event_id ON attendees(event_id);
CREATE INDEX idx_attendees_user_id ON attendees(user_id);
-- Resources Table
CREATE TABLE resources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
location_building VARCHAR(100),
location_floor INTEGER,
location_room VARCHAR(50),
capacity INTEGER,
features TEXT[],
is_available BOOLEAN DEFAULT TRUE,
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT valid_type CHECK (type IN ('ROOM', 'EQUIPMENT', 'PARKING', 'OTHER'))
);
CREATE INDEX idx_resources_type ON resources(type) WHERE deleted_at IS NULL;
CREATE INDEX idx_resources_features ON resources USING gin(features);
-- Reservations Table
CREATE TABLE reservations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
resource_id UUID NOT NULL REFERENCES resources(id),
event_id UUID NOT NULL REFERENCES events(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id),
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'CONFIRMED',
version INTEGER DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT valid_reservation_status CHECK (status IN ('CONFIRMED', 'CANCELLED', 'NO_SHOW')),
CONSTRAINT valid_reservation_time CHECK (end_time > start_time),
CONSTRAINT unique_resource_time UNIQUE (resource_id, start_time, end_time)
);
CREATE INDEX idx_reservations_resource_time ON reservations(resource_id, start_time, end_time) WHERE status = 'CONFIRMED';
CREATE INDEX idx_reservations_event_id ON reservations(event_id);
4.2 データパーティショニング戦略¶
水平パーティショニング: - Events テーブル: 月単位でパーティション(過去のデータアクセス頻度低減) - Audit ログ: 日単位でパーティション(データ保持ポリシー適用容易化)
シャーディング:
- Phase 1: 単一データベース(~5M ユーザーまで)
- Phase 2: ユーザーIDベースのシャーディング(5M+ ユーザー)
- Shard キー: user_id % shard_count
- クロスシャードクエリは避け、アプリケーション層で集約
4.3 キャッシング戦略¶
Redis キャッシュ階層:
Cache Layers:
L1 (Application):
- In-memory cache (Go sync.Map)
- TTL: 30 seconds
- Use case: Hot data (current user's calendar)
L2 (Redis):
- Distributed cache
- TTL: 5 minutes (calendars), 2 minutes (events)
- Use case: Frequently accessed data
- Keys: "calendar:{id}", "event:{id}", "user:{id}:calendars"
L3 (Database Read Replicas):
- PostgreSQL read replicas (3 replicas)
- Load balancing: Round-robin
- Use case: Cache miss時のフォールバック
キャッシュ無効化: - Write-Through: 書き込み時に即座にキャッシュを更新 - Event-Driven Invalidation: Kafka イベントによる分散キャッシュ無効化 - TTL ベース: 定期的な自動削除
キャッシュキー設計:
calendar:{calendar_id}
user:{user_id}:calendars
event:{event_id}
event:{event_id}:attendees
resource:{resource_id}:availability:{date}
4.4 Elasticsearch インデックス設計¶
イベント検索インデックス:
{
"mappings": {
"properties": {
"event_id": { "type": "keyword" },
"calendar_id": { "type": "keyword" },
"organizer_id": { "type": "keyword" },
"title": {
"type": "text",
"analyzer": "standard",
"fields": {
"keyword": { "type": "keyword" }
}
},
"description": { "type": "text" },
"location": { "type": "text" },
"start_time": { "type": "date" },
"end_time": { "type": "date" },
"attendee_emails": { "type": "keyword" },
"attendee_names": { "type": "text" },
"tags": { "type": "keyword" },
"status": { "type": "keyword" },
"created_at": { "type": "date" },
"suggest": {
"type": "completion",
"contexts": [
{
"name": "user_id",
"type": "category"
}
]
}
}
},
"settings": {
"number_of_shards": 5,
"number_of_replicas": 2,
"refresh_interval": "5s"
}
}
検索クエリ例:
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "quarterly business review",
"fields": ["title^3", "description", "location"],
"fuzziness": "AUTO"
}
}
],
"filter": [
{
"range": {
"start_time": {
"gte": "2025-01-01",
"lte": "2025-12-31"
}
}
},
{
"term": { "status": "CONFIRMED" }
}
]
}
},
"sort": [
{ "start_time": "asc" }
],
"highlight": {
"fields": {
"title": {},
"description": {}
}
}
}
5. インフラストラクチャ¶
5.1 AWS アーキテクチャ¶
コンピュートリソース: - EKS Cluster: Kubernetes 1.28 - Node Groups: - General Purpose (t3.large): 10-50 nodes (auto-scaling) - Compute Optimized (c6i.xlarge): 5-20 nodes - Pod Auto-scaling: HPA + KEDA (Kubernetes Event Driven Autoscaling) - Cluster Autoscaler: ノード数の自動調整
データベース: - RDS PostgreSQL 15: - Instance: db.r6g.2xlarge (8 vCPU, 64 GB RAM) - Multi-AZ: 3 AZ deployment - Read Replicas: 3 replicas (異なるAZ) - Storage: 2 TB gp3 (16,000 IOPS) - Backup: 自動バックアップ (7日保持) + スナップショット
キャッシュ: - ElastiCache Redis: - Instance: cache.r6g.xlarge - Cluster Mode: Enabled (3 shards, 2 replicas per shard) - Total Capacity: 192 GB - Eviction Policy: allkeys-lru
メッセージング: - MSK (Managed Streaming for Kafka): - Kafka Version: 3.5 - Brokers: 6 brokers (2 per AZ) - Instance: kafka.m5.xlarge - Storage: 1 TB per broker - Topics: calendar.events, notifications, sync.requests
ストレージ: - S3: - Attachments Bucket: Standard storage class - Backups Bucket: Glacier storage class - Lifecycle Policy: 30日後に Infrequent Access、90日後に Glacier
ネットワーク: - VPC: 10.0.0.0/16 - Public Subnets: 3 (各AZ) - Private Subnets: 3 (各AZ) - Database Subnets: 3 (各AZ) - NAT Gateway: 3 (各AZ、冗長性確保) - Application Load Balancer: - Cross-Zone Load Balancing: Enabled - SSL/TLS: AWS Certificate Manager - Target Groups: gRPC services
5.2 コスト見積もり¶
月間コスト概算(5,000ユーザー想定):
| リソース | 仕様 | 月額コスト |
|---|---|---|
| EKS Cluster | 1 cluster + 20 nodes (t3.large avg) | $2,880 |
| RDS PostgreSQL | db.r6g.2xlarge Multi-AZ + replicas | $3,200 |
| ElastiCache | cache.r6g.xlarge cluster | $1,100 |
| MSK | 6 kafka.m5.xlarge brokers | $2,160 |
| S3 | 500 GB storage + transfer | $50 |
| ALB | 2 load balancers | $40 |
| Data Transfer | 5 TB/month | $450 |
| CloudWatch | Metrics + Logs | $200 |
| 合計 | $10,080/月 |
年間コスト: 約 \(120,960 (約 ¥18M、1\) = 150円換算)
スケーリングシナリオ: - 10,000 ユーザー: 約 $18,000/月 - 50,000 ユーザー: 約 $45,000/月
6. セキュリティアーキテクチャ¶
6.1 認証・認可¶
認証 (AuthN): - OAuth 2.0 + OpenID Connect: - Authorization Server: Auth0 / AWS Cognito - Token Type: JWT (JSON Web Token) - Token Lifetime: Access Token (15分), Refresh Token (7日) - MFA: TOTP (Time-based One-Time Password) 必須
認可 (AuthZ): - RBAC (Role-Based Access Control):
Roles:
- Admin: Full access to all resources
- Owner: Full access to owned calendars/events
- Editor: Can modify calendar/events
- Viewer: Read-only access
- Guest: Limited access to specific events
- ABAC (Attribute-Based Access Control):
- リソース属性(visibility, sharing_settings)に基づく動的アクセス制御
- ポリシー例: "Private events は owner と explicit attendees のみアクセス可能"
API認証:
// JWT Validation Middleware
func ValidateJWT(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := extractToken(r)
// Verify signature
claims, err := verifyToken(token)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Check expiration
if claims.ExpiresAt < time.Now().Unix() {
http.Error(w, "Token expired", http.StatusUnauthorized)
return
}
// Add user context
ctx := context.WithValue(r.Context(), "user_id", claims.UserID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
6.2 データ保護¶
暗号化: - 転送中: TLS 1.3 (全通信) - 保存時: - RDS: AES-256 encryption at rest - S3: Server-Side Encryption (SSE-S3) - Redis: Encryption in transit and at rest - Application Level: - PII (個人識別情報) は追加の暗号化層(AES-256-GCM) - Encryption Keys: AWS KMS (Key Management Service)
データマスキング:
// Sensitive data masking
func MaskEmail(email string) string {
parts := strings.Split(email, "@")
if len(parts) != 2 {
return "***"
}
username := parts[0]
domain := parts[1]
if len(username) <= 2 {
return "**@" + domain
}
return username[:2] + "***@" + domain
}
// Example: john.doe@example.com → jo***@example.com
データ保持ポリシー: - アクティブデータ: 無期限(ユーザーがアクティブな限り) - 削除済みデータ: 30日間ソフトデリート後に完全削除 - 監査ログ: 7年間保持(コンプライアンス要件) - バックアップ: 90日間保持
6.3 ネットワークセキュリティ¶
Defense in Depth:
Internet
↓
CloudFront + WAF (Layer 7 DDoS protection, SQL injection filtering)
↓
API Gateway (Rate limiting, IP whitelist)
↓
Application Load Balancer (SSL termination)
↓
EKS Cluster (Network Policies, Security Groups)
↓
Services (mTLS, Service Mesh)
↓
Data Layer (Private subnets, No internet access)
Network Policies (Kubernetes):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: calendar-service-policy
spec:
podSelector:
matchLabels:
app: calendar-service
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: bff-layer
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
6.4 コンプライアンス¶
GDPR対応: - Right to Access: ユーザーデータのエクスポートAPI提供 - Right to Erasure: 完全なデータ削除機能(30日以内) - Data Portability: 標準フォーマット(iCal, JSON)でのデータエクスポート - Consent Management: 明示的な同意管理システム
監査: - すべての API アクセスをログに記録 - 監査ログは改ざん防止(Write-Once storage) - 定期的なセキュリティ監査(四半期ごと) - ペネトレーションテスト(年2回)
7. 可観測性とモニタリング¶
7.1 メトリクス収集¶
Prometheusメトリクス:
// Custom metrics
var (
eventCreationDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "event_creation_duration_seconds",
Help: "Duration of event creation operations",
Buckets: prometheus.DefBuckets,
},
[]string{"status"},
)
activeConnections = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "active_websocket_connections",
Help: "Number of active WebSocket connections",
},
)
cacheHitRate = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_hits_total",
Help: "Total number of cache hits",
},
[]string{"cache_layer", "key_type"},
)
)
収集するメトリクス: - Infrastructure: CPU, Memory, Disk I/O, Network - Application: Request rate, Error rate, Duration (RED method) - Business: Events created, Meetings scheduled, Active users - Database: Query latency, Connection pool, Deadlocks - Cache: Hit rate, Miss rate, Evictions
7.2 分散トレーシング¶
Jaeger統合:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
func CreateEvent(ctx context.Context, req *CreateEventRequest) (*Event, error) {
tracer := otel.Tracer("calendar-service")
ctx, span := tracer.Start(ctx, "CreateEvent")
defer span.End()
// Validate request
ctx, validateSpan := tracer.Start(ctx, "ValidateRequest")
if err := validateRequest(req); err != nil {
validateSpan.SetStatus(codes.Error, err.Error())
validateSpan.End()
return nil, err
}
validateSpan.End()
// Save to database
ctx, dbSpan := tracer.Start(ctx, "SaveToDatabase")
event, err := db.SaveEvent(ctx, req)
dbSpan.End()
// Publish event
ctx, publishSpan := tracer.Start(ctx, "PublishKafkaEvent")
err = kafka.Publish(ctx, "calendar.events", event)
publishSpan.End()
span.SetAttributes(
attribute.String("event.id", event.ID),
attribute.String("calendar.id", event.CalendarID),
)
return event, nil
}
トレース戦略: - すべてのサービス間呼び出しをトレース - データベースクエリの詳細なトレース - 外部API呼び出しのトレース - サンプリングレート: 100%(開発)、10%(本番)
7.3 ログ管理¶
構造化ログ:
import "go.uber.org/zap"
logger, _ := zap.NewProduction()
logger.Info("Event created",
zap.String("event_id", event.ID),
zap.String("user_id", user.ID),
zap.String("calendar_id", calendar.ID),
zap.Time("start_time", event.StartTime),
zap.Duration("duration", time.Since(start)),
)
ログレベル: - ERROR: システムエラー、例外 - WARN: 予期しない状況、リトライ - INFO: 重要なビジネスイベント - DEBUG: 詳細なデバッグ情報(開発環境のみ)
ELKスタック: - Elasticsearch: ログストレージ - Logstash: ログ集約、パース - Kibana: 可視化、検索 - Retention: 30日間(ホット)、90日間(ウォーム)、365日間(コールド)
7.4 アラート設定¶
重要なアラート:
Alerts:
- name: HighErrorRate
condition: error_rate > 5%
duration: 5m
severity: critical
notification: PagerDuty
- name: HighLatency
condition: p95_latency > 500ms
duration: 10m
severity: warning
notification: Slack
- name: DatabaseConnectionPoolExhausted
condition: db_connections > 90%
duration: 5m
severity: critical
notification: PagerDuty
- name: CacheHitRateLow
condition: cache_hit_rate < 70%
duration: 15m
severity: warning
notification: Slack
- name: KafkaConsumerLag
condition: consumer_lag > 10000
duration: 10m
severity: critical
notification: PagerDuty
8. ディザスタリカバリ¶
8.1 バックアップ戦略¶
RDS バックアップ: - 自動バックアップ: 毎日 3:00 AM UTC - スナップショット: 週次手動スナップショット(日曜日) - クロスリージョンレプリケーション: us-west-2 (DR site) - リストア時間: 30分以内(ポイントインタイムリカバリ)
S3バックアップ: - Versioning: Enabled - Cross-Region Replication: DR site - Lifecycle Policy: 90日後に Glacier
8.2 DR計画¶
RPO (Recovery Point Objective): 5分 RTO (Recovery Time Objective): 1時間
フェイルオーバー手順: 1. DNS フェイルオーバー(Route 53 Health Check) 2. DRサイトのRDSリードレプリカを昇格 3. アプリケーションをDRリージョンで起動 4. トラフィックをDRサイトにルーティング
定期的な訓練: - DR訓練: 四半期ごと - カオスエンジニアリング: 月次(本番環境で実施)
ドキュメントバージョン: 1.0 最終更新日: 2025-12-05 レビュアー: Technical Architecture Review Board 次回レビュー: 2025-03-05