Мобильные системы в офлайне: как работать без постоянной связи

 

 

 

 

АВТОНОМНОСТЬ // OFFLINE_ARCHITECTURE_INIT

Офлайн-режим в мобильных приложениях: архитектура автономности

Мобильное приложение может стабильно работать месяцами, пока все пользователи находятся в зоне уверенного приёма сети. Но достаточно сотруднику выйти на склад, спуститься в техническое помещение или приехать на удалённый объект, чтобы привычный сценарий перестал работать. Данные не загружаются, сохранение завершается ошибкой, а результат уже выполненной операции становится неопределённым.

Для корпоративных мобильных систем это не просто проблема доступности интерфейса. Если приложение используется для осмотра оборудования, обработки документов, складских операций, обслуживания объектов или выполнения заданий, потеря связи непосредственно влияет на бизнес-процесс.

Пользователь должен иметь возможность продолжать работу, даже когда сервер временно недоступен. При этом выполненные действия необходимо сохранить, передать после восстановления соединения и согласовать с изменениями, которые могли произойти в центральной системе за время отсутствия связи.

На первый взгляд задача выглядит простой: сохранить данные на устройстве, а затем отправить их на сервер. На практике именно между этими двумя действиями возникают наиболее сложные проблемы. Запрос может выполниться на сервере, но ответ потеряется. Один сотрудник может изменить запись, пока другой работает с её предыдущей версией. Удалённый объект может вернуться после синхронизации, а повторная отправка операции — создать дубликат.

АРХИТЕКТУРНЫЙ_ВЫЗОВ // OFFLINE_INTEGRITY_RISKS

Надёжный офлайн-режим — это архитектура, которая обеспечивает автономную работу, сохраняет историю несинхронизированных изменений и управляемо согласует данные после восстановления связи.

> Чтобы реализовать её правильно, необходимо спроектировать не только локальное хранение, но и протокол обмена, модель конфликтов, правила обработки повторных запросов, безопасность и восстановление после сбоев.

ВИЗУАЛИЗАЦИЯ // ЦЕПОЧКА АВТОНОМНОГО ЖИЗНЕННОГО ЦИКЛА
Локальное сохранение на устройствеРегистрация в очереди изменений (Outbox)Восстановление связи и отправкаИдемпотентная обработка на сервереРазрешение конфликтов версийФиксация и аудит (Синхронизировано)

 

 

СИНХРОНИЗАЦИЯ // REQUEST_RETRY_FALLACY

Почему мобильному приложению недостаточно просто повторять запросы

В обычном онлайн-приложении клиент обращается к серверу, получает актуальные данные, отправляет изменение и ждёт подтверждения. Сервер выступает основной точкой принятия решений, а мобильное приложение преимущественно отображает его состояние.

Когда соединение пропадает, такая модель перестаёт работать. Если каждое действие требует немедленного ответа API, пользователь не сможет полноценно продолжать работу.

Можно автоматически повторять неудачные запросы после восстановления сети. Это полезный механизм, но он решает только проблему доставки. Он не гарантирует, что запрос не выполнится дважды, что данные не устарели и что конкурентные изменения будут обработаны правильно.

Рассмотрим простой пример. Сотрудник заполнил результат технического осмотра и нажал «Сохранить». Сервер выполнил операцию, но соединение оборвалось до получения ответа. Приложение не знает, сохранились данные или нет.

Если оно считает операцию неуспешной и отправляет её заново, возможны два исхода:

[ ИСХОД А // РИСК ДУБЛИРОВАНИЯ ]

сервер повторно применит действие и создаст дубликат;

[ ИСХОД Б // ИДЕМПОТЕНТНОСТЬ ]

сервер распознает повтор и вернёт результат первоначальной операции.

Правильное поведение зависит от архитектуры серверной обработки, а не от количества повторных попыток на мобильном устройстве.

Поэтому необходимо различать три события: данные сохранены локально; запрос доставлен и обработан сервером; бизнес-операция подтверждена как успешно завершённая. Эти события не всегда происходят одновременно.

[Пользователь изменил данные][Сохранение на устройстве][Отправка на сервер][Обработка операции][Получение результата]Сбой возможен на каждом этапе

Если приложение не различает состояния операции, оно не сможет надёжно восстановить работу после обрыва связи.

 

 

АРХИТЕКТУРА // OFFLINE_FIRST_PARADIGM

Какую архитектуру использовать для работы офлайн

Для приложений, в которых пользователь должен продолжать работу без постоянного подключения, обычно рассматривают подход offline-first. Его смысл заключается в том, что локальное хранилище становится полноценной частью приложения, а не временным кэшем серверных ответов.

Пользователь читает доступные данные из локальной базы. Изменения сначала фиксируются на устройстве. Синхронизатор передаёт их серверу, когда это становится возможным, и получает обновления, сделанные другими участниками системы.

МОБИЛЬНОЕ ПРИЛОЖЕНИЕЛокальная базаОчередь операцийСинхронизаторСерверный APIПроверка правилСерверная БД

Схема показывает логические связи, а не обязательную физическую структуру компонентов. Например, локальная база и очередь могут находиться в одном хранилище, а серверный API и обработчик синхронизации — быть частью одного сервиса.

Важен другой принцип: пользовательские действия, локальное хранение и доставка изменений должны быть разделены настолько, чтобы временная недоступность сервера не приводила к потере уже сохранённой работы.

 

 

ХРАНИЛИЩЕ // LOCAL_DATA_STORE

Локальная база данных

В локальной базе хранятся сведения, необходимые для выполнения предусмотренных офлайн-сценариев: задания, карточки объектов, справочники, черновики, результаты проверок и другие рабочие данные.

Для структурированных данных обычно используют встроенную базу данных с поддержкой транзакций. Конкретная технология определяется платформой, объёмом данных, требованиями к запросам и условиями эксплуатации.

Принципиально важно, чтобы локальная база обеспечивала атомарность связанных изменений.

Предположим, пользователь завершает задание. Приложение должно сохранить результат проверки, обновить состояние задания и создать запись для последующей синхронизации. Если приложение аварийно завершится между этими действиями, данные не должны остаться в противоречивом состоянии.

Пользователь завершает задание🔒 Открытие локальной транзакции1. Сохранить результат проверки2. Обновить состояние задания3. Создать операцию в очереди (Outbox)COMMIT // Защита от сбоя между шагами
ПРАВИЛО_ПРОЕКТИРОВАНИЯ // LOCAL_TRANSACTION_MANDATE

> Поэтому изменение рабочей записи и создание соответствующей операции в очереди следует выполнять в одной локальной транзакции, если они представляют собой единое логическое действие.

 

 

БУФЕРИЗАЦИЯ // CLIENT_OUTBOX_QUEUE

Очередь операций

Очередь хранит изменения, которые ещё необходимо передать и согласовать с сервером.

Это могут быть создание документа, изменение полей, добавление комментария, регистрация результата осмотра или завершение задания.

МЕТАДАННЫЕ_ОПЕРАЦИИ // OPERATION_METRICS

[meta] устойчивый идентификатор;

[meta] связь с конкретным объектом;

[meta] версия исходных данных (основа);

[meta] текущий статус обработки.

ТРЕБОВАНИЕ_ЖИЗНЕННОГО_ЦИКЛА // PERSISTENCE_MANDATE

Очередь должна переживать перезапуск приложения и временную потерю связи.

> Её нельзя хранить только в оперативной памяти: после аварийного завершения пользователь не должен потерять выполненную работу.

 

 

СЕРВЕР // SERVER_SIDE_PROCESSING

Серверная сторона

Сервер принимает операции, проверяет полномочия пользователя, применяет бизнес-правила и фиксирует результат.

Он также должен распознавать повторные запросы, обнаруживать несовместимые изменения и возвращать клиенту понятный результат обработки.

РЕАКЦИЯ_НА_ОТКЛОНЕНИЕ // REJECTION_RESPONSE_OPTIONS

[action] повторить запрос;

[action] обновить локальные данные;

[action] запросить решение пользователя;

[action] сообщить о невозможности выполнения.

Входной запрос от клиентаПроверка полномочий и ИдемпотентностиПрименение правил и обнаружение конфликтовФиксация результата в БДВозврат понятного статуса обработки

Таким образом, сервер участвует не только в хранении данных, но и в обеспечении их согласованности.

 

 

СТРУКТУРИРОВАНИЕ // LOCAL_DATA_PARTITIONING

Какие данные необходимо хранить на устройстве

Офлайн-архитектура начинается с определения того, какие данные действительно нужны пользователю без подключения к серверу.

Здесь важно найти баланс. Если хранить слишком мало, приложение не сможет выполнять рабочие сценарии. Если загружать всю корпоративную базу, возрастут требования к памяти, времени синхронизации, защите данных и актуализации справочников.

Для большинства корпоративных приложений полезно разделять локальные данные на несколько категорий.

КАТЕГОРИИ_ДАННЫХ // CACHE_ARCHITECTURE_LAYERS
Рабочие данные
[Назначение]: Задания, объекты, документы и необходимые справочники
Локальные изменения
[Назначение]: Новые и изменённые значения, ещё не согласованные с сервером
Очередь операций
[Назначение]: Действия, которые необходимо передать и обработать
Вложения
[Назначение]: Фотографии, документы, подписи и другие файлы
Метаданные синхронизации
[Назначение]: Версии объектов, курсоры, идентификаторы и статусы
Данные доступа
[Назначение]: Токены и другие сведения, необходимые для авторизации
КОНФЛИКТ_ОБНОВЛЕНИЯ // OVERWRITE_PROTECTION_PRINCIPLE

Особое внимание необходимо уделить различию между локальной копией объекта и изменениями, которые ещё не синхронизированы.

Предположим, приложение загрузило карточку оборудования, после чего сотрудник изменил её статус без подключения к сети. Позднее сервер прислал обновлённую карточку.

Если приложение просто заменит локальную запись серверной, результат работы сотрудника исчезнет.

Следовательно, механизм обновления должен учитывать локальные изменения, которые ещё не получили окончательного результата обработки.

[Локальная запись][Есть несинхронизированные изменения?]Да[Сохранить локальное][изменение отдельно]Нет[Обновить][данные][Согласовать с сервером]

В конкретной реализации это может означать отдельное хранение серверной версии, локального состояния и журнала изменений либо применение другой модели данных, обеспечивающей тот же результат.

Главное требование — обновление серверной копии не должно незаметно уничтожать работу пользователя.

 

 

ПРАКТИЧЕСКИЙ_КЕЙС // OFFLINE_CONCURRENCY_CASE

Сквозной пример: технический осмотр без интернета

Рассмотрим условную компанию, которая обслуживает оборудование на удалённых объектах. Сотрудники получают задания в mobile приложении, проверяют оборудование, заполняют результаты осмотра и прикладывают фотографии.

Перед выездом сотрудник открывает задание № 417. Приложение загружает необходимые сведения и сохраняет локальную копию объекта версии 12.

На площадке интернет пропадает. Сотрудник выполняет осмотр, указывает результат «Исправно», добавляет комментарий и фотографию.

Приложение сохраняет результат локально. Пользователь может продолжить работу, но сервер пока не получил новые данные.

В это время диспетчер видит, что оборудование требует дополнительной проверки, и изменяет состояние задания в центральной системе. На сервере появляется версия 13.

Теперь локальная и серверная версии различаются.
ЗАЗАНИЕ № 417МОБИЛЬНОЕ УСТРОЙСТВОВерсия 12Статус: «Исправно»СЕРВЕРНАЯ СИСТЕМАВерсия 13Статус: «Требует ремонта»Нет связи ⚡Обнаружение расхожденияПрименение правил разрешения

Когда интернет восстановится, приложение не должно просто отправить локальную запись и заменить ею серверное состояние. Но и безусловно принять серверное значение недостаточно: результат фактического осмотра может содержать важные сведения.

Система должна обнаружить расхождение и определить, какое правило применимо к этому случаю. Например, если сотрудник и диспетчер изменили независимые поля, изменения можно объединить. Если они установили несовместимые значения одного поля, может потребоваться дополнительная проверка. Если задание было отменено, сервер должен определить, разрешено ли принимать результат позднего осмотра.

БИЗНЕС_ИНВАРИАНТ // CONFLICT_DUE_OFFLINE

Этот пример показывает принципиальную особенность офлайн-систем:

> временное расхождение данных ожидаемо. Проблемой оно становится тогда, когда архитектура не умеет его обнаруживать и корректно разрешать.

 

 

СИНХРОНИЗАЦИЯ // RECONNECTION_RECONCILIATION

Как организовать синхронизацию после восстановления связи

Синхронизация должна решать две задачи: передавать локальные изменения на сервер и получать изменения, которые произошли в центральной системе.

Эти направления нельзя безусловно сводить к операции «скачать всё и заменить локальные данные».

МОБИЛЬНОЕ УСТРОЙСТВОЛокальные измененияОтправка операцийНовые данныеСерверПроверка версий и правилПринятоКонфликтОтклонено

Конкретный порядок зависит от бизнес-процесса. Иногда необходимо сначала передать локальные изменения, в других случаях перед отправкой следует получить актуальное состояние объекта или проверить ограничения.

При этом результат каждой операции должен быть определённым. Клиенту недостаточно знать, что запрос был отправлен: нужно понимать, применено изменение или нет, а если результат неизвестен — безопасно выяснить его.

 

 

АКТУАЛИЗАЦИЯ // INCREMENTAL_SYNC_PROTOCOL

Инкрементальная синхронизация

Повторная загрузка всех данных при каждом подключении обычно неэффективна. По мере роста объёма локальной базы она увеличивает сетевую нагрузку и время восстановления актуального состояния.

Поэтому часто используют инкрементальную синхронизацию: сервер передаёт изменения, произошедшие после определённой позиции, версии или курсора.

Механизм может основываться на последовательности изменений, версиях записей или журнале событий. Выбор зависит от структуры системы и требований к согласованности.

УПРАВЛЕНИЕ_КУРСОРOM // CURSOR_PROGRESSION_RISK

Важен принцип продвижения курсора.

Предположим, приложение получило очередную порцию изменений, но завершилось до их записи в локальную базу. Если курсор уже обновлён, при следующем запуске система может пропустить эти изменения.

Сервер прислал порцию измененийПрименение изменений в локальную БД✔ Успешная запись данныхПродвижение курсора синхронизацииНарушение порядка ➔ Пропуск данных

Правильная последовательность должна обеспечивать согласованность между применением данных и фиксацией прогресса синхронизации. Если оба действия невозможно выполнить в одной транзакции, необходимо предусмотреть безопасное восстановление и повторную обработку.

 

 

ВЕРИФИКАЦИЯ // SYNC_TIMING_AND_BATCHING

Почему нельзя ориентироваться только на время устройства

Время на мобильном устройстве может быть неточным, изменяться вручную или отличаться от времени на сервере.

РИСК_РАССИНХРОНИЗАЦИИ // TIMESTAMP_FALLACY

Поэтому условие «загрузить все записи, изменённые позже времени последней синхронизации» само по себе не гарантирует полноту обмена.

> Для определения изменений лучше использовать серверные версии, курсоры или другие механизмы, которые обеспечивают однозначное продвижение по журналу изменений.

Временные метки могут быть полезны для аудита и диагностики, но не должны без дополнительных гарантий выступать единственным механизмом согласования данных.

Синхронизация порциями

При большом количестве операций удобно передавать данные пакетами. Это позволяет ограничивать размер запросов, контролировать нагрузку и продолжать обмен после прерывания связи.

Сетевой пакет (Пакет передачи)Проверка связанных изменений (N элементов)Слепая фиксацияЧастичная запись ➔ НарушениеБизнес-границыАтомарная транзакция ✔

Однако необходимо различать пакет передачи и бизнес-трантранзакцию.

Если пять изменений должны применяться только совместно, сервер не должен частично фиксировать их исключительно потому, что они попали в один сетевой пакет. Границы атомарности определяются бизнес-правилами и возможностями серверной обработки.

 

 

 

 

 

БУФЕРИЗАЦИЯ // OUTBOX_PAYLOAD_STRUCTURE

Очередь операций: как не потерять действие пользователя

Очередь — один из ключевых элементов офлайн-архитектуры. Она позволяет отделить момент выполнения действия от момента его передачи на сервер.

Предположим, пользователь создал документ, добавил несколько строк и завершил его. Приложение должно сохранить не только итоговое представление, но и достаточную информацию для корректного применения операции на сервере.

Для этого каждая операция может содержать:

СПЕЦИФИКАЦИЯ_ТРАНЗАКЦИИ // TRANSACTION_DATA_SPEC

[op]уникальный идентификатор;

[op]тип действия;

[op]идентификатор объекта;

[op]данные, необходимые для выполнения;

[op]версию, на которой основано изменение;

[op]зависимости от других операций;

[op]статус обработки и сведения о результате.

СТРУКТУРА_ПАКЕТА // LOGICAL_JSON_STRUCTURE
{
  "operation_id": "op-7f3a91",
  "entity_id": "inspection-417",
  "operation_type": "complete",
  "base_version": 12,
  "payload": {
    "result": "passed",
    "comment": "Проверка выполнена"
  },
  "status": "pending"
}

Это пример логической структуры, а не обязательный формат API. Реальный состав полей определяется архитектурой приложения.

 

 

АТОМАРНОСТЬ // LOCAL_STATE_DESYNCHRONIZATION

Почему локальное сохранение и очередь должны быть согласованы

Рассмотрим неправильную последовательность:

[ СЦЕНАРИЙ А // ПОТЕРЯ ОЧЕРЕДИ ]

Приложение сохраняет результат осмотра. Затем отдельно создаёт операцию в очереди. Между этими действиями приложение аварийно завершается. В результате данные уже присутствуют в локальной базе, но синхронизатор не знает, что их нужно отправить.

[ СЦЕНАРИЙ Б // ПОТЕРЯ ДАННЫХ ]

Возможна и обратная проблема: операция попала в очередь, но связанные изменения не сохранились полностью.

РЕШЕНИЕ // TRANSACTION_ATOMICITY_MANDATE

Правильное решение — фиксировать рабочие изменения и соответствующую запись очереди в одной локальной транзакции, когда они представляют единое логическое действие.

[Действие пользователя][Локальная транзакция][Изменить данные][Создать операцию][Общий COMMIT][Операция готова]к синхронизации]

Если транзакция не завершилась, ни одна из связанных частей не должна считаться окончательно сохранённой. Если завершилась — обе доступны для последующей обработки.

 

 

УПРАВЛЕНИЕ_ЖУРНАЛОМ // OUTBOX_STATE_MACHINE

Состояния операции

Очередь не должна представлять собой список, из которого запись удаляется сразу после отправки запроса.

Полезно различать состояния «ожидает отправки», «обрабатывается», «подтверждена», «требует разрешения конфликта» и «отклонена». Для некоторых систем нужны также состояния ожидания зависимостей и неопределённого результата.

Это позволяет после перезапуска определить, какие действия можно отправить, какие уже завершены и какие требуют дополнительной проверки.

Ожидает отправки / Ожидание зависимостейОбрабатывается (Отправлено в сеть)Результат обработки APIПодтвержденаКонфликт / ОтклоненаНеопределённыйУстановление факта проведения операции
РАЗГРАНИЧЕНИЕ_СТАТУСОВ // DELIVERY_VS_EXECUTION

При этом состояние «отправлено» не эквивалентно состоянию «применено».

> Запрос мог дойти до сервера, но клиент мог не получить подтверждение.

 

 

ИДЕМПОТЕНТНОСТЬ // IDEMPOTENCY_CONTROL_CONTRACT

Идемпотентность: как защититься от повторного выполнения

Одна из наиболее сложных проблем синхронизации связана с неопределённым результатом запроса.

Допустим, мобильное приложение отправило команду «Создать документ». Сервер создал документ и зафиксировал транзакцию, но соединение оборвалось до получения ответа. С точки зрения клиента операция выглядит незавершённой. После восстановления связи приложение повторяет запрос. Если сервер воспринимает повтор как новую команду, появляются два документа.

Решение — идемпотентная обработка операций.

Идемпотентность в данном контексте означает, что повторная доставка одной и той же логической операции не приводит к повторному выполнению её бизнес-эффекта. Для этого клиент присваивает операции устойчивый идентификатор, а сервер сохраняет информацию о её обработке. При повторном получении того же идентификатора сервер возвращает ранее зафиксированный результат вместо повторного выполнения действия.

[Запрос: operation_id = A17][Проверка на сервере]Уже обработан[Вернуть ранее][сохранённый результат]Новый запрос[Выполнить операцию][Сохранить результат][Результат зафиксирован]
УГЛУБЛЕННЫЙ_КОНТРОЛЬ // CONCURRENCY_AND_VALIDATION

Но одной проверки идентификатора недостаточно. Проверка и выполнение операции должны быть организованы так, чтобы параллельные запросы с одним идентификатором не могли одновременно пройти проверку и создать два бизнес-эффекта.

На практике серверу необходимо согласованно фиксировать результат операции и связанные изменения. В зависимости от архитектуры это обеспечивается транзакциями, ограничениями уникальности и другими средствами защиты от конкурентной обработки.

> Также следует определять, что происходит, если один идентификатор повторно используется с другим содержимым. Такой запрос нельзя автоматически считать безопасным повтором.

Повторная отправка — нормальная часть работы ненадёжной сети. Повторное выполнение уже завершённой операции должно контролироваться сервером.

 

 

СТОЛКНОВЕНИЕ_ДАННЫХ // CONFLICT_DETECTION_ENGINE

Конфликты данных: когда два участника изменили одну запись

Пока устройство работает офлайн, другие пользователи могут продолжать менять те же объекты. После восстановления связи локальные изменения необходимо согласовать с актуальным состоянием сервера.

Конфликт возникает не при любом расхождении, а тогда, когда изменения нельзя безопасно применить одновременно по установленным правилам.

Рассмотрим два сценария.

АНАЛИЗ_СЦЕНАРИЕВ // MERGE_COMPATIBILITY
Сценарий 1 // Независимые поля
Сотрудник изменил комментарий, а диспетчер обновил контактное лицо. Если это независимые поля и нет дополнительных ограничений, система может объединить изменения.
Сценарий 2 // Пересечение полей
Оба участника изменили статус оборудования: один указал «Исправно», другой — «Требует ремонта». Здесь автоматическое объединение неочевидно, потому что значения противоречат друг другу.
[Общий объект][Сотрудник А]Статус: исправно[Сотрудник Б]Статус: неисправно[Синхронизация][Конфликт][Автоматическоеслияние][Ручное илибизнес-решение]

 

 

СТРАТЕГИЯ_СЛИЯНИЯ // LAST_WRITE_WINS_POLICY

Стратегия 1. Последняя запись побеждает

При подходе last-write-wins система выбирает одно значение согласно определённому правилу порядка.

Это может быть приемлемо для некритичных полей, например для некоторых настроек отображения. Но здесь необходимо учитывать две независимые проблемы.

ДЕФЕКТЫ_МОДЕЛИ // LWW_CRITICAL_ISSUES
Проблема 1 // Рассинхронизация времени
Нужно надёжно определить порядок изменений. Время устройства может быть неточным, а порядок поступления запросов на сервер не обязательно совпадает с порядком действий пользователей.
Проблема 2 // Утеря бизнес-контекста
Даже правильно установленный порядок не гарантирует правильность бизнес-решения. Более позднее значение не всегда имеет право отменять предыдущее.
Конкурирующие изменения полейСравнение временных меток (LWW)Более позднееЗапись перезаписывает БД ✔Более раннееЗначение уничтожается ❌

Поэтому данная стратегия применима только там, где потеря конкурирующего значения допустима и заранее определено, как выбирать победителя.

 

 

СТРАТЕГИЯ_СЛИЯНИЯ // MERGE_INDEPENDENT_FIELDS

Стратегия 2. Слияние независимых изменений

Если участники изменили разные поля, система может объединить их изменения.

ОГРАНИЧЕНИЕ_МЕТОДА // BUSINESS_LOGIC_CONSTRAINTS

Однако слияние должно учитывать не только структуру данных, но и бизнес-ограничения. Изменение одного поля может влиять на допустимость другого.

Например, объединить новый комментарий со старой карточкой оборудования обычно проще, чем объединить статус закрытого задания с результатом его повторного выполнения.

> Следовательно, техническая возможность объединить поля ещё не означает, что итоговый объект будет корректным.

Изменение разных полей одного объектаТехническое объединение + Бизнес-валидацияКорректноАвтоматическое слияние ✔ПротиворечиеОтказ / Ручная сверка ❌

 

 

СТРАТЕГИЯ_СЛИЯНИЯ // OPTIMISTIC_CONCURRENCY_CONTROL

Стратегия 3. Оптимистический контроль версий

Один из распространённых подходов — передавать версию объекта, на которой основано изменение.

Предположим, устройство загрузило версию 12. После локального изменения оно отправляет операцию с указанием **base_version = 12**. Но сервер уже хранит версию 13.

УстройствоСерверВерсия 12Версия 12Изменение офлайнИзменение диспетчераВерсия 13Версии не совпадают (12 != 13)Проверка конфликтаМожно применитьСлияние строк ✔Нельзя применитьБлокировка / Откат ❌

Сервер обнаруживает, что операция основана на устаревшей версии, и применяет установленное правило.

Для реализации могут использоваться поле версии, условные обновления, оптимистическая блокировка или эквивалентные механизмы.

РАЗДЕЛЕНИЕ_КОНТУРОВ // DETECTION_VS_RESOLUTION

Важно, что обнаружение конфликта и его разрешение — разные задачи.

> Проверка версии позволяет заметить потенциальную проблему, но сама по себе не определяет, какое значение должно победить.

 

 

СТРАТЕГИЯ_СЛИЯНИЯ // EVENT_SOURCING_LOG

Стратегия 4. Журнал событий вместо перезаписи состояния

В некоторых процессах правильнее хранить отдельные факты выполнения, а не перезаписывать один общий статус.

Например, два сотрудника могут независимо провести осмотр одного оборудования. Если каждый результат имеет собственный идентификатор и фиксируется как отдельная запись, система сохраняет оба результата, после чего применяет правила формирования итогового состояния.

Это особенно полезно там, где важна история действий, аудит и возможность объяснить, как было получено текущее состояние объекта.

Параллельные факты осмотра (События)Событие #101Событие #102Append-only Журнал (Сохранение обоих фактов)Применение правил редукции / проекцииТекущее расчетное состояние объекта ✔
КРИТЕРИЙ_АРХИТЕКТУРЫ // ARCHITECTURAL_COMPLEXITY_TRADEOFF

Однако переход к журналу событий увеличивает сложность архитектуры и не требуется для каждой мобильной системы.

> Его выбирают тогда, когда бизнес-смысл действительно заключается в накоплении событий, а не в хранении одного изменяемого значения.

 

 

СПЕЦ_ОПЕРАЦИИ // DELETE_AND_ATTACHMENT_MANAGEMENT

Удаление записей

Предположим, пользователь удалил объект на устройстве. При следующей синхронизации приложение получает старую серверную копию этого объекта. Если локальное удаление не зафиксировано как самостоятельное изменение, запись может появиться снова.

Для предотвращения этого используют маркеры удаления, часто называемые tombstones, или эквивалентные механизмы фиксации удалённых объектов.

РИСК_РЕГРЕССИИ // TOMBSTONE_RETENTION_RISK

Сведения об удалении необходимо хранить достаточно долго, чтобы их получили все участвующие устройства и процессы.

> Если удалить маркер слишком рано, устройство, которое долго было офлайн, может восстановить уже удалённый объект.

Фотографии и другие вложения

Файлы часто передаются отдельно от основной записи. Это позволяет ограничивать размер запросов, возобновлять загрузку и независимо отслеживать состояние вложений.

Для каждого файла полезно хранить устойчивый идентификатор, размер, контрольную сумму и статус передачи. Конкретный набор метаданных зависит от требований системы.

Отправка закрытия задания на серверПроверка условия: Фотография обязательна?Да (Обязательна)Блокировка закрытия ❌Ожидание загрузки бинаряНет (Необязательна)Подтверждение задания ✔Файлы дольются позже

Если фотография обязательна для закрытия задания, сервер не должен подтверждать окончательное завершение, пока файл не получен и не прошёл необходимые проверки. Если вложение необязательно, его можно передавать отдельно, сохраняя связь с исходным объектом.

 

 

ОРКЕСТРАЦИЯ // CHAINED_OPERATIONS_PIPELINE

Зависимые операции

Допустим, сотрудник офлайн создал документ, добавил к нему строку и завершил обработку.

Сервер может требовать, чтобы создание документа произошло раньше добавления строки, а добавление строки — раньше завершения.

[Создать документ][Получить ID документа][Добавить строки][Передать вложения][Завершить документ]Параллельная отправка нарушит строгий порядок

Если отправить все операции параллельно, сервер может получить завершение раньше создания документа или вложение раньше объекта, к которому оно относится.

РЕГЛАМЕНТ_ОЧЕРЕДИ // DEPENDENCY_ENFORCEMENT

Поэтому очередь должна учитывать зависимости.

> Независимые операции можно отправлять параллельно, если это безопасно, а зависимые — только после выполнения необходимых предварительных условий.

 

 

ФУНКЦИОНАЛЬНОСТЬ // OFFLINE_CAPABILITY_MATRIX

Какие операции можно выполнять без интернета

Офлайн-доступность должна определяться бизнес-правилами, а не только техническими возможностями приложения.

Возможность сохранить действие локально не означает, что его результат можно считать окончательно подтверждённым.

Например, пользователь может создать заявку на резервирование ресурса без сети. Но подтвердить резервирование система сможет только после проверки актуальной доступности ресурса на сервере, если бизнес-процесс требует глобальной гарантии отсутствия пересечений.

КЛАССИФИКАЦИЯ_ДЕЙСТВИЙ // OPERATION_CATEGORIES
Полностью локальные действия
Их результат не требует немедленного обращения к серверу. Например, сохранение черновика или подготовка комментария.
Действия с отложенным подтверждением
Пользователь может выполнить операцию локально, но её окончательный результат определяется после синхронизации. Например, передача результата проверки или создание заявки.
Операции с серверной зависимостью
Их нельзя окончательно подтвердить офлайн, если для корректности необходимы актуальные права, остатки, лимиты или отсутствие конкурирующих действий.
[Операция пользователя][Можно выполнить офлайн?]Да[Сохранить локально]Нет[Отложить / Запретить][Синхронизировать и проверить правила][Принять][Конфликт / Отклонить]
ИНТЕРФЕЙСНЫЙ_КОНТРАКТ // UI_STATUS_MUTATION

У каждой категории должны быть свои правила интерфейса.

> Если действие сохранено только локально, пользователь не должен воспринимать его как подтверждённую сервером операцию.

 

 

ИНТЕРФЕЙС // UI_STATE_INDICATION

Как отображать состояние пользователю

В интерфейсе полезно различать следующие статусы:

МАТРИЦА_ВИЗУАЛЬНЫХ_СТАТУСОВ // UI_STATUS_CODES

[ui] сохранено на устройстве;

[ui] ожидает синхронизации;

[ui] передаётся;

[ui] подтверждено сервером;

[ui] требует разрешения конфликта;

[ui] отклонено;

[ui] результат обработки неизвестен.

ОБРАБОТКА_НЕОПРЕДЕЛЕННОСТИ // UNKNOWN_OUTCOME_POLICY

Последнее состояние особенно важно. Если сервер мог выполнить операцию, но подтверждение потерялось, нельзя просто объявить её неуспешной.

> Система должна выяснить результат по идентификатору операции или безопасно повторить запрос.

Чёткая индикация состояний уменьшает число повторных действий пользователя и помогает избежать ситуаций, когда одна операция выполняется несколько раз вручную.

 

 

БЕЗОПАСНОСТЬ // LOCAL_DATA_SECURITY_CONTOUR

Безопасность локальных данных

Офлайн-режим расширяет поверхность риска, поскольку данные теперь находятся не только в центральной инфраструктуре, но и на устройствах сотрудников.

Если телефон потерян, устройство выведено из эксплуатации или доступ пользователя отозван, локальные сведения могут остаться доступными до тех пор, пока не сработают предусмотренные механизмы защиты.

Поэтому необходимо определить, какие данные разрешено хранить на устройстве, как они защищаются и как долго остаются доступными без подключения.

Шифрование и хранение секретов

Следует оценить необходимость шифрования локальной базы и вложений, безопасного хранения ключей и ограничения доступа к файлам.

ХРАНЕНИЕ_ТОКЕНОВ // TOKEN_PROTECTION_MANDATE

Токены и другие секреты не следует хранить в обычных файлах приложения без подходящих механизмов защиты.

> Используются средства безопасного хранения, предусмотренные платформой, с учётом модели угроз.

Полный массив корпоративной информацииПринцип минимизации данных (Фильтрация контекста)🔒 Локальная зашифрованная БДДоступны только назначенные задания сотрудника ✔

При этом шифрование не отменяет необходимость минимизации данных. Если сотруднику для работы нужны только назначенные ему задания, нет необходимости загружать на устройство весь массив корпоративной информации.

 

 

БЕЗОПАСНОСТЬ // OFFLINE_AUTH_LIMITATIONS

Авторизация без связи

Приложение может разрешать доступ к заранее загруженным данным после временной потери сети. Однако необходимо определить, какие действия доступны без повторной проверки полномочий и как долго сохраняются такие разрешения.

ФУНДАМЕНТАЛЬНОЕ_ОГРАНИЧЕНИЕ // REVOCATION_LAG_RISK

> Есть фундаментальное ограничение: устройство без связи не может немедленно узнать, что сервер отозвал доступ пользователя.

Для чувствительных операций может потребоваться обязательное подключение, ограниченный период офлайн-доступа или дополнительное подтверждение при следующей синхронизации.

Удалённая блокировка

Удалённая команда блокировки или удаления данных не может гарантированно поступить на устройство, которое полностью отключено от сети.

Потеря связи ➔ Старт офлайн сессии (TTL)Проверка: Лимит времени офлайна превышен?ДаБлокировка сессии локально ❌НетДоступ по кэшу разрешен ✔

Поэтому нельзя строить безопасность исключительно на возможности удалённого управления. Необходимы защита локального хранилища, ограничение объёма данных, безопасное управление ключами и заранее определённые политики доступа.

 

 

АУДИТ // DATA_RETENTION_AND_LOGGING

Журналирование

Журнал операций помогает выяснять причины конфликтов, повторов и отклонений. Но он не должен бесконтрольно собирать содержимое документов, персональные данные и секреты.

БЕЗОПАСНОСТЬ_ЛОГОВ // LOG_DATA_LEAK_PREVENTION

> Нужно заранее определить состав журналируемых событий, доступ к журналам, срок хранения и правила удаления чувствительных сведений.

Конфликт / Отклонение операцииМаскирование ПДн и Секретов (Фильтр логов)Журнал операций (Очищенный мета-трейл)Установлен регламент очистки и TTL хранения ✔

 

 

ВЕРИФИКАЦИЯ // OFFLINE_TESTING_METRICS

Как тестировать офлайн-систему

Обычные функциональные тесты при стабильном соединении не позволяют проверить надёжность офлайн-архитектуры.

Необходимо моделировать потерю связи в произвольный момент, повторную доставку запросов, конкурентные изменения, перезапуск приложения и частичное завершение операций.

МАТРИЦА_ТЕСТ_КЕЙСОВ // EDGE_CASE_TEST_SUITE

[case] Связь пропадает до локального сохранения.

[case] Приложение завершается после локального сохранения, но до отправки.

[case] Сервер применяет операцию, а подтверждение теряется.

[case] Один запрос отправляется повторно несколько раз.

[case] Объект изменяется на сервере до синхронизации локальной версии.

[case] Две зависимые операции отправляются в неправильном порядке.

[case] Загрузка вложения прерывается.

[case] Сервер отзывает доступ пользователя во время его отсутствия в сети.

[case] Устройство не подключается несколько дней.

[case] Локальная база не может завершить транзакцию.

[case] Приложение получает новые серверные данные одновременно с локальным изменением объекта.

[case] Несколько устройств одновременно отправляют одну и ту же логическую операцию.

Для каждого сценария нужно определить ожидаемое состояние системы: какие данные должны сохраниться, какие действия разрешено повторять, когда возникает конфликт и что увидит пользователь.

Тестировать нужно не только ошибки, но и восстановление

Предположим, приложение отправило 100 операций. Сервер обработал часть из них, после чего соединение пропало. После восстановления система должна продолжить обмен, не теряя неподтверждённые операции и не создавая повторных бизнес-эффектов.

МЕТОДОЛОГИЯ_ТЕСТИРОВАНИЯ // RECOVERY_TEST_MANDATE

> Это означает, что тестирование должно охватывать не только факт возникновения сбоя, но и весь последующий путь восстановления.

 

 

МОНИТОРИНГ // POST_LAUNCH_TELEMETRY_METRICS

Какие показатели контролировать после запуска

Для оценки надёжности офлайн-режима полезно отслеживать:

СПЕЦИФИКАЦИЯ_МОНИТОРИНГА // TELEMETRY_CHECKLIST

[metric] количество операций, ожидающих синхронизации;

[metric] возраст самой старой необработанной операции;

[metric] долю операций, завершённых без ручного вмешательства;

[metric] частоту конфликтов по типам объектов;

[metric] количество повторных запросов;

[metric] случаи дублирования бизнес-эффектов;

[metric] время от локального сохранения до серверного подтверждения;

[metric] количество операций с неопределённым результатом;

[metric] ошибки передачи вложений;

[metric] потери локальных изменений.

МЕТОДОЛОГИЯ_АНАЛИЗА // AVERAGING_FALLACY_RISK

При этом среднее время синхронизации не должно быть единственным показателем качества. Система может быстро передавать большинство операций, но при этом редко терять критичные изменения.

> Для бизнеса такие редкие ошибки могут оказаться гораздо существеннее обычных задержек.

Телеметрию следует собирать с учётом конфиденциальности: для диагностики чаще достаточно идентификаторов, типов операций, версий и кодов ошибок, а не содержимого документов.

 

 

АНАЛИЗ_ОШИБОК // ARCHITECTURAL_ANTI_PATTERNS

Типичные архитектурные ошибки

Даже если локальная база и очередь уже реализованы, отдельные решения могут сделать систему ненадёжной.

РЕВИЗИЯ_АНТИПАТТЕРНОВ // DEFECT_RESOLUTION_MATRIX
Ошибка 1. Ложная завершённость

[Дефект]: Считать локальное сохранение подтверждением сервера. Создаёт иллюзию готовности, когда данные ещё не доступны другим.

✔ Решение: Разделить локальное сохранение и серверное подтверждение в модели состояний и UI.
Ошибка 2. Слепая перезапись данных

[Дефект]: Безусловно заменять локальные данные серверными. Обновление может полностью уничтожить несинхронизированную работу.

✔ Решение: Учитывать локальные изменения, версии и незавершённые операции при обновлении.
Ошибка 3. Анонимные повторы

[Дефект]: Повторять запрос без идентификатора операции. Если исходный статус неясен, новый запрос создаст дубликат.

✔ Решение: Устойчивые идентификаторы операций и серверная идемпотентная обработка.
Ошибка 4. Слепое доверие хронологии

[Дефект]: Считать, что последняя запись всегда правильная. Порядок поступления пакетов не определяет их бизнес-приоритет.

✔ Решение: Применять версионные проверки и правила, соответствующие смыслу конкретной операции.
Ошибка 5. Игнорирование зависимостей

[Дефект]: Не учитывать связи. Приводит к отправке завершения документа раньше создания или передаче вложения без объекта.

✔ Решение: Явно моделировать зависимости и разрешать параллелизм только там, где он безопасен.
Ошибка 6. Игнорирование затяжного офлайна

[Дефект]: Не проверять длительное отсутствие связи. Локальные данные стареют, права отзываются, объекты отменяются.

✔ Решение: Предусмотреть проверку актуальности, обработку удалений и процедуру восстановления после долгого офлайна.
Ошибка 7. Преждевременная очистка

[Дефект]: Удалять операцию из очереди сразу после отправки. Сам факт отправки сетевого пакета не гарантирует фиксацию на сервере.

✔ Решение: Завершать обработку операции только после получения или восстановления достоверного результата.

 

 

ПРОЕКТИРОВАНИЕ // ARCHITECTURE_MATCHING

Как выбрать подходящую архитектуру

Не каждому мобильному приложению нужна сложная система синхронизации. Архитектура должна соответствовать последствиям ошибки и характеру бизнес-процесса.

УРОВНИ_ОФЛАЙН_СИСТЕМ // CAPABILITY_STAGES

[stage 1] Справочные системы: достаточно локального кэша и планового обновления данных.

[stage 2] Автономный ввод: необходимы локальная база, очередь Outbox, UUID операций и повторы.

[stage 3] Совместная работа: требуются версионный контроль (MVCC) и стратегии разрешения конфликтов.

[stage 4] Критичные операции: обязательны бизнес-инварианты на бэкенде, аудит и разбор неизвестных исходов.

Перед проектированием полезно ответить на следующие вопросы:

ВОПРОСЫ_АРХИТЕКТОРУ // DESIGN_CHECKLIST

[?] Какие действия пользователь должен выполнять без подключения?

[?] Какие данные необходимо заранее загрузить на устройство?

[?] Какие изменения допустимо объединять автоматически?

[?] Какие операции требуют подтверждения актуального состояния сервера?

[?] Как определяется результат запроса, если ответ потерян?

[?] Как восстанавливается синхронизация после частичного сбоя?

[?] Что происходит с локальными данными при отзыве доступа?

[?] Как система обрабатывает устройства, которые долго не выходили в сеть?

Эти ответы определяют требования к архитектуре гораздо сильнее, чем выбор конкретной библиотеки или сетевого протокола.

 

 

РЕЗЮМЕ // OFFLINE_ARCHITECTURE_CONCLUSION

Заключение

Работа без постоянного интернета требует от мобильной системы самостоятельности на уровне хранения данных, обработки операций и восстановления после сбоев.

Локальная база позволяет продолжать работу, очередь сохраняет действия для последующей передачи, идемпотентность защищает от повторного выполнения, а версионный контроль помогает обнаруживать несовместимые изменения. Дополнительные правила необходимы для вложений, удаления записей, зависимых операций и защиты локальных данных.

При этом универсального алгоритма синхронизации, подходящего для любого бизнеса, не существует. Решение зависит от того, какие операции выполняются, какие изменения могут происходить параллельно и насколько критичны последствия потери или повторного применения данных.

Надёжная система должна не просто отправлять изменения после восстановления связи, но и сохранять возможность объяснить, что произошло с каждой операцией: была ли она принята, повторена, объединена, отклонена или направлена на разрешение конфликта.

Главный принцип офлайн-архитектуры:

временная потеря связи допустима, но потеря уже сохранённой работы, незаметная перезапись изменений и неконтролируемое повторное выполнение бизнес-операций не должны становиться нормальным поведением системы.

Именно это отличает простую надстройку над кэшем от полноценного контура автономной работы мобильной системы.

 

 

НАВИГАЦИЯ // SUGGESTED_READING
 
[ Модуль // ИНТЕГРАЦИИ ]

API, Webhook или очередь сообщений?

 
[ Модуль // ИНТЕГРАЦИИ ]

Когда API есть, а интеграция всё равно не работает

 
[ Модуль // АВТОМАТИЗАЦИЯ ]

Как соединить производство и корпоративные системы

 
[ EXPERTISE JOURNAL ]

Все инженерные материалы и экспертные статьи

 
[ CORE GLOSSARY ]

Полный алфавитный справочник ИТ-терминов

 

[ DIRECT EMAIL LINE ]
info@log-ai.ru

 

🔒 Данные и доступ к системам заказчика обрабатываются в соответствии с согласованными требованиями к безопасности и конфиденциальности. Условия доступа, хранения и обработки данных определяются архитектурой проекта и договором.

Сайт носит исключительно информационный характер и не является публичной офертой в соответствии со статьёй 437 Гражданского кодекса РФ. Цены, состав и условия предоставления услуг уточняются при проектировании решения и фиксируются в договоре. Условия технического сопровождения и уровень сервиса могут дополнительно определяться отдельным соглашением (SLA).

© 2026 Log-AI Москва
Автоматизация бизнеса и ИИ-решения под ключ
Самозанятый Скопец Антон Викторович ИНН 741709260870
Политика конфиденциальности Пользовательское соглашение
AI Ассистент
×