System Design Cases
Offline-first architecture
Offline-first architecture concept page. Local-first apps (Linear, Figma offline, Obsidian). IndexedDB / SQLite-WASM is source of truth, sync engine (Replicache/ElectricSQL) replays mutations to a Sync API backed by Postgres. Three scenarios: offline edit drained on reconnect, CRDT conflict merge across two devices, optimistic UI rollback when server rejects. Carries an ADR comparing Replicache vs ElectricSQL vs custom sync.
Offline-first: local commit, idempotent sync и explicit conflicts
Offline-first означает, что критическая локальная задача имеет определённое поведение без сети. Это не обещание eventual success: storage может быть очищен, credentials истечь, сервер отклонить intent, а concurrent edits потребовать domain-specific merge или выбора пользователя.
Надёжная модель сначала атомарно сохраняет локальное состояние и outbox intent, затем повторяет sync с idempotency identity. Server остаётся authority для глобальных invariants и возвращает explicit version/conflict.
Проверяемые утверждения
- IndexedDB предоставляет транзакции в пределах заданного scope; related local record и outbox intent следует коммитить в одной readwrite transaction, если требуется атомарность.
- Background Sync имеет limited availability, поэтому foreground reconnect/manual retry остаётся обязательным correctness path.
- navigator.onLine или событие reconnect не доказывает доступность API; только конкретный request outcome определяет sync state.
- Idempotency устраняет повторный effect для одного operation ID, но не решает semantic conflict между разными concurrent operations.
Границы и компоненты
| Компонент | Роль в модели |
|---|---|
| Offline-capable UI | local readwrite transaction; announce durability state |
| Versioned Local Database | local readwrite transaction; same transaction intent; write reconciled version |
| Durable Operation Outbox | same transaction intent; claim pending operation |
| Restartable Sync Coordinator | claim pending operation; replay with identity; resolve explicit conflict |
| Authorized Idempotent API | replay with identity; authorize and commit |
| Authoritative Server State | authorize and commit |
| Domain Conflict Policy | resolve explicit conflict; write reconciled version |
| Accessible Sync Status | announce durability state |
Сценарии
Commit local data and intent atomically
While offline, the UI writes the edited record and a stable operation ID to stores in one IndexedDB readwrite transaction. It reports local durability only after transaction complete.
Проверяемый исход: A crash cannot leave a visible committed edit without its pending sync intent, and the user sees that server confirmation is still pending.
Replay the outbox idempotently
A foreground or supported background trigger starts a restartable worker. It reads one pending operation, sends idempotency and expected-version metadata, then marks acknowledged only after durable server result.
Проверяемый исход: Lost acknowledgements can cause duplicate delivery but not duplicate effect; uncertain operations remain retryable/reconcilable.
Resolve a concurrent version conflict
The server rejects an operation based on stale version. The coordinator invokes a domain policy: safe field merge, CRDT merge, user choice or explicit rejection; it never applies universal last-write-wins blindly.
Проверяемый исход: The reconciled local record names its server base/version and any unresolved user decision remains visible.
Survive missing background sync or quota
The platform lacks Background Sync or storage rejects a write. The app keeps a foreground/manual retry path, exposes the real durability state and never promises queued success before local commit.
Проверяемый исход: Unsupported APIs reduce convenience, not correctness; a failed local commit is shown as unsaved with recoverable user options.
Failure, concurrency и evolution checklist
- Make outbox processing restartable: pending, leased, acknowledged and terminal-rejected states need recovery after crash/termination.
- Use bounded exponential backoff with jitter and a retry ceiling/dead-letter user path; permanent authorization or validation errors are not transient.
- Handle schema upgrade with multiple tabs through versionchange/blocked behavior and a reversible migration or safe reset policy.
- Reconcile logout, account switch and token expiry before replay so one user cannot sync another user’s local partition.
Security и privacy
- Partition local records and operations by stable authenticated subject/tenant, clear or cryptographically isolate on logout, and never trust local ownership claims.
- Browser storage is available to same-origin script; XSS protection, minimal sensitive retention and server authorization remain required. Local encryption cannot protect against code running with the decryption capability.
- Validate operation type, object scope, expected version and idempotency identity server-side; bound payload size and retention.
Метрики, формулы и допущения
- A stable queue requires long-run
arrival_rate < effective_ack_rate; temporary bursts createbacklog_change ≈ (arrival_rate − ack_rate) × interval. minimum_drain_time ≥ pending_operations / sustained_ack_operations_per_second, ignoring new arrivals; include backoff and server limits in the measured sustained rate.local_bytes ≈ records + pending_payloads + indexes + engine_overhead; quota is implementation/user dependent and not a product constant.- Conflict rate is
operations_rejected_for_version / version-checked_operations; last-write-wins may reduce this counter by silently losing intent, so track user-visible loss separately.
Числа и bounds выше действуют только при названных units, population и assumptions. Ни паттерн, ни browser API сами по себе не задают SLA, capacity или correctness.
Решения для production review
- Name which tasks work offline, what local durability means and which outcomes require server confirmation.
- Choose conflict policy per domain object/field and global invariant; CRDT, merge, rejection and user choice are separate tools.
- Treat Background Sync as an optimization and preserve a foreground/manual path on every supported platform.
Первичные и официальные источники
- https://www.w3.org/TR/IndexedDB/
- https://www.w3.org/TR/service-workers/
- https://developer.mozilla.org/en-US/docs/Web/API/Background_Synchronization_API
- https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria
Scope note
Диаграмма показывает offline mutation lifecycle. Она не гарантирует permanent device storage, automatic background execution, multi-device convergence, end-to-end encryption или acceptance сервером каждого локального intent.