🏗️ Meeting Scheduler — High-Level Design¶
Target Level: Senior/Staff Engineer Focus: Calendar management, conflict detection, room booking, notifications
1. SYSTEM OVERVIEW¶
Purpose: Enterprise meeting scheduling system with room booking, participant management, and calendar sync.
Scale: 10K employees, 100K meetings/month, thousands of rooms across buildings.
2. SYSTEM ARCHITECTURE¶
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Web App │ │ Mobile App │ │ Outlook/ │
│ (Calendar) │ │ (Calendar) │ │ Google Sync │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌────────────▼────────────┐
│ API Gateway │
└────────────┬────────────┘
│
┌────────────┴────────────┐
│ Meeting Scheduler │
│ (Java - Spring Boot) │
├─────────────────────────┤
│ Conflict Detection │
│ Room Booking │
│ Notification │
└────────────┬────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ PostgreSQL │ │ Redis │ │ Message Q │
│ (Meetings) │ │ (Calendar) │ │ (RabbitMQ) │
└─────────────┘ └─────────────┘ └─────────────┘
Figure: clients, scheduler service and stores.
flowchart TB
W["Web app"] --> G["API gateway"]
M["Mobile app"] --> G
O["Outlook / Google sync"] --> G
G --> S["Meeting scheduler: conflicts, rooms, notifications"]
S --> P[("PostgreSQL: meetings")]
S --> R[("Redis: calendar cache")]
S --> Q["Message queue"]
3. MEETING LIFECYCLE¶
Figure: meeting lifecycle.
stateDiagram-v2
[*] --> SCHEDULED
SCHEDULED --> ONGOING
ONGOING --> COMPLETED
SCHEDULED --> CANCELLED
ONGOING --> RESCHEDULED
RESCHEDULED --> SCHEDULED
4. CONFLICT DETECTION¶
| Algorithm | Approach | Complexity |
|---|---|---|
| Sorted map of non-overlapping intervals | TreeMap floor/higher neighbours (what the LLD uses) |
O(log n) per check |
| Interval tree | Needed only when stored intervals may overlap (e.g. a person's tentative + accepted invites) | O(log n + k) |
| Linear scan | Check all bookings | O(n) |
| Database query | start_at < :end AND end_at > :start on index (room_id, start_at) |
O(log n + k) |
| Database constraint | Postgres EXCLUDE USING gist (room_id WITH =, tstzrange(start_at, end_at) WITH &&) |
Enforced on commit; the only check that is safe across many app instances |
5. RECURRENCE HANDLING¶
| Pattern | Implementation |
|---|---|
| Daily | Create instances for each day |
| Weekly | Same day-of-week, time, room |
| Monthly | Same day-of-month |
| Exception | Cancel/modify single instance, leave series |
6. TRADE-OFF ANALYSIS¶
| Decision | Choice | Rationale |
|---|---|---|
| Conflict detection | DB exclusion constraint is authoritative; in-memory/cached calendars only for suggestions | With several service instances, an in-memory check alone cannot prevent two instances booking the same room |
| Calendar storage | PostgreSQL source of truth + Redis free/busy cache | Cache is invalidated on every write to the person's or room's calendar; stale cache only affects suggestions |
| Notifications | Async via Observer pattern | Non-blocking, extensible |
| Granularity | 15-minute slots | Standard enterprise calendar |
7. CONSISTENCY, IDEMPOTENCY & FAILURE MODES¶
Write path: a booking inserts one row per attendee calendar plus the room row in a single transaction. The room exclusion constraint makes double-booking a room impossible; for people, a conflict is usually allowed with a warning (people double-book themselves on purpose), so it is a check, not a constraint. Partition by tenant (company): every booking is single-tenant, so transactions stay on one shard.
Figure: booking write path; the room exclusion constraint decides concurrent bookings.
sequenceDiagram
participant C as Client
participant S as Scheduler
participant D as PostgreSQL
C->>S: Create meeting (idempotency key)
S->>D: One txn: room row, attendee rows, outbox
alt overlap on room
D-->>S: Exclusion constraint violation
S-->>C: 409 Conflict
else free
D-->>S: Commit
S-->>C: Created
end
Read path (free/busy): the expensive query is "when are these 8 people free this week". Precompute per person per day a busy bitmap at 15-minute resolution (96 bits/day), cache it, and AND the bitmaps of all attendees. Invalidate on any write to that person's calendar.
| Failure | Response |
|---|---|
| Client retries a create after a timeout | Idempotency key on create (UNIQUE(tenant_id, idempotency_key)), so the retry returns the existing meeting |
| Two instances book the same room concurrently | Exclusion constraint: one commit fails with a constraint violation, which maps to 409 Conflict |
| Invite email / push fails | Notifications go through an outbox table written in the booking transaction and are delivered by a worker with retries; the booking is never rolled back because email failed |
| External calendar sync (Google/Exchange) diverges | Treat sync as eventually consistent: store external ids and etags, sync incrementally (watch channels / sync tokens), resolve conflicts by last-writer-wins on non-time fields and by re-validating time changes against room constraints |
| Stale free/busy cache | Suggestions may be wrong for a few seconds; the booking transaction re-validates, so correctness is unaffected |
Capacity: 10K employees × ~5 meetings/day ≈ 50K meeting rows/day and ~250K attendee rows/day: small. Free/busy lookups dominate (every scheduling UI open triggers one for each attendee); with bitmaps one person-week is 7 × 12 bytes, so a full company-week of free/busy fits in about 1 MB of cache.