Реализация миграции данных при обновлении мобильного приложения

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Реализация миграции данных при обновлении мобильного приложения
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1054
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Реализация миграции данных при обновлении мобильного приложения

Миграция схемы и миграция данных — разные задачи. Можно идеально написать ALTER TABLE, но при этом получить битые данные в продакшене. Это происходит, когда меняется не структура таблицы, а формат или семантика хранимых значений: даты из Unix timestamp переходят в ISO 8601, суммы из float в integer-cents, статусы из числовых кодов в строковые enum. Такие преобразования требуют явной обработки для каждой строки.

Что такое миграция данных на практике

Конкретные сценарии из реальных проектов:

  • Поле amount хранилось как REAL (double), нужно перейти на INTEGER центов, чтобы избежать floating-point ошибок при сравнении. 100.1010010.
  • Поле status было INTEGER (0, 1, 2), теперь TEXT ("pending", "active", "completed"). Нужно смаппировать каждое число в строку.
  • Поле date было Unix timestamp в секундах, в новой версии — миллисекунды. 17000000001700000000000.
  • JSON, хранившийся в TEXT-колонке, изменил структуру: старый {"items":[...]} → новый {"data":{"list":[...]}}.

Каждый из этих случаев — строковая трансформация всей таблицы внутри миграции.

Реализация в Room (Android)

val MIGRATION_3_4 = object : Migration(3, 4) {
    override fun migrate(db: SupportSQLiteDatabase) {
        // Конвертация суммы из float в integer cents
        db.execSQL("""
            UPDATE transactions
            SET amount_cents = CAST(ROUND(amount * 100) AS INTEGER)
        """)

        // Конвертация статуса из int в string
        db.execSQL("UPDATE transactions SET status = 'pending' WHERE status_code = 0")
        db.execSQL("UPDATE transactions SET status = 'active'  WHERE status_code = 1")
        db.execSQL("UPDATE transactions SET status = 'completed' WHERE status_code = 2")

        // Конвертация timestamp из секунд в миллисекунды
        db.execSQL("UPDATE events SET created_at = created_at * 1000 WHERE created_at < 9999999999")
    }
}

Условие WHERE created_at < 9999999999 в последнем примере защищает от повторного применения при ошибочном повторном запуске — дата в миллисекундах всегда больше этого числа.

Batch-обновление для больших таблиц

Если в таблице миллионы строк — обновление одним UPDATE может занять десятки секунд и заблокировать запуск. Батчевый подход:

val MIGRATION_4_5 = object : Migration(4, 5) {
    override fun migrate(db: SupportSQLiteDatabase) {
        var offset = 0
        val batchSize = 1000
        while (true) {
            val updated = db.compileStatement("""
                UPDATE transactions
                SET metadata = transform_metadata(metadata)
                WHERE id IN (
                    SELECT id FROM transactions
                    WHERE metadata_migrated = 0
                    LIMIT $batchSize
                )
            """).executeUpdateDelete()
            if (updated == 0) break
        }
    }
}

Для очень больших таблиц (500 000+ строк) — миграцию лучше делать лениво: при первом обращении к записи, не в onUpgrade. Добавить поле-флаг migrated INTEGER DEFAULT 0 и трансформировать при чтении.

Ленивая миграция данных

Когда полная миграция занимает слишком долго для блокирующего выполнения при старте:

// iOS — ленивая миграция при доступе к данным
func fetchTransaction(id: String) -> Transaction {
    let raw = database.fetch(id: id)
    if !raw.isMigrated {
        let migrated = DataMigrator.migrate(raw)
        database.save(migrated)
        return migrated
    }
    return raw
}

Плюс: приложение стартует мгновенно. Минус: нужно поддерживать оба формата в коде, пока не все записи мигрированы. Фоновый WorkManager / BGProcessingTask постепенно мигрирует оставшееся.

Тестирование трансформаций

@Test
fun testAmountConversion() {
    val helper = MigrationTestHelper(instrumentation, AppDatabase::class.java)
    val db = helper.createDatabase("test.db", 3)
    db.execSQL("INSERT INTO transactions (id, amount) VALUES ('t1', 100.10)")
    db.close()

    val migrated = helper.runMigrationsAndValidate("test.db", 4, true, MIGRATION_3_4)
    val cursor = migrated.query("SELECT amount_cents FROM transactions WHERE id = 't1'")
    cursor.moveToFirst()
    assertEquals(10010, cursor.getInt(0))
}

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

Откат при ошибке

SQLite поддерживает транзакции — весь onUpgrade автоматически оборачивается в транзакцию в Room. Если что-то падает — изменения откатываются. На iOS с Core Data — аналогично через NSMigrationManager.

Но откат не означает «приложение работает нормально» — при следующем запуске снова попытается мигрировать. Нужна обработка ошибок и отображение пользователю сообщения о проблеме.

Что входит в работу

  • Аудит данных в текущей БД: форматы, исключения, NULL-значения
  • Написание трансформаций с защитой от повторного применения
  • Batch-обновление для больших таблиц
  • Ленивая миграция для критически больших объёмов
  • Тесты на граничных случаях

Сроки

Простые UPDATE-трансформации (1–3 таблицы): 1 день. Сложные преобразования JSON, ленивая миграция с фоновым воркером: 2–4 дня.