Local-first-приложения: что это, как работают и почему это важно
Долгие годы стандартная архитектура современного ПО была простой:
Приложение работает на вашем устройстве. Данные хранятся где-то ещё.
Вы открываете приложение, вносите изменение, отправляете запрос на сервер и ждёте, пока удалённая система его обработает.
Когда сеть быстрая, эта модель работает на удивление хорошо.
Но что происходит, когда сеть медленная?
Что происходит, когда вы офлайн?
Что происходит, когда вы хотите перенести свои данные в другое приложение?
И, пожалуй, самый важный вопрос:
Что произойдёт с вашими данными, если сервис, который их хранит, исчезнет?
Local-first-подход предлагает другой ответ.
Вместо того чтобы считать сервер центром приложения, он рассматривает локальную копию данных пользователя как полноценную часть системы.
Сеть становится тем, что связывает ваши данные, а не тем, от чего они принципиально зависят.
Интернет должен соединять ваши данные — а не владеть ими.
Эта статья объясняет, что такое local-first-приложения, чем они отличаются от offline-first и cloud-first архитектур, как работают синхронизация и CRDT, каковы компромиссы и почему local-first становится всё более важным способом думать о ПО.
Что такое local-first-приложения?
Local-first — это подход к архитектуре приложений, при котором локальные данные пользователя доступны и полезны независимо от сети.
Приложение сначала читает и записывает локальные данные.
Синхронизация с удалёнными системами происходит отдельно.
Упрощённая модель выглядит так:
┌───────────────┐
│ Laptop │
│ Local Data │
└───────┬───────┘
│
│ sync
▼
┌───────────────┐
│ Sync / Relay │
│ Server │
└───────┬───────┘
│
│ sync
▼
┌───────────────┐
│ Phone │
│ Local Data │
└───────────────┘
Важно не просто то, что данные кэшируются локально.
Локальная копия является частью рабочей модели данных приложения.
Вы можете открыть приложение.
Вы можете читать свои документы.
Вы можете вносить изменения.
Вы можете создавать новые данные.
И вам не обязательно ждать удалённый сервер, прежде чем приложение отреагирует.
Когда появляется соединение, локальные изменения могут синхронизироваться с другими репликами.
Так возникают принципиально иные отношения между пользователем, приложением, сетью и сервером.
Cloud-first: традиционная модель
Большинство SaaS-приложений построено вокруг централизованной модели:
User
│
▼
Application
│
▼
API
│
▼
Database
Обычно сервер содержит авторитетное состояние.
Клиент — прежде всего интерфейс к этому состоянию.
У такой архитектуры огромные преимущества.
Централизованные данные относительно легко анализировать и понимать.
Транзакции проще.
Контроль доступа централизован.
Резервные копии централизованы.
Поиск и аналитика просты.
Для многих приложений это именно правильная архитектура.
Но она также создаёт зависимости.
Если сеть недоступна, приложение может потерять доступ к важному функционалу.
Если сервер недоступен, приложение может стать непригодным.
А если провайдер закрывается, доступ пользователя к его данным может зависеть от того, предлагает ли провайдер рабочий экспорт.
Таким образом, архитектура устанавливает неявную связь:
Application
↓
Server
↓
Database
↓
Your data
Local-first переворачивает эту связь.
Local-first: другая модель
Local-first-приложение может вместо этого выглядеть так:
┌──────────────────┐
│ Application │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Local Data │
│ Source of Work │
└────────┬─────────┘
│
│ background sync
▼
┌──────────────────┐
│ Sync Server │
└────────┬─────────┘
│
▼
Other Devices /
Remote Backup
Сервер не исчез.
Изменилась его роль.
Он по-прежнему может обеспечивать:
- синхронизацию
- аутентификацию
- совместный доступ
- совместную работу
- зашифрованные резервные копии
- уведомления
- поиск
- биллинг
- контроль доступа
- обнаружение устройств
Но он не обязан участвовать в каждом взаимодействии с данными пользователя.
Это различие и есть основа local-first архитектуры.
Local-first против offline-first
Эти понятия часто считают синонимами.
Это не так.
Offline-first
Offline-first означает:
Приложение должно продолжать работать, когда сеть недоступна.
Типичное offline-first-приложение может поддерживать локальный кэш:
Application
│
▼
Local Cache
│
▼
Sync Queue
│
▼
Server
Сервер при этом может оставаться конечным источником истины.
Локальная копия существует прежде всего для того, чтобы приложение было устойчиво к перебоям сети.
Local-first
Local-first идёт дальше.
Он спрашивает:
Почему сеть вообще должна требоваться для обычного взаимодействия с данными пользователя?
Локальное представление становится основой приложения:
Local Data
↕
Synchronization
↕
Remote Replica
Различие тонкое, но важное.
Offline-first — прежде всего стратегия надёжности.
Local-first — архитектурная модель и модель владения данными.
Полезная мысленная модель
Offline-first:
«Что должно происходить, когда я офлайн?»
Local-first:
«Почему для этой операции вообще нужно быть онлайн?»
Семь идеалов local-first-приложений
На современное обсуждение local-first сильно повлияло эссе 2019 года Local-first software: You own your data, in spite of the cloud от Ink & Switch.
В эссе описаны семь идеалов, которые остаются полезной рамкой для оценки local-first систем.
1. Быстрота
Локальные взаимодействия могут быть чрезвычайно быстрыми, потому что не требуют сетевых round trip.
Вместо:
Click
↓
Request
↓
Internet
↓
Server
↓
Database
↓
Response
↓
UI update
можно получить:
Click
↓
Local update
↓
UI update
↓
Background sync
Это может радикально изменить ощущение от приложения.
Ввод текста перестаёт зависеть от сетевой задержки.
Открытие документа не требует запроса.
Перемещение заметки между папками не ждёт сервер.
Сеть становится деталью реализации, а не ощутимой частью каждого взаимодействия.
2. Несколько устройств
Local-first не означает хранение всего на одном устройстве.
Скорее наоборот.
Цель — позволить нескольким устройствам поддерживать полезные локальные копии одних и тех же данных.
Например:
Your Data
│
┌───────────┼───────────┐
▼ ▼ ▼
Laptop Phone Desktop
│ │ │
└───────────┼───────────┘
│
Sync
Каждое устройство может работать независимо.
Синхронизация выравнивает реплики, когда есть соединение.
Поэтому использование на нескольких устройствах — это уже не постоянное получение последнего состояния с сервера, а поддержание синхронизированных реплик.
3. Сеть необязательна
Local-first-приложение должно оставаться полезным, когда соединение пропадает.
Представьте, что вы редактируете документ в поездке.
Сеть исчезает.
Традиционное приложение может показать:
Не удаётся подключиться.
Local-first-приложение просто продолжит работу.
Ваши изменения сохраняются локально.
Приложению не нужно останавливаться.
Когда соединение возвращается, синхронизация возобновляется.
Это особенно ценно для:
- путешествий
- мобильной работы
- ненадёжных сетей
- удалённых мест
- выездной работы
- сред, чувствительных к приватности
Возможность работы офлайн — не аварийный режим.
Это естественное следствие архитектуры.
4. Бесшовная совместная работа
На первый взгляд local-first-приложения плохо сочетаются с совместной работой.
Если у каждого устройства своя копия данных, как несколько человек могут редактировать один документ?
Здесь на первый план выходят распределённые системы.
Представьте двух человек, работающих офлайн:
Shared Document
│
┌─────────┴─────────┐
▼ ▼
User A User B
Change A Change B
│ │
└─────────┬─────────┘
▼
Merge
│
▼
Shared State
Системе нужен способ согласовать независимые изменения.
Это одна из самых сложных технических задач в local-first-приложениях.
И именно здесь становятся полезны технологии вроде CRDT.
5. Долгосрочное владение данными
Local-first — не только о производительности.
Это ещё и о том, что происходит с вашими данными со временем.
Возьмём обычный SaaS-продукт.
Ваши данные могут фактически существовать внутри:
Application
↓
Company infrastructure
↓
Company database
Если компания меняет продукт, тарифы, политику хранения или бизнес-модель, ваш доступ к этой среде тоже может измениться.
Local-first-системы могут уменьшить эту зависимость, сохраняя значимые копии данных на устройствах пользователя.
Это не гарантирует переносимость автоматически.
Проприетарная локальная база данных всё ещё может вас запереть.
Настоящее владение данными требует большего, чем локальное хранилище.
Ему помогают:
- открытые форматы
- возможность экспорта
- читаемые файлы
- долговечные схемы
- резервные копии под контролем пользователя
- прозрачная синхронизация
Поэтому сильнейшие local-first-системы рассматривают переносимость данных как часть архитектуры, а не как запоздалую мысль.
6. Приватность
Local-first архитектура может также улучшить модель приватности приложения.
Централизованное облачное приложение часто выглядит так:
User
↓
Provider infrastructure
↓
Provider database
Инфраструктура провайдера становится центральным местом, содержащим большие объёмы пользовательских данных.
Local-first допускает другую модель:
User device
↓
Local data
│
└── encrypted synchronization
↓
Cloud replica
Это может существенно сократить объём информации, которую нужно централизовать.
Но есть важная оговорка:
Local-first не означает приватность автоматически.
Приложение может хранить данные локально и всё равно загружать всё на сервер.
Приватность зависит от всей системы:
- что хранится локально
- что синхронизируется
- зашифрована ли синхронизация
- кто контролирует ключи шифрования
- какая телеметрия собирается
- может ли сервер читать пользовательский контент
- как работают резервные копии
- как работает аутентификация
Поэтому local-first — это архитектурная возможность для приватности, а не её гарантия.
7. Контроль пользователя
Последняя идея связывает техническую архитектуру с более широким принципом:
Программное обеспечение должно оставаться полезным для пользователя, даже когда сервис вокруг него меняется.
Если ваши данные хранятся в переносимом формате на вашем собственном устройстве, приложение становится менее критичным для дальнейшего существования данных.
Идеальные отношения становятся такими:
You own the data.
The application helps you use it.
The cloud helps you synchronize it.
А не такими:
The company owns the infrastructure.
The application provides access.
Your data exists inside the service.
Это различие — один из сильнейших аргументов в пользу local-first-приложений.
Что такое CRDT?
CRDT расшифровывается как Conflict-free Replicated Data Type — реплицируемый тип данных без конфликтов.
CRDT — это структуры данных, предназначенные для того, чтобы независимо изменённые реплики сходились к согласованному состоянию.
Представьте два устройства.
У обоих один и тот же документ.
Затем они уходят в офлайн.
Устройство A вносит одно изменение.
Устройство B — другое.
Initial State
│
┌────────┴────────┐
▼ ▼
Device A Device B
Change A Change B
│ │
└────────┬────────┘
▼
Merge
│
▼
Final State
Важное свойство в том, что у системы есть чётко определённый способ объединить изменения.
CRDT позволяют сделать это, не требуя, чтобы все устройства постоянно общались с центральным сервером.
Однако:
CRDT — это не определение local-first.
Local-first-приложения могут использовать другие стратегии синхронизации.
И само по себе использование CRDT не делает приложение local-first автоматически.
CRDT — инструмент для решения одной конкретной задачи распределённых систем.
Разрешение конфликтов — сложная задача
Синхронизация становится особенно интересной, когда изменения конфликтуют.
Представьте, что общая заметка изначально содержит:
Buy milk
Пока устройства офлайн:
Устройство A меняет её на:
Buy oat milk
Устройство B меняет её на:
Buy almond milk
Каким должен быть итоговый результат?
Упрощённая система может использовать:
Last write wins
Но это может молча уничтожить информацию.
Разные типы данных требуют разных стратегий слияния.
Например:
| Тип данных | Возможная стратегия |
|---|---|
| Счётчик | Математическое слияние |
| Множество | Объединение / семантика множества |
| Текст | Посимвольное или структурное слияние |
| Упорядоченный список | Слияние с учётом позиций |
| Форматированный документ | Структурное слияние |
| Состояние приложения | Правила предметной области |
Именно поэтому local-first-приложения — не просто фронтенд-оптимизация.
Это в основе своей задача распределённых данных.
Local-first не означает «без сервера»
Ещё одно распространённое заблуждение:
Local-first означает, что всё должно быть peer-to-peer и никакого облака нет.
Не обязательно.
Local-first-приложение по-прежнему может сильно опираться на облачную инфраструктуру.
Например:
User Devices
┌──────┴──────┐
▼ ▼
Local DB Local DB
│ │
└──────┬──────┘
▼
Sync Server
│
┌───────────┼───────────┐
▼ ▼ ▼
Backup Sharing Discovery
Сервер может приносить огромную пользу.
Ключевое отличие в том, что сервер не обязательно является единственным местом, где приложение пользователя может работать.
Это делает local-first и облачную инфраструктуру взаимодополняющими, а не взаимоисключающими.
Практическая local-first архитектура
Современное веб-приложение может реализовать local-first архитектуру с помощью нескольких слоёв.
┌─────────────────────────────────────────┐
│ UI / PWA │
├─────────────────────────────────────────┤
│ Application Logic │
├─────────────────────────────────────────┤
│ Local Database │
│ IndexedDB / SQLite / OPFS │
├─────────────────────────────────────────┤
│ Synchronization Layer │
├─────────────────────────────────────────┤
│ Network / WebSocket │
└────────────────────┬────────────────────┘
│
▼
┌───────────────┐
│ Sync Backend │
├───────────────┤
│ Auth │
│ Replication │
│ Sharing │
│ Backup │
└───────┬───────┘
│
▼
Server DB
Возможные технологии включают:
- IndexedDB
- OPFS
- SQLite
- WebAssembly
- WebSockets
- WebRTC
- CRDT
- журналы событий
- журналы операций
- зашифрованное объектное хранилище
Но выбор технологий должен следовать за моделью данных.
Начинать с вопроса «Какой CRDT нам использовать?» часто неправильно.
Лучшая отправная точка:
Что является источником истины, какие данные нужно реплицировать и что должно происходить, когда реплики расходятся?
Почему local-first-приложения ощущаются иначе
Одно из самых интересных следствий local-first архитектуры — изменение UX приложения.
Традиционные облачные приложения часто выставляют состояние сети наружу:
Loading...
Saving...
Connecting...
Retrying...
Offline
Reconnecting...
Save failed.
Local-first-приложение часто может свести всё это к:
User action
↓
Local state
↓
Immediate UI update
↓
Background synchronization
Пользователю не нужно думать о сети.
Это может сделать приложение заметно более отзывчивым.
Сеть становится тем, что происходит вокруг работы пользователя, а не тем, что управляет его работой.
Сложные места local-first
У local-first есть значительные преимущества.
Но он приносит и значительную сложность.
Синхронизация
Несколько реплик могут меняться независимо.
Система должна определить, как эти изменения обмениваются и применяются.
Разрешение конфликтов
Не все конфликты можно разрешить автоматически.
Некоторые требуют правил предметной области.
Некоторые могут требовать вмешательства пользователя.
Хранение
Локальные данные могут становиться большими.
Приложению нужно думать о:
- индексации
- уплотнении
- миграциях
- лимитах хранилища
- резервных копиях
- вытеснении кэша
Безопасность
Данные могут существовать на нескольких устройствах.
Скомпрометированное устройство может раскрыть локально сохранённую информацию.
Поэтому шифрование и управление ключами становятся особенно важными.
Аутентификация
Приложение, работающее офлайн, не может предполагать, что каждое действие можно проверить живым запросом аутентификации.
Идентичность, сессии и авторизация требуют тщательного проектирования.
Миграции схемы
Устройство может быть офлайн неделями или месяцами.
Когда оно снова подключается, его локальная база данных может использовать старую схему.
Система синхронизации должна безопасно это обрабатывать.
Сложность бэкенда не исчезает
Поиск, совместный доступ, биллинг, уведомления, аналитика и многие другие функции могут по-прежнему требовать серверной инфраструктуры.
Local-first меняет распределение сложности.
Он не устраняет сложность.
Когда local-first имеет смысл?
Local-first особенно привлекателен, когда пользователи создают или управляют ценными личными данными.
Примеры:
- приложения для заметок
- управление личными знаниями
- редакторы документов
- приложения для письма
- исследовательские инструменты
- творческое ПО
- управление проектами
- инструменты для разработчиков
- выездные приложения
- личные базы данных
Полезное правило:
Чем ценнее, персональнее и долговечнее данные, тем сильнее аргументы в пользу local-first.
Для чего-то вроде разового запроса погоды local-first может быть излишним.
Для десяти лет личных заметок, исследований, текстов и знаний вопрос становится гораздо важнее.
Когда local-first может не подойти
Local-first — не универсальная замена централизованным системам.
Некоторым приложениям нужен центральный авторитет.
Примеры систем, где:
- каждая операция должна быть глобально упорядочена
- транзакции должны быть строго согласованными
- результат определяется вычислениями на сервере
- регуляторные требования требуют централизованной обработки
- локальное хранилище создаёт неприемлемые риски безопасности
Возьмём банковскую транзакцию.
Нельзя просто записать:
Balance += $1,000
локально и объявить успех.
Системе нужна авторитетная проверка на сервере.
Правильная архитектура зависит от задачи.
Local-first — инструмент, а не религия.
Local-first и веб-приложения
Local-first не ограничен нативными десктопными приложениями.
Современные технологии браузера делают возможными всё более сложные local-first веб-приложения.
Progressive Web App может сочетать:
- локальное постоянное хранилище
- service workers
- IndexedDB
- OPFS
- WebAssembly
- фоновую синхронизацию
- WebSockets
Это позволяет создавать веб-приложения, где браузер — не просто тонкий клиент.
Он может стать настоящей средой выполнения приложений со своим постоянным слоем данных.
Это важный сдвиг.
Браузер может всё больше вести себя как платформа локальных приложений, а не просто как окно в удалённое приложение.
Самая важная мысленная модель
Понять local-first проще всего, если перестать думать о сервере как о месте, где живёт приложение.
Вместо этого думайте о приложении как о том, что живёт на устройствах пользователя.
Your Data
│
┌────────────┼────────────┐
▼ ▼ ▼
Laptop Phone Desktop
│ │ │
└────────────┼────────────┘
│
Sync
│
▼
Internet
Интернет соединяет ваши копии.
Ему не обязательно ими владеть.
В этом суть.
Будущее за local-first?
Local-first не означает, что облачное ПО исчезнет.
Облако невероятно полезно.
Централизованная инфраструктура упрощает многое:
- совместную работу
- резервные копии
- обнаружение
- общий доступ
- глобальный доступ
- вычисления на сервере
- платежи
- аналитику
Более интересная возможность — гибридная архитектура.
Application
│
┌──────────┴──────────┐
▼ ▼
Local-first data Cloud services
│ │
▼ ▼
User-owned state Sync / sharing /
backup / search /
infrastructure
В этой модели облако не исчезает.
Оно становится сервисом, который использует приложение, а не местом, где приложение принципиально существует.
Это гораздо более реалистичное будущее, чем «всё станет децентрализованным».
Local-first в конечном счёте о контроле
Технические концепции, лежащие в основе local-first, важны.
Локальные базы данных.
Репликация.
Синхронизация.
CRDT.
Разрешение конфликтов.
Шифрование.
Но это средства, а не конечная цель.
Более глубокий вопрос:
Кто в конечном счёте контролирует данные пользователя?
Хорошее local-first-приложение должно стремиться давать несколько гарантий:
- приложение остаётся полезным без сетевого соединения
- локальные изменения мгновенны
- синхронизация происходит в фоне
- данные могут существовать на устройствах пользователя
- несколько устройств могут сходиться
- конфликты обрабатываются предсказуемо
- данные можно резервировать независимо
- данные можно экспортировать в долговечном формате
- провайдер — не единственное место, где существует информация пользователя
Это меняет отношения между ПО и его пользователями.
Приложение становится инструментом.
Облако становится инфраструктурой.
А данные остаются данными пользователя.
В этом обещание local-first-приложений.
Local-first в одном предложении
Если cloud-first ПО говорит:
Ваше приложение подключается к вашим данным.
Offline-first говорит:
Ваше приложение может продолжать работать, когда соединение исчезает.
Local-first говорит:
Ваши данные и приложение должны оставаться полезными, даже когда сети нет.
Это различие может показаться небольшим.
Архитектурно оно огромно.
Часто задаваемые вопросы
Local-first — то же самое, что offline-first?
Нет.
Offline-first сосредоточен на том, чтобы приложение оставалось работоспособным без сетевого соединения. Local-first рассматривает локальные данные как фундаментальную часть приложения и стремится минимизировать зависимость от удалённой инфраструктуры.
Означает ли local-first, что сервера нет?
Нет.
Сервер по-прежнему может обеспечивать синхронизацию, резервные копии, аутентификацию, общий доступ, поиск, уведомления и другие сервисы.
Требует ли local-first CRDT?
Нет.
CRDT — один из подходов к распределённой синхронизации и разрешению конфликтов. Возможны и другие подходы.
Local-first безопаснее?
Не автоматически.
Local-first может уменьшить централизацию и позволить строить более сильные архитектуры приватности, но безопасность по-прежнему зависит от шифрования, управления ключами, аутентификации, безопасности устройств и устройства синхронизации.
Может ли веб-приложение быть local-first?
Да.
Современные технологии браузера, такие как IndexedDB, OPFS, service workers и WebAssembly, делают возможными сложные local-first веб-приложения.
Что происходит, когда два устройства редактируют один документ офлайн?
Слой синхронизации должен согласовать изменения. В зависимости от модели данных это может включать CRDT, операционное преобразование, правила слияния предметной области или разрешение конфликтов с участием пользователя.
Устраняет ли local-first облачную инфраструктуру?
Нет.
Во многих практических системах облачная инфраструктура остаётся важной для синхронизации, резервных копий, общего доступа, обнаружения и других сервисов.
Каково главное преимущество local-first-приложений?
Сочетание скорости, устойчивости к работе офлайн, возможностей нескольких устройств, владения данными и меньшей зависимости от одного удалённого сервиса.
Каков главный недостаток?
Распределённая синхронизация значительно сложнее, чем создание обычного приложения с сервером в роли авторитета.
Заключительная мысль
Долгое время мы считали централизованное облачное хранилище неизбежной частью современного ПО.
Это не так.
Облако — мощный инструмент для соединения людей и устройств.
Но то, что сервер может хранить ваши данные, не означает, что он должен быть единственным местом, где ваши данные существуют.
Local-first-приложения предлагают другую модель:
Держите данные пользователя рядом. Пусть приложение работает локально. Синхронизируйтесь, когда это возможно. Используйте облако там, где оно приносит реальную пользу. И оставляйте контроль пользователю.
Интернет должен соединять ваши данные.
Ему не обязательно ими владеть.