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 са механизмом retry обезбеђује доставу eventually.

Побољшање UX у условима лоше везе — према GSMA Mobile Economy Report (2025), око 40% мобилних корисника у свету има нестабилну интернет везу. Offline Queue чини апликацију употребљивом у метроу, лифтовима, удаљеним областима — свуда где је веза испрекидана.

Смањење губитка података — без реда, све радње обављене ван мреже се губе. Корисник може попунити дугачак образац, притиснути „Пошаљи“ и видети грешку мреже — сав унос нестаје. Offline Queue чува податке и шаље их при првој прилици. Аутоматско чување у Google Docs је класичан пример офлајн реда за документе.

Асинхрона синхронизација — ред омогућава апликацији да не блокира интерфејс током слања. Корисник наставља да ради, а менаџер синхронизације обрађује ред у позадини. Ово је у складу са принципима Реактивне архитектуре и побољшава одзивност интерфејса.

Архитектура офлајн реда: складиштење и обрада

Три слоја реда: складиште (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-у: гарантује извршење чак и након поновног покретања уређаја, подржава ограничења доступности мреже и омогућава постављање политике понављања кроз 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 захтева синхронизацију времена — timestamp мора бити генерисан на серверу или користити Logical Clock (Lamport сатове). Недостатак: подаци једног корисника могу бити пребрисани подацима другог без упозорења.

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. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође