Isar Flutter: чому запити до локальної бази гальмують на зростанні даних
Локальна база Flutter майже ніколи не гальмує на старті: на сотні записів будь-який запит відповідає миттєво, і це створює хибне відчуття, що схема спроєктована правильно.
Проблема проявляється пізніше, коли колекції виростають до десятків тисяч об'єктів — екран списку починає відкриватися із затримкою, пошук «підвисає» на введенні, а прокрутка смикається.
Причина не в самому сховищі, а в тому, що індекси та запити до Isar Flutter проєктувалися навмання, без опори на реальні сценарії читання.
Вузьке місце зазвичай одне з трьох. Перше — фільтрація за полем без індексу: замість вибірки за ключем двигун перебирає всю колекцію, і час відповіді зростає лінійно разом з даними.
Друге — сортування та пагінація «в лоб», коли потрібний порядок не підтриманий індексом і кожен запит заново перебудовує вибірку. Третє — реактивні підписки на занадто широкі запити: будь-яка зміна в колекції перебудовує весь список на екрані, хоча змінився один запис.
Ціна такої архітектури видна не відразу, але накопичується: користувач сприймає застосунок як «важкий», зростає кількість звернень у підтримку, а команда витрачає час на точкові «костулі» замість виправлення схеми.
Найгірше те, що деградація нелінійна — до певного обсягу все терпимо, а потім поведінка змінюється різко.
Розбираємо, як це закривається: як проєктувати індекси під реальні запити, обмежувати реактивні підписки та тримати час відгуку стабільним незалежно від того, скільки записів накопичилося в користувача.
Що дає вивірена схема зберігання та доступу до даних
Швидкість вибірки в Isar визначається не обсягом бази, а тим, чи потрапляє запит в індекс.
Поки фільтр іде по полю без індексу, двигун перебирає всі записи колекції — на десятках тисяч об'єктів це помітно неозброєним оком: список відкривається із затримкою, пошук «підгальмовує» під час введення.
Індекс перетворює такий перебір на точкове читання, і вибірка за ключем або діапазоном дат перестає залежати від того, скільки всього записів накопичилося.
Споживання пам'яті зростає через те, як застосунок читає дані. Запит, що повертає об'єкти цілком, тягне в пам'ять усі поля, включно з важкими — вкладені списки, довгі тексти, посилання на пов'язані колекції.
Якщо екрану потрібні лише ім'я та дата, розумніше повернути проєкцію з потрібних полів, а пов'язані сутності підвантажувати окремим запитом за ідентифікатором. Так пік пам'яті на екрані залишається рівним, а не стрибає щоразу при відкритті розділу.
Реактивність — третя частина картини. Підписка на запит віддає оновлення при зміні даних, але якщо підписатися на всю колекцію, перемальовування спрацьовує на будь-яку зміну й тягне за собою зайву роботу. Підписка на вузький запит з індексом оновлює лише ті екрани, яких зміна реально стосується.
Як це виглядає в цифрах
- Вибірка за індексованим полем перестає сповільнюватися в міру зростання бази: час відповіді тримається на одному рівні при десятикратному збільшенні обсягу.
- Проєкції замість повних об'єктів знижують пікове споживання пам'яті на списках у рази.
- Точкові підписки прибирають перемальовування, не пов'язані зі зміною даних.
Стійкість до зростання — наслідок цих трьох рішень. Схема з продуманими індексами, проєкціями та вузькими підписками не потребує переписувати шар доступу до даних, коли колекції виростають на порядок.
Підходи до організації локального сховища: порівняння варіантів
Локальне сховище у Flutter — це не «одна бібліотека на все».
Вибір залежить від трьох речей: структури даних (плоскі пари ключ-значення чи пов'язані сутності), характеру доступу (точкове читання за ідентифікатором чи вибірки з фільтрами й сортуванням) і вимог до реактивності — чи має інтерфейс сам перемальовуватися при зміні запису. Нижче — реальні альтернативи, між якими обирають на старті проєкту.
| Варіант | Коли підходить | Обмеження |
|---|---|---|
| Сховище ключ-значення | Налаштування, прапорці, токени, невеликі довідники | Немає зв'язків між сутностями та вибірок за умовою; складні фільтри доводиться робити в пам'яті |
| Реляційна база | Строга схема, звіти, зв'язки «один-до-багатьох», агрегати | Важча в налаштуванні, міграції схеми потребують ручного контролю |
| Документне сховище | Гнучка схема, вкладені об'єкти, швидкий старт | Слабше виражені зв'язки, частина перевірок лягає на код застосунку |
| Об'єктне сховище з індексами | Пов'язані сутності, вибірки за полями, реактивні потоки | Потрібно проєктувати індекси заздалегідь, інакше вибірки деградують на зростанні даних |
Окремо варто згадати режим доступу: синхронне читання зручне для дрібних довідників, але на великих вибірках блокує інтерфейс, тому важкі запити виносять в асинхронні потоки.
Реактивний режим — підписка на зміни колекції — позбавляє від ручного оновлення екранів, але потребує акуратної відписки, інакше утечі пам'яті.
Практичний висновок: для налаштувань достатньо сховища ключ-значення, для облікових і пов'язаних даних — об'єктне сховище з індексами, реляційна база виправдана там, де потрібні звіти й агрегати. Змішувати режими в одному застосунку нормально: під кожен клас задач — свій інструмент.
Як ми налаштовуємо Isar Flutter: етапи інженерної роботи
Налаштування Isar у Flutter — це не «підключити пакет і забути», а робота зі схемою даних: від того, як спроєктовані індекси та запити, залежить, чи займатиме пошук по локальній базі частки секунди на десятках тисяч записів, чи впиратиметься в повний перебір колекції. Нижче — як ми ведемо таку роботу за кроками, від аудиту до передачі.
- Аудит поточної схеми. Дивимося колекції, зв'язки між ними, типи полів і те, які запити реально виконуються в застосунку. Часто з'ясовується, що частина фільтрів іде по неіндексованих полях, а вкладені об'єкти зберігаються так, що вибірка тягне за собою зайві дані.
- Проєктування індексів. Під кожен сценарій пошуку та сортування визначаємо складений індекс із правильним порядком полів: спочатку поле рівності, потім діапазон, потім сортування. Прибираємо дублювальні індекси — кожен із них прискорює читання, але сповільнює запис і займає місце у файлі бази.
- Налаштування запитів. Переписуємо вибірки під індекси: замінюємо фільтрацію в пам'яті на умови на боці бази, додаємо посторінкову видачу замість завантаження всієї колекції, для реактивних екранів підключаємо спостереження за запитом замість ручного перечитування даних після кожної зміни.
- Перевірка на реальних обсягах. Проганяємо сценарії на даних, близьких до бойових: десятки тисяч записів, конкурентний запис, холодний старт. Заміряємо час вибірки та запису, дивимося, де з'являються блокування, і коригуємо схему до того, як це побачать користувачі.
- Міграції та сумісність. Продумуємо, як схема змінюватиметься далі: версіонування, перенесення даних при оновленні застосунку, поведінка при відкаті на попередню версію. Це захищає від втрати локальних даних у частини користувачів після релізу.
- Документація та передача. Фіксуємо підсумкову схему, призначення кожного індексу та правила додавання нових запитів. Ваша команда отримує не разовий патч, а зрозумілий регламент, за яким база залишається швидкою при розвитку продукту.
Опишіть вашу задачу — повернемося з оцінкою та планом робіт за вашою схемою даних.
Індекси, реактивні запити та міграції схеми
Індекс в Isar — це не «прискорення на всяк випадок», а рішення конкретної вибірки. Складений індекс за парою полів працює, лише якщо порядок полів у запиті збігається з порядком в індексі; при фільтрі за одним «хвостовим» полем він не застосовується.
Тому індексуємо те, за чим реально йде фільтрація та сортування: зовнішні ідентифікатори, статуси, дати. Індекс на низькоселективному полі (прапор із двох значень) дає зростання файлу бази без виграшу на читанні — його краще не заводити.
Реактивність будуємо на watch-запитах, а не на ручному оновленні стану. Потік віддає нову вибірку при зміні заторкнутих об'єктів, і UI перемальовується точково.
Щоб не ловити зайві перебудови, звужуємо запит до конкретного списку або одного об'єкта і не підписуємося на всю колекцію: підписка на широкий набір смикає інтерфейс при кожному записі в базу.
Міграції схеми — найбільш недооцінена частина. Isar зберігає версію схеми, і при незбігу відкриття бази падає, якщо не описати перехід.
Ми фіксуємо версіонування даних явно: додаємо крок міграції, переносимо поля, при перейменуванні заводимо нове поле і переносимо значення, а не перевикористовуємо старе.
- Індекси — лише під реальні фільтри та сортування, з перевіркою складу й порядку полів.
- Реактивні підписки — точкові, з відпискою при виході з екрана, щоб не тримати потік.
- Версіонування схеми — явна версія та покрокові міграції замість «перестворити базу».
- Обробка помилок відкриття — окрема гілка на незбіг версії, а не падіння застосунку.
- Тести міграцій — прогін на копії реальних даних до релізу, а не на порожній базі.
Такий підхід до Isar прибирає клас відмов, коли після оновлення застосунок не стартує у частини користувачів. Розбираємо далі, як це лягає на конкретну схему даних.
Що замовник отримує після налаштування сховища
Налаштування сховища — це не «встановили бібліотеку й забули».
Після робіт у команди замовника залишається комплект артефактів, за якими застосунок можна розвивати без автора: новий розробник відкриває репозиторій і бачить, як влаштовані дані, чому обрано саме такі індекси та що робити при зміні моделі. Нижче — що саме передаємо.
- Схеми колекцій — опис кожної сутності з типами полів, зв'язками та правилами унікальності. За ними видно, які дані лежать локально, а які підтягуються із сервера, і де можливі конфлікти при синхронізації.
- Конфігурації індексів — які поля проіндексовані, складені це індекси чи одиночні, під які запити вони заведені. Окремо позначаємо індекси, які дорого підтримувати при записі, щоб їх не додавали «про всяк випадок».
- Регламенти міграцій — покроковий порядок зміни схеми: як додається поле, як переносяться дані, як відкочується невдала міграція. З версіонуванням, щоб оновлення не затирало дані користувача.
- Документація щодо запитів — типові вибірки, фільтри та сортування з прикладами коду й поясненням, який індекс їх прискорює. Плюс список запитів, які свідомо не оптимізовані, і чому.
- Правила реактивності — які потоки даних підписані на зміни колекцій, як оновлюється інтерфейс і як уникнути зайвих перемальовувань при масовому записі.
- Тести сховища — перевірки на коректність міграцій, цілісність зв'язків і поведінку при паралельному записі. Прогоняються в загальній збірці, а не вручну.
- Інструкція із супроводу — як додати колекцію, перезібрати індекси, відкотити реліз і куди дивитися при розходженні даних.
Комплект залишається у вашому репозиторії та оновлюється разом із кодом. Опишіть задачу — повернемося з оцінкою та планом робіт по вашому застосунку.
Чим відрізняється Isar від інших локальних рішень у Flutter?
Isar — це вбудована об'єктна база даних, яка зберігає дані в одному файлі та працює в тому самому процесі, що й застосунок.
На відміну від рішень, побудованих поверх SQL, тут немає окремого шару трансляції між об'єктами в коді та рядками в таблиці: модель описується класами, а запити будуються через типізований інтерфейс.
Це прибирає цілий клас помилок — розсинхронізацію схеми, ручний розбір результатів, невідповідність типів під час читання.
Ключова відмінність від класичних SQL-рішень — модель реактивності. Isar вміє віддавати потік, який сам оновлюється при зміні даних, тому інтерфейс перемальовується без ручного опитування бази та без проміжного шару керування станом.
Там, де на SQL довелося б вручну інвалідувати кеш і стежити за актуальністю вибірки, тут підписка на запит закриває задачу повністю.
За швидкістю на масових операціях Isar виграє завдяки відсутності накладних витрат на трансляцію запитів і роботі з пам'яттю напряму. Індекси та складені індекси налаштовуються декларативно, тому вибірка за кількома полями залишається передбачуваною навіть на великих обсягах.
Але виграш не універсальний: складні аналітичні вибірки з групуванням і об'єднаннями все ще зручніше й надійніше робити на SQL — там це рідна територія, а не надбудова.
Є й сценарії, де Isar поступається. Якщо потрібна синхронізація між пристроями з розв'язанням конфліктів, вбудованих механізмів тут немає — логіку доведеться будувати самим.
Для застосунків із жорсткими вимогами до серверної валідації та складною рольовою моделлю доступу реляційна база з обмеженнями на рівні схеми дає більше гарантій.
І якщо команда вже щільно працює з SQL, перехід на об'єктну модель потребуватиме перенавчання та переписування шару доступу до даних.
На практиці вибір виглядає так: Isar беруть там, де важливі швидкість локальних операцій, реактивний інтерфейс і простота моделі — списки, стрічки, офлайн-режим, кеш відповідей сервера.
SQL залишається сильнішим у звітності, складних зв'язках і там, де цілісність даних критична на рівні схеми. Рішення ухвалюється не за «краще/гірше», а за тим, який профіль навантаження в конкретного застосунку.
Кейс: прискорили вибірки в застосунку з базою, що зростає
Що було до змін
Застосунок — каталог з офлайн-режимом на Isar: локальна база зберігає товари, категорії та історію замовлень, синхронізація із сервером іде у фоновому режимі. На старті все працювало, але база зростала: спочатку десятки тисяч записів, потім сотні.
Списки категорій і результатів пошуку почали відкриватися з помітною затримкою, а на слабких пристроях з'являлися пропуски кадрів під час прокручування.
Корінь виявився не в обсязі даних, а в тому, як ми до них зверталися. Фільтри та сортування йшли за полями без індексів, застосунок витягував з бази весь набір об'єктів і відсіював зайве вже в пам'яті.
Паралельно синхронізація переписувала пов'язані сутності цілком, і реактивні підписки на екранах спрацьовували на кожне таке оновлення — інтерфейс перемальовувався частіше, ніж змінювалися дані.
Що змінили
Перезібрали схему: додали складені індекси під реальні сценарії — категорія плюс дата, статус плюс дата зміни, окремий індекс для пошуку за назвою.
Запити переписали так, щоб фільтрація та сортування виконувалися на боці бази, а не в пам'яті, і додали посторінкову вибірку замість читання всього набору.
Синхронізацію перевели на точкові оновлення: замість перезапису пов'язаних сутностей цілком змінюються лише ті поля, що змінилися. Реактивні підписки звузили до конкретних запитів — екран списку більше не реагує на оновлення картки товару, яку користувач не відкривав.
Метрики до і після
| Показник | До | Після |
|---|---|---|
| Відкриття списку категорій | 1.8–2.4 с | 180–260 мс |
| Пошук по каталогу | 3.1 с | 240–310 мс |
| Споживання пам'яті на екрані списку | 210–260 МБ | 90–120 МБ |
| Пропуски кадрів під час прокручування | регулярно | поодинокі |
Окремо закрили клас відмов під час синхронізації: раніше при обриві зв'язку на середині перезапису пов'язаних сутностей частина записів залишалася в проміжному стані, і користувач бачив розбіжність між локальною базою та сервером.
Точкові оновлення в межах однієї транзакції прибрали це вікно — неповних станів після збою більше не виникає.
Робота зайняла два тижні, включно з прогоном на реальних вивантаженнях бази.
Часті технічні сумніви щодо Isar
Isar у Flutter ставлять, коли застосунку потрібна локальна база з реактивними запитами та передбачуваною швидкістю на великих обсягах. Нижче — розбір сумнівів, з якими команди звертаються найчастіше.
Наскільки Isar сумісний із поточним стеком Flutter?
Isar — нативна база для Dart, працює і на мобільних платформах, і на десктопі, і у вебзбірці через окремий бекенд. Схеми описуються звичайними класами з анотаціями, генератор збирає код доступу на етапі збірки — ручний мапінг рядків і колонок не потрібен. Якщо в проєкті вже є шар репозиторіїв, Isar вбудовується за ним без переписування екранів.
Який обсяг даних витримує без деградації?
Запити йдуть за індексами, а не повним перебором, тому вибірка за складеним ключем залишається швидкою і на сотнях тисяч записів. Індекси оголошуються у схемі: унікальні, складені, з заміною регістру.
Важливо стежити за розміром індексованих полів — роздутий індекс сповільнює запис сильніше, ніж зростає сама база.
Як влаштована реактивність і чи не буде зайвих перемальовувань?
Підписка на запит віддає потік, який перезбирається при зміні заторкнутих об'єктів. Оновлення точкове: правка одного запису не тягне перерахунок усієї колекції, якщо запит обмежений фільтром. На практиці це знімає ручну синхронізацію стану між екранами.
Що з міграціями при зміні схеми?
Версія схеми зберігається в самій базі, при незбігу запускається міграція: перейменування полів, зміна типів, перебудова індексів. Дані користувача при цьому зберігаються — критично для застосунків, де локальна база це єдине сховище. Міграції проганяються на копії реальних даних до релізу.
Підтримка платформ і доступ до файлу бази
Одна кодова база закриває мобільні платформи та десктоп; відмінності стосуються шляху до файлу та прав доступу, їх закриває шар ініціалізації.
Для вебу використовується окремий режим зберігання — частина нативних можливостей там недоступна, це враховують на етапі проєктування схеми.
Якщо залишилися питання щодо конкретного сценарію — опишіть задачу, повернемося з оцінкою та планом.
Обговоримо ваше завдання щодо локального сховища
Щоб оцінка була точною, а не «за відчуттями», нам потрібні вихідні дані щодо вашого проєкту: схема даних і зв'язки між сутностями, сценарії читання та запису, вимоги до офлайн-режиму й синхронізації. Чим конкретніші вхідні дані, тим менше припущень у плані робіт і тим передбачуваніші строки.
Опишіть завдання — повернемося з оцінкою та планом робіт. У відповідь ви отримаєте:
- розбір поточної схеми зберігання та вузьких місць: де запити виконуються повним перебором, а де бракує індексів;
- пропозицію щодо структури колекцій і складу індексів під ваші сценарії вибірки;
- схему реактивних підписок: які екрани на що підписані та як уникнути зайвих перебудов інтерфейсу;
- план міграції даних між версіями схеми без втрати користувацьких записів;
- стратегію синхронізації із сервером: розв'язання конфліктів, повторні спроби, робота при обриві зв'язку;
- перелік перевірок на тестових наборах даних, включно з граничними випадками та великими обсягами;
- оцінку трудомісткості за етапами та склад команди під ваш обсяг.
Якщо застосунок уже в продакшені, окремо проговоримо, як змінювати схему на живій базі: версіонування, зворотна сумісність, відкат на попередню версію застосунку. Це той клас завдань, де помилка в міграції коштує дорожче, ніж сама розробка.
Надішліть доступ до репозиторію або вивантаження моделей та опис ключових запитів — цього достатньо, щоб перейти від загальних слів до конкретного плану.







