Защо да извличаме данни, когато можем да ги синхронизираме? Локално-ориентирана архитектура с ElectricSQL и TanStack DB

Изображение: Svetni.me / ИИ генерирано изображение
В съвременната уеб разработка стремежът към по-висока производителност и по-бърз интерфейс е движеща сила за еволюцията на платформите. В своята презентация на конференцията QCon San Francisco [1], James Arthur – съосновател на ElectricSQL [2] – разглежда критичните ограничения на традиционния модел за извличане на данни (data fetching). Той твърди, че след години на оптимизиране на реактивността в рамките на клиента (чрез сигнали, редуктори и компоненти), следващата голяма стъпка е разширяването на тази реактивност до самия сървър [1].
Вместо разработчиците ръчно да пишат код за изпращане на заявки по мрежата и да се справят с междинни състояния на зареждане, архитектурата трябва да премине изцяло към синхронизация. Това е основата на т.нар. локално-ориентирани (local-first) приложения, при които данните са достъпни локално и се управляват декларативно.
Реактивният модел на синхронизация
Традиционните уеб приложения разчитат на императивни мрежови заявки за зареждане и промяна на състоянието, което неизбежно въвежда латентност от десетки или стотици милисекунди. За разлика от тях, синхронизиращата архитектура (sync engine architecture) разделя интерфейса от директното извикване на мрежови API. Тя разчита на два реактивни цикъла: вътрешен (за локалния клиент) и външен (между клиента и сървъра) [1].
При извършване на запис от потребителя, промяната веднага се отразява в локалното хранилище като оптимистично състояние. Това незабавно задейства прерисуване на интерфейса и потребителят вижда промяната без забавяне. Вторият цикъл работи във фонов режим, изпращайки записите към сървъра и следейки за нови промени в централната база данни. Когато промените от други потребители или процеси пристигнат в базата данни, те се синхронизират обратно към клиента, като оптимистичното състояние се заменя с потвърдените данни [1].

Изображение: Svetni.me / ИИ генерирана инфографика
Инфраструктурни предизвикателства: HTTP срещу WebSockets
В миналото изграждането на синхронизиращи механизми изискваше значителни инженерни ресурси и беше запазена територия за компании с големи екипи като Meta, Notion или Figma. Основното предизвикателство се корени в избора на мрежов протокол за пренос на данни в реално време. Докато HTTP заявките са без състояние (stateless), лесни за наблюдение и кеширане, то WebSockets връзките изискват поддържане на състояние на сървъра и са трудни за мащабиране [1].
За да реши този проблем, екипът на ElectricSQL проектира протокол, базиран на HTTP дълго съгласуване (long polling). Този подход позволява преносът на промени да се разпределя през стандартна CDN инфраструктура (като Cloudflare или Fastly). Чрез използването на заявки с фиксирани URL адреси се постига обединяване на заявките (request coalescing) на ниво CDN. Когато множество клиенти в една и съща сесия се абонират за едни и същи данни, към централния сървър се изпраща само една заявка, което спестява значителен ресурс на базата данни [1].
За сценарии, които изискват изключително ниска латентност и високочестотен обмен (като присъствие в реално време или стрийминг), ElectricSQL предлага и режим за сървърно изпратени събития (SSE - Server-Sent Events). Това позволява непрекъснат поток от данни с ниски системни изисквания в сравнение със стандартните WebSockets [1].
Интеграция с компоненти: ElectricSQL и TanStack DB
За да улесни разработчиците при внедряването на този модел в съществуващи стекове, ElectricSQL си партнира с общността TanStack, управлявана от Tanner Linsley [3]. Резултатът е TanStack DB – изключително лека (около 20 килобайта) реактивна клиентска база данни. Тя не изисква зареждане на тежки WebAssembly модули и позволява поетапна миграция на съществуващи приложения [1].
TanStack DB въвежда абстракцията на т.нар. колекции (collections) и позволява на разработчиците да пишат декларативни заявки директно към тях. Колекциите могат да бъдат захранени от обикновени API заявки или от ElectricSQL синхронизиращ механизъм (чрез ElectricCollection). Под капака системата използва TypeScript имплементация на диференциален поток от данни (differential dataflow). Това гарантира, че промените се пропагират на ниво делта-разлики за части от милисекундата, без да се налага преизчисляване на цялата заявка при всяка промяна в базата данни [1].
За гарантиране на референциалната цялост на клиента, проектът се интегрира и с PGlite – олекотена версия на PostgreSQL, работеща изцяло в браузъра [1], [4]. Това дава възможност за локално изпълнение на релационни заявки и трансакции с пълната функционалност на Postgres.
Зареждане при поискване и Query-Driven Sync
Едно от най-важните нововъведения в TanStack DB е поддръжката на синхронизация, задвижвана от заявки (Query-Driven Sync) [1]. При големи бази данни е невъзможно и непрактично да се синхронизира цялата информация на клиента. Чрез активиране на режима syncMode: 'on-demand', клиентът изпраща филтриращи предикати (например специфично ID на проект) към синхронизиращия механизъм [1].
Външната граница на данните, които даден потребител е оторизиран да вижда, се дефинира като "форма" (shape) [1]. На базата на потребителските действия в интерфейса (например навигация или скролиране), TanStack DB динамично изисква и добавя съответните подмножества от данни. По този начин се реализират функции като безкрайно скролиране и пагинация, без това да натоварва мрежата или паметта на устройството. Този подход получава висока оценка от утвърдени разработчици в индустрията като Joe Savona от Meta [1].
В демо версията "LinearLarge", съдържаща над 500 000 реда в базата данни, приложението зарежда интерфейса моментално [1]. Това е възможно, тъй като първоначално се изтегля само малка част от данните, а останалите се дозареждат динамично при скролиране, запазвайки пълната реактивност и консистентност на локалния клиент.
Управление на конфликти и автономни системи
Когато разработваме синхронизиращи архитектури, въпросът с паралелните промени от различни потребители е неизбежен. ElectricSQL съзнателно разделя процеса на синхронизация при четене от този при запис [1]. По този начин разработчиците могат сами да определят бизнес логиката за запис обратно в сървъра. Базовото решение разчита на трансакции с потвърждение от сървъра и автоматично връщане на състоянието (rollback) при грешка [1].
За по-сложни случаи на съвместна работа в реално време, архитектурата позволява интеграция с конфликтно-свободни репликирани типове данни (CRDTs) и библиотеки като Yjs или Automerge [1]. Според Артур, по-голямата част от приложенията нямат нужда от сложни CRDT структури и стандартният сървърно-авторитарен модел с трансакционно отхвърляне на промените е напълно достатъчен за постигане на надеждност [1].
Този модел става критичен в контекста на новата ера на изкуствен интелект и ИИ агенти. Тъй като агентите извършват множество паралелни фонови операции на сървъра, традиционното периодично извличане на данни (polling) е неефективно. Синхронизиращият механизъм се явява единственият разумен начин за поддържане на консистентност и реално време на интерфейса, обединявайки действията на хората и автономните агенти в единна реактивна среда [1].
Източници:
[1]: Why Fetch When You Can Sync? Building Local-First Apps on a Sync Engine Architecture - InfoQ
[2]: ElectricSQL Official Website