Offline Queue: принципи, стратегії та механізми роботи

Автор: IT Sectr Опубліковано: 2026-06-13 Час читання: 10 хв

Offline Queue — це механізм, який зберігає операції користувача локально, коли пристрій перебуває поза мережею, і надсилає їх на сервер після відновлення з'єднання. Без офлайн-черги користувач втрачає всі дії, виконані без інтернету, що в мобільних додатках неприпустимо. За даними Google Developers (2025), впровадження offline-first архітектури підвищує утримання користувачів на 30% у регіонах з нестабільним інтернетом.

Головне

  • Offline Queue — FIFO-черга операцій, які користувач виконує без інтернету, для подальшої синхронізації.
  • Persistent storage — черга зберігається в локальній БД (SQLite, Room) для збереження при перезапуску додатку.
  • Exponential backoff — стратегія повторних спроб зі збільшенням інтервалу при збої надсилання.
  • Conflict resolution — механізм вирішення колізій, коли офлайн-зміни конфліктують з серверними даними.
  • Idempotency keys — унікальні ключі операцій для запобігання дублюванню на сервері при повторному надсиланні.

Що таке офлайн-черга?

Offline Queue — це впорядкована колекція операцій (створення, оновлення, видалення), які додаток зберігає локально, коли пристрій не має доступу до мережі. Як тільки з'єднання відновлюється, черга надсилає операції на сервер у тому ж порядку, в якому користувач їх виконав.

Уявіть сценарій: користувач месенджера набирає повідомлення в метро без інтернету. Кожне натискання «Надіслати» додається до Offline Queue. Коли поїзд виїжджає з тунелю і мережа з'являється, всі повідомлення надсилаються автоматично. Користувацький досвід — безшовний: він не помічає, що був в офлайні, окрім легкої затримки при надсиланні.

За даними Uber Engineering (2024), їхня офлайн-черга обробляє понад 2 мільйони операцій на день у регіонах з низькою якістю зв'язку. Черга використовує локальне сховище Room з FIFO-порядком та механізмом гарантованої доставки exactly-once.

kotlin
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 з механізмом повторних спроб забезпечує доставку зрештою.

Покращення UX в умовах поганого зв'язку — за даними GSMA Mobile Economy Report (2025), близько 40% мобільних користувачів у світі мають нестабільне інтернет-з'єднання. Offline Queue робить додаток придатним для використання в метро, ліфтах, віддалених районах — усюди, де зв'язок переривчастий.

Зменшення втрати даних — без черги всі дії, виконані в офлайні, втрачаються. Користувач може заповнити довгу форму, натиснути «Надіслати» і побачити помилку мережі — весь ввід пропадає. Offline Queue зберігає дані та надсилає їх при першій нагоді. Автоматичне збереження в Google Docs — класичний приклад офлайн-черги для документів.

Асинхронна синхронізація — черга дозволяє додатку не блокувати інтерфейс під час надсилання. Користувач продовжує роботу, а менеджер синхронізації обробляє чергу у фоні. Це відповідає принципам Реактивної архітектури та покращує чуйність інтерфейсу.

Архітектура офлайн-черги: зберігання та обробка

Три шари черги: сховище (persistence), диспетчер (scheduler) та процесор (executor). Сховище — Room з таблицею QueuedOperation. Диспетчер — WorkManager (Android) або BGTaskScheduler (iOS), який запускає синхронізацію при появі мережі. Процесор — послідовний ітератор FIFO, що надсилає операції по одній.

Порядок обробки — критично важливий для консистентності даних. Якщо користувач створив запис, а потім відредагував його, обидві операції повинні надіслатися в тому ж порядку. Інакше сервер спочатку отримає оновлення неіснуючого запису — помилка. Послідовний FIFO — строгий порядок з контролем залежностей між операціями.

Стратегія злиття — якщо в черзі є CREATE і одразу за ним DELETE одного об'єкта, можна видалити обидві операції без надсилання: кінцевий стан — об'єкт не створено. Аналогічно, CREATE + UPDATE можна злити в один CREATE з останніми даними. Оптимізація черги скорочує кількість HTTP-запитів та прискорює синхронізацію.

За даними Android Developers (2025), WorkManager — найкращий спосіб обробки Offline Queue на Android: він гарантує виконання навіть після перезапуску пристрою, підтримує обмеження на наявність мережі та дозволяє задати політику повторних спроб через NetworkType.CONNECTED.

kotlin
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 та retry policy

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 вимагає синхронізації часу — часова мітка повинна генеруватися на сервері або використовувати логічний годинник (Lamport clocks). Мінус: дані одного користувача можуть бути перезаписані даними іншого без попередження.

OT (Operational Transformation) — алгоритм, який використовують Google Docs та Figma для спільного редагування в реальному часі, включаючи офлайн-режим. OT перетворює операції так, щоб вони застосовувалися до будь-якого стану документа, забезпечуючи консистентність без блокувань. CRDT (Conflict-Free Replicated Data Types) — альтернатива OT, що набирає популярність у мобільних додатках: дані структуровані так, що конфлікти вирішуються математично, без центрального сервера.

Користувацьке злиття — для додатків з простою моделлю даних (нотатки, контакти) можна реалізувати власні правила злиття. Наприклад, для нотатки: якщо текст змінено у двох версіях, об'єднати їх як конкатенацію з роздільником. Конфлікт, вирішений користувачем — якщо автоматичне злиття неможливе, показати користувачу обидві версії та запропонувати вибрати. Dropbox (2024) використовує такий підхід для конфліктів у офлайн-файлах, створюючи копії з префіксом «Conflicted Copy».

Idempotency keys — захист від дублювання

Idempotency Key — унікальний ідентифікатор операції, який сервер використовує для виявлення дублюючих запитів. Якщо клієнт надсилає той самий запит з тим самим ключем, сервер повертає результат вже виконаної операції, не виконуючи її повторно. Це критично важливо для Offline Queue, де можливі повторні надсилання при мережевих помилках.

Формат idempotency key — UUID або хеш від параметрів запиту. Сервер повинен зберігати виконані ключі разом з результатом протягом деякого часу (зазвичай 24 години) для виявлення дублікатів. Stripe API (2024) — еталонний приклад: ключ передається в заголовку Idempotency-Key, і повторні запити з тим самим ключем повертають кешовану відповідь.

Генерація на клієнті — ключ створюється на клієнті до надсилання операції та зберігається в таблиці QueuedOperation. При повторній спробі ключ не змінюється. Архітектура exactly-once — комбінація idempotency key на клієнті та дедуплікації на сервері — єдиний спосіб гарантувати, що операція не буде виконана двічі.

kotlin
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 Queue зберігає операції користувача для подальшого запису на сервер. Кеш працює на читання, черга — на запис. Обидва компоненти можуть співіснувати в offline-first архітектурі.

Який розмір черги безпечний для мобільного пристрою?

Рекомендований ліміт — 100–500 операцій. Більше — ризик переповнення пам'яті та довгої синхронізації при відновленні мережі. При перевищенні ліміту додаток повинен попередити користувача та запропонувати пріоритезувати операції. Розумне обмеження — 50 операцій на оновлення + 10 на створення.

Як обробляти застарілі операції в черзі?

Операції старші 7 днів з нульовим успіхом переміщуються в dead letter queue. Аналізуйте їх вручну: можливо, змінився API, і ендпоінт більше не існує. Автоматичне очищення — HealthCheck-задача щодня видаляє або архівує прострочені операції.

Що робити, якщо операція залежить від попередньої, яка ще не надіслана?

Використовуйте граф залежностей (DAG): кожна операція містить список parentOperationId, які повинні завершитися до її надсилання. Room-запит з ORDER BY parent поверне операції в правильній послідовності. Каскадне надсилання — після завершення кожної операції перевіряйте, чи не розблоковані дочірні.

Як тестувати Offline Queue?

Використовуйте Network Less Tool в Android Emulator або Network Link Conditioner в iOS Simulator для емуляції втрати мережі. Пишіть тести, які додають операції в чергу в офлайн-режимі, відновлюють з'єднання та перевіряють, що всі операції надіслано та оброблено сервером.

Підсумки

  • Offline Queue — FIFO-черга операцій, яка зберігається локально для надсилання після відновлення з'єднання.
  • Persistent storage (Room / SQLite) — обов'язковий для збереження черги при перезапуску додатку.
  • Exponential backoff з jitter — стандартна стратегія повторів для запобігання перевантаженню сервера.
  • Conflict resolution — LWW, OT, CRDT або власні правила для вирішення колізій офлайн-даних.
  • Idempotency key — UUID кожної операції для забезпечення exactly-once доставки на сервері.
  • Dead letter queue — ізоляція проблемних операцій після вичерпання спроб для ручного аналізу.
  • Найкраща практика для Android — WorkManager + Room + ExponentialBackoff — перевірена комбінація від Google.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також