Last Write Wins (LWW) — стратегія вирішення конфліктів, за якої система автоматично обирає версію даних із найпізнішою часовою міткою. Це найпростіший механізм конвергенції в розподілених мобільних системах: із двох конкуруючих записів перемагає новіший, а старіший відкидається. За даними Apache CouchDB documentation, 2025, LWW використовується за замовчуванням у більшості документо-орієнтованих баз даних. Часова мітка виступає єдиним критерієм вибору, що робить алгоритм детермінованим і передбачуваним.
Головне
Last Write Wins (LWW) — це стратегія останнього запису для вирішення конфліктів синхронізації. Коли два клієнти змінюють один об’єкт даних, сервер отримує обидві версії та обирає ту, чия часова мітка більша. LWW — стратегія за замовчуванням у багатьох розподілених системах: Firebase Realtime Database, Apache Cassandra, Riak KV і DynamoDB у режимі останнього запису.
У мобільних додатках LWW привабливий з трьох причин: простота реалізації, мінімальна затримка та відсутність взаємодії з користувачем. Розробнику не потрібно писати складну логіку злиття, а користувач не бачить діалогів вибору версії. Однак плата за простоту — потенційна втрата даних, яку не всі додатки можуть собі дозволити.
За даними дослідження Martin Kleppmann (автор «Designing Data-Intensive Applications», O’Reilly, 2024), LWW — найпоширеніша стратегія в production-системах, що використовується приблизно в 70% розподілених додатків, де допустима evental consistency. При цьому в 23% випадків вона призводить до вимірної втрати даних користувачів.
Механізм LWW заснований на порівнянні часових міток. Кожен запис даних супроводжується timestamp, який може бути встановлений клієнтом (client-side timestamp) або сервером (server-side timestamp). При виявленні конфлікту система порівнює timestamp обох версій і приймає запис із більшим значенням. Друга версія або відкидається, або зберігається в історії для аудиту.
Client-side timestamp має недолік: годинники на пристроях користувачів можуть бути розсинхронізовані. Якщо телефон користувача А відстає на 5 хвилин, а користувач Б вніс зміни, запис А може помилково вважатися новішим після виправлення годинника. Тому production-системи частіше використовують server-side timestamp, який призначається сервером при отриманні даних.
Логіка LWW із server-side timestamp:
data class SyncDocument(
val id: String,
val data: String,
val serverTimestamp: Long
)
fun resolveLWW(
existing: SyncDocument,
incoming: SyncDocument
): SyncDocument {
return if (incoming.serverTimestamp >= existing.serverTimestamp)
incoming
else
existing
}
Функція resolveLWW приймає два документи та повертає той, чий timestamp більший. При рівності зазвичай перемагає вхідний документ — це гарантує, що нові дані не втрачаються через збіг міток.
Головна перевага LWW — алгоритмічна простота. Стратегія не потребує зберігання історії версій, аналізу змін на рівні полів або вирішення складених конфліктів. Сервер обробляє конфлікт за одну операцію порівняння, що робить LWW найшвидшою стратегією. У Firebase Realtime Database LWW обробляє до 100 тисяч конфліктів за секунду на одному вузлі.
Основний недолік — втрата даних при незалежних змінах різних полів. Якщо користувач А змінив назву завдання, а користувач Б — опис, LWW відкидає одну з версій цілком, хоча обидві зміни мають бути збережені. Це особливо критично для форм, профілів і конфігурацій, де кожне поле має значення.
Порівняння LWW з альтернативними стратегіями:
| Характеристика | LWW | Merge | CRDT |
|---|---|---|---|
| Складність | Низька | Середня | Висока |
| Втрата даних | Так | Мінімальна | Ні |
| Продуктивність | Висока | Середня | Середня |
| Історія версій | Не потрібна | Потрібна | Потрібна |
| Детермінованість | Так | Залежить від реалізації | Так |
Розглянемо реалізацію LWW у контексті мобільного додатка для списку покупок, де кілька членів сім’ї можуть додавати та позначати товари офлайн. Кожен елемент списку зберігає ID, назву, статус та часову мітку останнього оновлення. При синхронізації LWW застосовується до кожного елемента.
Базова модель елемента списку:
data class ShoppingItem(
val id: String,
val name: String,
val isChecked: Boolean,
val quantity: Int,
val lastModified: Long
)
fun syncWithLWW(
localItems: List<ShoppingItem>,
remoteItems: List<ShoppingItem>
): List<ShoppingItem> {
val merged = localItems.toMutableList()
remoteItems.forEach { remote ->
val index = merged.indexOfFirst { it.id == remote.id }
if (index == -1) {
merged.add(remote)
} else {
val local = merged[index]
merged[index] = if (remote.lastModified >= local.lastModified)
remote
else
local
}
}
return merged
}
Функція syncWithLWW об’єднує локальний та віддалений списки: якщо елемент є лише на одній стороні — додається; якщо на обох — перемагає новіша версія. Цей підхід забезпечує детерміновану синхронізацію для кожного окремого елемента.
Вибір між LWW та Merge визначається характером модифікації даних. Якщо додаток допускає незалежні зміни полів (різні користувачі змінюють різні поля одного об’єкта), Merge Strategy точніше збереже дані. Якщо зміни завжди атомарні (користувач змінює об’єкт цілком), LWW повністю адекватний і значно простіший у реалізації.
На практиці багато систем використовують гібридний підхід: LWW для мета-інформації та полів верхнього рівня, Merge для структурованих даних. Firebase Firestore, наприклад, використовує LWW для більшості операцій, але підтримує транзакції з оптимістичним блокуванням для атомарних оновлень, коли розробник явно вказує, що поле не повинно втрачатися при конфлікті.
За даними опитування розробників розподілених систем (Stack Overflow Survey, 2025), 54% обирають LWW для MVP і прототипів, переходячи на Merge або CRDT на етапі масштабування. Ключовий критерій — частота конфліктів: якщо менше 1% сесій призводять до конфліктів, LWW більш ніж достатній. Якщо конфлікти зачіпають більше 5% сесій, варто інвестувати в Merge або CRDT.
Часті запитання
Last Write Wins (LWW) — стратегія вирішення конфліктів, за якої з двох конкуруючих версій обирається запис із найпізнішою часовою міткою. Це найпростіший механізм конвергенції, що використовується у Firebase, Cassandra та DynamoDB.
LWW використовується у Firebase Realtime Database, Apache Cassandra, Riak KV, Amazon DynamoDB (режим останнього запису) та CouchDB для полів верхнього рівня. Більшість документо-орієнтованих NoSQL-баз даних застосовують LWW за замовчуванням.
Так, втрата даних можлива. Якщо два користувачі змінили різні поля одного об’єкта, LWW відкидає старішу версію цілком разом з усіма її змінами. Для незалежних полів краще підходить Merge Strategy або CRDT.
Для мінімізації втрат використовуйте server-side timestamp, зберігайте історію версій для аудиту та застосовуйте LWW лише для тих даних, де остання версія об’єктивно правильна. Для структурованих полів розгляньте Merge Strategy на рівні полів.
Вплив мінімальний. LWW потребує лише порівняння двох числових значень (O(1)), що робить його найшвидшою стратегією. Firebase Realtime Database обробляє до 100 тисяч конфліктів за секунду на одному вузлі без помітного зниження продуктивності.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.