← Назад к вопросам

Что было бы записано в базе данных в приложении с синхронизацией клиент-сервера

1.8 Middle🔥 151 комментариев
#Архитектура и паттерны#Сетевое взаимодействие

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные принципы и сценарии записи данных

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

1. Локальная база данных на устройстве (Room/SQLite)

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

  • Оптимистичная запись (Optimistic Sync): Данные сразу сохраняются локально как "валидные", а затем асинхронно отправляются на сервер.
  • Пессимистичная запись (Pessimistic Sync): Данные помечаются как "ожидающие" (pending), и отправляются на сервер, а после успешного ответа помечаются как "синхронизированные".

Пример структуры сущности в Room для оптимистичной синхронизации:

@Entity(tableName = "tasks")
data class Task(
    @PrimaryKey(autoGenerate = false)
    val id: String, // Глобальный UUID, генерируется на клиенте
    val title: String,
    val isCompleted: Boolean,

    // Поля для управления синхронизацией
    @ColumnInfo(name = "last_modified")
    val lastModified: Long, // Timestamp изменения на клиенте
    @ColumnInfo(name = "is_dirty")
    val isDirty: Boolean = false, // Флаг "грязных" данных, измененных локально
    @ColumnInfo(name = "deleted")
    val deleted: Boolean = false // "Мягкое" удаление
)

Что записывается:

  • Новые объекты с уникальным id (часто UUID).
  • Поля lastModified (время изменения) и isDirty = true при любом редактировании.
  • При удалении — флаг deleted = true, а не физическое удаление строки.

2. Данные, отправляемые на сервер

На сервер отправляются не все данные, а только изменения. Обычно это происходит через очередь запросов (Request Queue) или журнал изменений (Change Log). Отправляется дельты (разница).

Пример тела запроса для синхронизации в формате JSON:

{
  "device_id": "abc123",
  "last_sync_timestamp": 1678886400000,
  "changes": [
    {
      "operation": "CREATE",
      "entity_type": "task",
      "entity_id": "task_uuid_1",
      "data": {
        "title": "Купить молоко",
        "isCompleted": false
      },
      "client_timestamp": 1678886500000
    },
    {
      "operation": "UPDATE",
      "entity_type": "task",
      "entity_id": "task_uuid_2",
      "data": {
        "isCompleted": true
      },
      "client_timestamp": 1678886600000
    }
  ]
}

3. База данных на сервере

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

  • Версионность (Versioning): У каждого объекта есть поле version или updated_at.
  • Векторные часы (Vector Clocks): Для сложных распределенных систем.

После применения изменений сервер сохраняет:

  1. Актуальное состояние сущностей.
  2. Историю изменений (для отката и анализа конфликтов).
  3. Информацию о последней синхронизации для каждого устройства (например, last_synced_at).

4. Ответ сервера и фиксация на клиенте

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

{
  "accepted_changes": ["task_uuid_1", "task_uuid_2"],
  "rejected_changes": [],
  "server_changes": [
    {
      "entity_type": "task",
      "entity_id": "task_uuid_3",
      "operation": "CREATE",
      "data": { "title": "Новая задача с сервера", "isCompleted": false },
      "server_version":(Markdown)
    }
  ],
  "new_last_sync_timestamp": 1678886700000
}

На клиенте в этот момент происходит:

  • Сброс флага isDirty у успешно принятых сервером объектов.
  • Обновление локальных данных на основе server_changes.
  • Обновление метки времени последней синхронизации.

5. Конфликтующие изменения и их разрешение

Самый сложный сценарий — конфликт данных (когда один объект был изменен и на сервере, и на клиенте). Стратегии разрешения:

  • "Last Write Wins" (LWW): Побеждает изменение с более поздним timestamp (риск потери данных).
  • Клиент всегда побеждает / Сервер всегда побеждает.
  • Интерактивное разрешение: Пользователю показывается диалог выбора.
  • Слияние (Merge): Для определенных типов данных (например, списков).

В базу данных при конфликте может быть записано:

  1. Победившая версия данных.
  2. Проигравшая версия — в таблицу конфликтов для возможного последующего анализа.

Критически важные поля в БД для синхронизации (итог):

  • Глобальный уникальный ID (UUID): Идентификация объекта между клиентом и сервером.
  • Флаги состояния синхронизации: isDirty, isPending, deleted.
  • Метки времени: createdAt, updatedAt, lastModified, syncedAt — на обеих сторонах.
  • Версия: version (инкрементируется на сервере при каждом изменении).
  • Идентификатор устройства/пользователя: Для мультиустройственной синхронизации.

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

Что было бы записано в базе данных в приложении с синхронизацией клиент-сервера | PrepBro