Offline Queue — это механизм, который сохраняет пользовательские операции локально, когда устройство находится вне сети, и отправляет их на сервер после восстановления соединения. Без офлайн-очереди пользователь теряет все действия, совершённые без интернета, что в мобильных приложениях недопустимо. По данным Google Developers (2025), внедрение offline-first архитектуры повышает удержание пользователей на 30% в регионах с нестабильным интернетом.
Главное
Offline Queue — это упорядоченная коллекция операций (создание, обновление, удаление), которые приложение сохраняет локально, когда устройство не имеет доступа к сети. Как только соединение восстанавливается, очередь отправляет операции на сервер в том же порядке, в котором пользователь их совершил.
Представьте сценарий: пользователь мессенджера набирает сообщения в метро без интернета. Каждое нажатие «Отправить» добавляется в Offline Queue. Когда поезд выезжает из тоннеля и сеть появляется, все сообщения отправляются автоматически. Пользовательский опыт — бесшовный: он не замечает, что был в офлайне, кроме лёгкой задержки при отправке.
По данным Uber Engineering (2024), их офлайн-очередь обрабатывает более 2 миллионов операций в день в регионах с низким качеством связи. Очередь использует локальное хранилище Room с FIFO-порядком и механизмом гарантированной доставки exactly-once.
data class QueuedOperation(
val id: String,
val type: OperationType,
val endpoint: String,
val payload: String,
val timestamp: Long,
val retryCount: Int = 0,
val idempotencyKey: String
)
Каждая операция содержит все данные, необходимые для повторной отправки: эндпоинт, тело запроса, временную метку и idempotencyKey. Room-БД гарантирует сохранность очереди при перезапуске приложения и сбоях ОС.
Гарантия доставки — основная задача очереди. Пользователь должен быть уверен, что его действие (отправка сообщения, лайк, заказ) будет выполнено, даже если сеть недоступна в момент выполнения. Offline Queue с механизмом retry обеспечивает доставку eventually.
Улучшение UX в условиях плохой связи — по данным GSMA Mobile Economy Report (2025), около 40% мобильных пользователей в мире имеют нестабильное интернет-соединение. Offline Queue делает приложение пригодным для использования в метро, лифтах, удалённых районах — везде, где связь прерывиста.
Снижение потери данных — без очереди все действия, совершённые в офлайне, теряются. Пользователь может заполнить длинную форму, нажать «Отправить» и увидеть ошибку сети — весь ввод пропадёт. Offline Queue сохраняет данные и отправляет их при первой возможности. Auto-save в Google Docs — классический пример офлайн-очереди для документов.
Асинхронная синхронизация — очередь позволяет приложению не блокировать UI на время отправки. Пользователь продолжает работу, а менеджер синхронизации обрабатывает очередь в фоне. Это соответствует принципам Reactive Architecture и улучшает отзывчивость интерфейса.
Три слоя очереди: хранилище (persistence), диспетчер (scheduler) и процессор (executor). Хранилище — Room с таблицей QueuedOperation. Диспетчер — WorkManager (Android) или BGTaskScheduler (iOS), который запускает синхронизацию при появлении сети. Процессор — последовательный итератор FIFO, отправляющий операции по одной.
Порядок обработки — критически важен для консистентности данных. Если пользователь создал запись, а затем отредактировал её, обе операции должны отправиться в том же порядке. Иначе сервер сначала получит апдейт несуществующей записи — ошибка. Sequential FIFO — строгий порядок с контролем зависимостей между операциями.
Стратегия слияния — если в очереди есть CREATE и сразу за ним DELETE одного объекта, можно удалить обе операции без отправки: конечное состояние — объект не создан. Аналогично, CREATE + UPDATE CREATE можно слить в один CREATE с последними данными. Оптимизация очереди сокращает количество HTTP-запросов и ускоряет синхронизацию.
По данным Android Developers (2025), WorkManager — предпочтительный способ обработки Offline Queue на Android: он гарантирует выполнение даже после перезапуска устройства, поддерживает constraints на наличие сети и позволяет задать политику повторных попыток через NetworkType.CONNECTED.
class SyncWorker(
private val context: Context,
private val params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result = runCatching {
queueRepository.processNextBatch(batchSize = 10)
Result.success()
}.getOrDefault(Result.retry())
}
CoroutineWorker обрабатывает батчи операций и возвращает Result.retry() при сбое — WorkManager автоматически повторяет запуск с экспоненциальной задержкой. Это простейший способ получить надёжную Offline Queue на Android.
Exponential Backoff — стандартная стратегия повторов с увеличивающимся интервалом: 2 сек, 4 сек, 8 сек, 16 сек и так далее до максимального порога. Это предотвращает повторную перегрузку сервера, если он временно недоступен. Java-библиотека Resilience4j (2024) предоставляет готовую реализацию Retry с настраиваемым backoff.
Максимальное количество попыток — критический параметр. Если после 5–10 попыток операция не удалась, дальнейшие повторы потребительны и бесполезны. Рекомендуется dead letter queue: после исчерпания попыток операция перемещается в отдельную таблицу для ручного анализа. По данным Microsoft Patterns & Practices (2024), dead letter queue упрощает отладку проблем синхронизации и предотвращает блокировку очереди ошибочными операциями.
Jitter — случайная вариация — добавление случайного числа к интервалу backoff. Если у тысячи устройств одновременно появилась сеть после отключения, все они начнут синхронизацию одновременно. Jitter разносит их по времени, предотвращая Cache Stampede на сервере. Полный jitter: delay = random(0, backoff) — рекомендован AWS (2024) для API-клиентов.
Last Write Wins (LWW) — простейшая стратегия: при конфликте побеждает операция с более поздней временной меткой. LWW требует синхронизации времени — timestamp должен генерироваться на сервере или использовать Logical Clock (Lamport clocks). Минус: данные одного пользователя могут быть перезаписаны данными другого без предупреждения.
OT (Operational Transformation) — алгоритм, используемый Google Docs и Figma для совместного редактирования в реальном времени, включая offline-режим. OT преобразует операции так, чтобы они применялись к любому состоянию документа, обеспечивая консистентность без блокировок. CRDT (Conflict-Free Replicated Data Types) — альтернатива OT, набирающая популярность в мобильных приложениях: данные структурированы так, что конфликты разрешимы математически, без центрального сервера.
Custom Merge — для приложений с простой моделью данных (заметки, контакты) можно реализовать кастомные правила слияния. Например, для заметки: если текст изменён в двух версиях, объединить их как конкатенацию с разделителем. User-разрешённый конфликт — если автоматическое слияние невозможно, показать пользователю обе версии и предложить выбрать. Dropbox (2024) использует такой подход для конфликтов в офлайн-файлах, создавая копии с префиксом «Conflicted Copy».
Idempotency Key — уникальный идентификатор операции, который сервер использует для обнаружения дублирующихся запросов. Если клиент отправляет тот же запрос тот же ключ, сервер возвращает результат уже выполненной операции, не выполняя её повторно. Это критически важно для Offline Queue, где возможны повторные отправки при сетевых ошибках.
Формат idempotency key — UUID или хеш от параметров запроса. Сервер должен хранить выполненные ключи вместе с результатом в течение некоторого времени (обычно 24 часа) для обнаружения дубликатов. Stripe API (2024) — эталонный пример: ключ передаётся в заголовке Idempotency-Key, и повторные запросы с тем же ключом возвращают кэшированный ответ.
Клиентская генерация — ключ создаётся на клиенте до отправки операции и сохраняется в таблице QueuedOperation. При повторной попытке ключ не меняется. Архитектура exactly-once — комбинация idempotency key на клиенте и дедупликации на сервере — единственный способ гарантировать, что операция не будет выполнена дважды.
fun createOperation(type: OperationType, payload: String): QueuedOperation =
QueuedOperation(
id = UUID.randomUUID().toString(),
type = type,
endpoint = type.endpoint,
payload = payload,
timestamp = currentTimeMillis(),
idempotencyKey = UUID.randomUUID().toString()
)
Каждая операция получает два UUID: один — идентификатор записи в очереди, второй — idempotency key для сервера. Серверная дедупликация по idempotencyKey гарантирует, что даже при повторной отправке заказ не будет дублирован.
Часто задаваемые вопросы
Кэш хранит копии данных для быстрого чтения в офлайне. Offline Queue хранит операции пользователя для последующей записи на сервер. Кэш работает на чтение, очередь — на запись. Оба компонента могут сосуществовать в offline-first архитектуре.
Рекомендуемый лимит — 100–500 операций. Больше — риск переполнения памяти и долгой синхронизации при восстановлении сети. При превышении лимита приложение должно предупредить пользователя и предложить приоритезировать операции. Разумное ограничение — 50 операций на обновление + 10 на создание.
Операции старше 7 дней с нулевым успехом перемещаются в dead letter queue. Анализируйте их вручную: возможно, изменился API, и эндпоинт больше не существует. Автоматическая очистка — HealthCheck-задача раз в сутки удаляет или архивирует просроченные операции.
Используйте граф зависимостей (DAG): каждая операция содержит список parentOperationId, которые должны завершиться до её отправки. Room-запрос с ORDER BY parent вернёт операции в правильной последовательности. Каскадная отправка — после завершения каждой операции проверяйте, не разблокированы ли дочерние.
Используйте Network Less Tool в Android Emulator или Network Link Conditioner в iOS Simulator для эмуляции потери сети. Пишите тесты, которые добавляют операции в очередь в офлайн-режиме, восстанавливают соединение и проверяют, что все операции отправлены и обработаны сервером.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также