시스템 설계 : 캘린더 앱
I’m happy to help translate the article, but I need the full text you’d like translated. Could you please paste the content (excluding the source line you already provided) so I can translate it into Korean while preserving the formatting and code blocks?
기능 요구 사항
- 이벤트 생성, 이벤트 수정, 이벤트 취소
- 캘린더를 일간, 주간, 연간으로 보기
- 반복 회의 설정
- 변경 사항을 이메일로 알림 전송
Non‑Functional Requirements
- High availability → consistency (eventual consistency for syncing events)
- Should support 1 billion users
- Low latency for viewing the calendar (read‑heavy > write‑heavy)
데이터 모델
- User
- Event
- Recurrence
API
POST /events/
{
"title": "Meeting title",
"userId": "creator_id", // creator
"userIds": ["participant1", "participant2"], // optional list of attendees
"startTime": "2025-01-10T15:00:00Z",
"endTime": "2025-01-10T16:00:00Z",
"content": { // JSON blob: video call link, description, etc.
"videoCallLink": "https://example.com/meet/123",
"description": "Discuss project status"
},
"recurrence": "weekly|biweekly|monthly|yearly" // optional
}
GET /events?startDay=&endDay=
요청된 날짜 범위에 해당하는 이벤트 목록을 반환합니다.
고수준 설계
(구성 요소, 캐싱 레이어, 데이터베이스 및 클라이언트 동기화 개요.)
Deep Dives
1. 일일 또는 반복 이벤트 저장
이벤트 생성 및 저장
Case 1 – Simple (One‑Time) Event
예시: 병원 예약
| event_id | owner_id | title | start_time_utc | end_time_utc | rrule | tz | version |
|---|---|---|---|---|---|---|---|
| evt_101 | user_1 | Doctor Appointment | 2025‑01‑10 15:00 | 2025‑01‑10 16:00 | NULL | UTC | 1 |
행이 그대로 반환되고 렌더링됩니다; 확장이 필요하지 않습니다.
Case 2 – Recurring Event (Weekly)
예시: 팀 동기화
| event_id | owner_id | title | start_time_utc | end_time_utc | rrule | tz | version |
|---|---|---|---|---|---|---|---|
| evt_201 | user_1 | Team Sync | 2025‑01‑06 10:00 | 2025‑01‑06 11:00 | FREQ=WEEKLY;BYDAY=MO | UTC | 1 |
GET behavior (Week view) – 기본 행을 가져와 요청된 범위에 대해서만 규칙을 확장하고, 메모리에서 발생을 생성합니다.
Case 3 – Recurring Event with Exception (One Cancellation)
예시: 1월 20일에 취소된 팀 동기화
event_exceptions 테이블:
| exception_id | event_id | exception_date | type |
|---|---|---|---|
| ex_301 | evt_201 | 2025‑01‑20 | CANCELLED |
GET behavior (Week of Jan 20) – 주간 규칙을 확장하고 예외와 매칭하여 해당 발생을 제외한 나머지 이벤트를 반환합니다.
저장 요약
- 일회성 이벤트 → 1 행
- 수년간의 주간 반복 → 1 행
- 주간 + N 개의 예외 → 1 + N 행
결과: 최소한의 저장 공간.
2. 뷰 생성 및 저지연
사용자 흐름: 서버가 이벤트 목록을 클라이언트에 전송합니다.
최적화 – Redis 캐시
- 클라이언트가 이벤트를 요청합니다.
- 서버가 Redis를 확인합니다:
- Cache hit → 캐시된 이벤트 반환.
- Cache miss → DB를 조회하고 Redis에 채운 뒤 결과 반환.
장점: 빠른 읽기, DB 부하 감소.
단점: 캐시 무효화 복잡성, 최종 일관성 지연.
결론: 하이브리드 접근 방식 – 클라이언트‑사이드 확장 + 오프라인 저장을 위한 SQLite 조합이 저지연 및 오프라인 기능을 제공합니다.
3. 충돌 감지 및 잠금 전략
- Optimistic Locking (default) – 경쟁이 적은 개인 캘린더에 적합; 서비스에서 재시도 로직 필요.
- Pessimistic Locking – 경쟁이 높은 캘린더에 사용; 레이스 컨디션 방지를 위해 여러 행을 잠급니다.
4. 다중 디바이스 동기화 (Push & Pull)
- Pull (Delta Sync) – 초기 시작, 재연결, 놓친 업데이트에 사용.
- Push (SSE / Push Notifications) – 업데이트된 규칙을 가져와 로컬에서 재확장하고 SQLite와 UI를 업데이트합니다. SSE가 선호됩니다: 단방향, 가벼움, 배터리 친화적.
- Hybrid model – 신선도는 푸시, 정확도는 풀을 조합.
5. 데이터베이스 선택: SQL vs NoSQL
SQL이 잘 맞는 이유
- 쓰기량이 적당해 관계형 DB가 충분히 확장됩니다.
- 트랜잭션으로 충돌 검사가 간단합니다.
- 반복 쿼리(RRULE 확장)가 관계형 모델에 자연스럽게 들어맞습니다.
- 강한 일관성(또는 구성 가능한 최종 일관성)을 보장하기 쉽습니다.
NoSQL 트레이드‑오프
- 트랜잭션이 없으면 충돌 검사가 어렵습니다.
- 반복 쿼리는 부적합합니다.
- 기본적으로 최종 일관성이므로 정확성을 위해 추가 복잡성이 필요합니다.
- 동일한 보장을 위해 구현 복잡도가 높아집니다.