Реалізація міграції даних при оновленні мобільного додатку

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
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

Реалізація міграції даних при оновленні мобільного додатку

Міграція схеми та міграція даних — різні завдання. Можна ідеально написати ALTER TABLE, але при цьому отримати биті дані у production. Це трапляється, коли змінюється не структура таблиці, а формат або семантика збережених значень: дати із 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 дні.