ElectricSQL - jak PostgreSQL synchronizuje się z przeglądarką i dlaczego offline-first zmienia zasady gry

Handlowiec jest u klienta w magazynie. Zasięg telefonu: zero kresek. Musi sprawdzić stany magazynowe, złożyć zamówienie, potwierdzić cenę. W tradycyjnej aplikacji webowej widzi spinner, potem "Brak połączenia z serwerem". Mówi klientowi: "Wrócę do biura i wyślę mailem". Klient dzwoni do konkurencji.

W aplikacji z ElectricSQL handlowiec otwiera apkę. Dane są. Aktualne. Składa zamówienie. Zamyka telefon. Dwie godziny później, gdy wsiada do samochodu i łapie zasięg, zamówienie automatycznie synchronizuje się z serwerem. Magazyn dostaje powiadomienie. Faktura generuje się w tle. Klient dostaje potwierdzenie.

Żadnego spinnera. Żadnego "brak połączenia". Żadnej utraconej sprzedaży.

Problem: aplikacje webowe zakładają internet

Przez 25 lat budowaliśmy aplikacje webowe w tym samym modelu: przeglądarka wysyła żądanie → serwer odpowiada → przeglądarka wyświetla. Jeśli serwer nie odpowiada, aplikacja nie działa. Proste.

Ten model sprawdza się w biurze z łączem światłowodowym. Nie sprawdza się:

  • W terenie - handlowcy, serwisanci, geodeci, kontrolerzy jakości. Magazyny, hale produkcyjne, piwnice, wieś
  • W transporcie - kierowcy, kurierzy, konduktorzy. Tunele, strefy bez zasięgu, roaming
  • Przy niestabilnym internecie - polskie "LTE" z 2 sekundami latencji, przeciążone WiFi hotelowe, współdzielone łącze biurowe
  • Przy awariach serwera - deploy, restart, DDoS, awaria data center. Twój serwer jest offline 30 minut, ale 200 handlowców pracuje dalej

Tradycyjne rozwiązanie? PWA z service workerami i lokalnym cache'em. Problem: to Ty musisz zaprogramować synchronizację, rozwiązywanie konfliktów, kolejkowanie zmian offline, retry przy reconnect. Tygodnie pracy, setki edge case'ów, gwarancja bugów.

ElectricSQL mówi: nie rób tego sam.

Czym jest ElectricSQL

ElectricSQL to warstwa synchronizacji, która łączy PostgreSQL (serwer) z lokalną bazą danych w przeglądarce lub aplikacji mobilnej. Nie jest ORM-em, nie jest frameworkiem, nie jest bazą danych. Jest mostem, który sprawia, że dane z PostgreSQL są dostępne lokalnie - i synchronizują się automatycznie w obie strony.

Jak to działa

  1. Definiujesz "shapes" - podzbiory danych z PostgreSQL, które mają być dostępne na kliencie (np. "zamówienia tego handlowca z ostatnich 30 dni")
  2. ElectricSQL streamuje dane - klient otrzymuje początkowy snapshot, potem real-time aktualizacje przez HTTP streaming
  3. Klient pracuje lokalnie - odczyty i zapisy idą do lokalnej bazy (PGlite, SQLite, IndexedDB)
  4. Synchronizacja jest automatyczna - zmiany lokalne propagują się do PostgreSQL, zmiany serwerowe propagują się do klienta
  5. Offline jest transparentne - gdy nie ma sieci, klient pracuje na lokalnych danych. Gdy sieć wraca, synchronizacja odbywa się automatycznie
graph TD
    PG[("PostgreSQL
(serwer)")] ESQL["ElectricSQL
(sync layer)"] BROWSER["Przeglądarka
(PGlite)"] PHONE["Telefon
(SQLite)"] TABLET["Tablet
(SQLite)"] PG <-- "http
Replication" --> ESQL ESQL -- "HTTP Streaming" --> BROWSER & PHONE & TABLET

PostgreSQL jako źródło prawdy

To kluczowa decyzja architektoniczna ElectricSQL. Serwer nie ma własnej bazy danych - używa Twojego PostgreSQL. Tego samego, w którym masz zamówienia, klientów, produkty. ElectricSQL czyta WAL (Write-Ahead Log) PostgreSQL i streamuje zmiany do klientów.

To oznacza:

  • Zero migracji danych - nie przenosisz niczego do nowej bazy
  • Zero duplikacji - nie masz dwóch kopii danych do synchronizowania
  • Istniejące zapytania działają - Twoja aplikacja Phoenix dalej czyta z tego samego PostgreSQL
  • Istniejące narzędzia działają - pg_dump, pg_stat_statements, replikacja - wszystko bez zmian

Shapes - kontrola nad tym, co synchronizujesz

Nie synchronizujesz całej bazy danych na telefon handlowca. Synchronizujesz to, czego potrzebuje:

// Tylko zamówienia tego handlowca z ostatnich 30 dni
const shape = await electric.sync({
  table: 'zamowienia',
  where: `handlowiec_id = '${userId}' AND data >= now() - interval '30 days'`
})

// Produkty z cennika - tylko aktywne
const produkty = await electric.sync({
  table: 'produkty',
  where: `aktywny = true`
})

// Klienci przypisani do tego regionu
const klienci = await electric.sync({
  table: 'klienci',
  where: `region = '${userRegion}'`
})

Shapes to nie statyczne filtry. To dynamiczne widoki, które aktualizują się w czasie rzeczywistym. Gdy nowy produkt zostanie dodany do cennika w PostgreSQL, pojawi się na telefonie handlowca w sekundach - lub po odzyskaniu połączenia, jeśli handlowiec jest offline.

Dlaczego offline-first, a nie offline-tolerant

Jest fundamentalna różnica między "aplikacja jakoś działa offline" a "aplikacja jest zaprojektowana offline-first".

Offline-tolerant (tradycyjne podejście)

Aplikacja działa online. Gdy traci połączenie, pokazuje cached dane (zwykle read-only). Gdy użytkownik próbuje coś zmienić offline, dostaje komunikat błędu albo zmiany idą do "kolejki offline", której nikt nie testował, bo "kto pracuje offline?". Po odzyskaniu połączenia synchronizacja albo działa, albo nie. Konflikty? "Wygrała ostatnia zmiana" albo "plik konfliktu.xlsx".

Offline-first (ElectricSQL)

Aplikacja zawsze pracuje na lokalnych danych. Online czy offline - ten sam kod, ten sam interfejs, ta sama wydajność. Synchronizacja to warstwa transportowa, nie logika biznesowa. Aplikacja nie "wie", czy jest online. Po prostu czyta i pisze do lokalnej bazy.

Efekt dla użytkownika: aplikacja jest zawsze szybka. Nawet przy dobrym internecie dane nie lecą z serwera - lecą z lokalnej bazy. Latencja: zero. Nie 50ms, nie 200ms - zero, bo dane są na urządzeniu.

Scenariusze biznesowe

Handlowiec w terenie

Firma dystrybucyjna, 50 handlowców, 2000 klientów, 15 000 produktów.

Bez ElectricSQL: Handlowiec potrzebuje internetu do sprawdzenia ceny, stanu magazynowego, historii zamówień klienta. W magazynie klienta nie ma WiFi. Handlowiec spisuje zamówienie na kartce, wraca do biura, przepisuje do systemu. 3 godziny później klient dostaje potwierdzenie. A cennik, z którego korzystał, mógł się zmienić w międzyczasie.

Z ElectricSQL: Aplikacja na tablecie ma zsynchronizowany cennik, stany magazynowe, historię klienta. Handlowiec składa zamówienie offline. Zamówienie jest zapisane lokalnie z dokładnym timestampem i cenami z momentu złożenia. Gdy tablet łapie zasięg w samochodzie, zamówienie leci do PostgreSQL. Oban generuje fakturę. Klient dostaje potwierdzenie emailem. Handlowiec jest już u następnego klienta.

Kontrola jakości na produkcji

Zakład produkcyjny, hala bez WiFi (metal zakłóca sygnał), kontrolerzy z tabletami.

Bez ElectricSQL: Kontroler spisuje pomiary na kartce, idzie do biura, wpisuje do systemu. Jeśli pomiar wykazuje problem, reakcja jest opóźniona o godziny. Partia wadliwych produktów jedzie dalej na linię.

Z ElectricSQL: Kontroler wpisuje pomiary do tabletu. Dane są zapisane lokalnie. Algorytm walidacji działa offline - natychmiast sygnalizuje, że pomiar jest poza tolerancją. Kontroler zatrzymuje linię. Gdy tablet zsynchronizuje się z serwerem (np. przerwa obiadowa w kantynie z WiFi), dane trafiają do systemu jakości. Raport dzienny generuje się automatycznie.

Kelner z tabletem

Restauracja, 10 stolików, tablet dla każdego kelnera.

Bez ElectricSQL: WiFi w restauracji pada (goście streamują Netflix na telefonie). Kelnerzy nie mogą przyjmować zamówień. Wyciągają bloczki papierowe. Kuchnia dostaje zamówienia z opóźnieniem. Klienci czekają.

Z ElectricSQL: WiFi pada. Kelnerzy dalej przyjmują zamówienia na tabletach. Menu z cenami jest lokalne. Zamówienie zapisuje się do lokalnej bazy. Gdy WiFi wraca po 15 minutach, zamówienia synchronizują się z systemem kuchennym. Kuchnia dostaje je w kolejności złożenia z prawidłowymi timestampami.

Serwis techniczny

Firma serwisowa, technicy naprawiający urządzenia u klientów.

Bez ElectricSQL: Technik dzwoni do biura: "Jaki numer seryjny ma filtr do modelu X?", "Ile kosztuje wymiana sprężarki?", "Czy ten klient ma gwarancję?". Bez zasięgu - nie wie.

Z ElectricSQL: Baza części zamiennych, cenniki serwisowe, historia urządzeń klienta - wszystko offline na tablecie. Technik wycenia naprawę na miejscu, klient akceptuje, technik wprowadza protokół serwisowy. Synchronizacja w drodze do następnego klienta. Faktura generuje się automatycznie.

ElectricSQL + Phoenix LiveView

ElectricSQL i Phoenix LiveView to nie konkurencja - to komplementarne technologie.

LiveView zarządza interfejsem, gdy użytkownik jest online. Real-time aktualizacje, formularze, dashboardy - wszystko renderowane na serwerze.

ElectricSQL przejmuje, gdy użytkownik traci połączenie. Dane lokalne, zapis offline, synchronizacja po reconnect.

Razem tworzą aplikację, która jest:

  • Real-time gdy online - LiveView WebSocket, natychmiastowe aktualizacje
  • W pełni funkcjonalna gdy offline - ElectricSQL, lokalna baza
  • Automatycznie synchronizowana - bez kodu po stronie programisty

Architektura hybrydowa

Online:
  Użytkownik ←→ Phoenix LiveView ←→ PostgreSQL
                    (WebSocket)

Offline:
  Użytkownik ←→ Lokalna aplikacja ←→ PGlite/SQLite

                                    (po reconnect)

                               ElectricSQL ←→ PostgreSQL

                                           Oban (zadania)

                                     Email, PDF, notyfikacje

Dashboard zarządu działa na LiveView - jest online, na dużym ekranie, potrzebuje real-time danych z całej firmy. Aplikacja handlowca działa na ElectricSQL - jest mobilna, często offline, potrzebuje swoich danych lokalnie.

Ten sam PostgreSQL. Te same tabele. Dwa różne sposoby dostępu, zoptymalizowane pod różne scenariusze.

Rozwiązywanie konfliktów

Największe pytanie przy synchronizacji offline: co się stanie, gdy dwie osoby zmienią ten sam rekord offline?

Last-Write-Wins (domyślne)

Najprostsze podejście: ostatnia zmiana wygrywa. Timestamp decyduje. Dla wielu scenariuszy to wystarczy - kontroler aktualizuje pomiar, handlowiec zmienia adres klienta. Konflikty są rzadkie, a gdy wystąpią, ostatnia wartość jest zwykle prawidłowa.

Merge na poziomie kolumn

ElectricSQL może mergować zmiany na poziomie poszczególnych kolumn. Handlowiec A zmienia numer telefonu klienta, handlowiec B zmienia adres email - oba zmiany są zachowane, bo dotyczą różnych kolumn.

Custom resolution po stronie serwera

Dla scenariuszy, gdzie domyślna strategia nie wystarczy, logikę rozwiązywania konfliktów implementujesz w PostgreSQL:

-- Trigger na tabeli zamówień
-- Jeśli zamówienie zostało zmienione offline przez dwóch handlowców,
-- zachowaj to z wyższą wartością (pesymistycznie - nie tracimy przychodu)
CREATE OR REPLACE FUNCTION resolve_zamowienie_conflict()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.wartosc < OLD.wartosc THEN
        NEW.wartosc := OLD.wartosc;
        NEW.uwagi := OLD.uwagi || ' [konflikt: zachowano wyższą wartość]';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Logika jest w bazie danych, nie w kliencie. Jedno miejsce, jedna reguła, spójna dla wszystkich urządzeń.

Czym ElectricSQL różni się od alternatyw

vs Firebase / Firestore

Firebase to zamknięty ekosystem Google. Vendor lock-in na poziomie infrastruktury. Migracja z Firebase to przepisanie aplikacji od zera. ElectricSQL używa Twojego PostgreSQL - możesz w dowolnym momencie wyłączyć ElectricSQL i wrócić do tradycyjnego modelu. Dane nie zmieniają lokalizacji.

Firebase nie obsługuje SQL. Zapytania są ograniczone do modelu dokumentowego. Raporty, joiny, agregacje - musisz robić po stronie klienta albo przez Cloud Functions. ElectricSQL synchronizuje PostgreSQL - masz pełne SQL po stronie serwera.

vs CouchDB / PouchDB

CouchDB to baza dokumentowa z wbudowaną replikacją. Dojrzała technologia, sprawdzona w offline-first. Ale to osobna baza danych - nie PostgreSQL. Musisz synchronizować dane między CouchDB a swoim głównym storage'em. ElectricSQL eliminuje ten problem - Twoje dane są w PostgreSQL, zawsze.

vs Realm (MongoDB)

Realm to SDK MongoDB do synchronizacji mobilnej. Vendor lock-in w ekosystem MongoDB. Wymaga MongoDB Atlas (chmura MongoDB). ElectricSQL działa z dowolnym PostgreSQL - self-hosted, AWS RDS, Supabase, Neon, czy serwer pod biurkiem.

vs Własna synchronizacja

Możesz zbudować synchronizację sam. Service workery, IndexedDB, kolejka zmian, retry, conflict resolution. Zespoły, które próbowały, raportują 3-6 miesięcy pracy na samą synchronizację. I potem miesiące bugfixów. ElectricSQL daje to z pudełka - przetestowane, zoptymalizowane, utrzymywane przez dedykowany zespół.

Kiedy ElectricSQL nie jest odpowiedzią

Uczciwie - nie każda aplikacja potrzebuje offline-first:

Dashboardy analityczne - dane muszą być aktualne, nie ma sensu pracować na wczorajszych danych. LiveView bez ElectricSQL jest lepszym wyborem.

Systemy transakcyjne w czasie rzeczywistym - giełda, aukcje, rezerwacje lotów. Stany muszą być spójne globalnie w każdym momencie. Offline to nie opcja.

Aplikacje z ogromnymi zbiorami danych - nie zsynchronizujesz 10 milionów produktów na tablet. Shapes pomagają (synchronizujesz podzbiór), ale są granice tego, co mieści się na urządzeniu mobilnym.

Systemy, gdzie użytkownicy są zawsze online - biuro z redundantnym łączem, aplikacja wewnętrzna na desktopach. Dodatkowa złożoność offline-first nie jest uzasadniona.

Wpływ na biznes

Policzalny zysk

Firma dystrybucyjna z 50 handlowcami. Każdy handlowiec traci średnio 30 minut dziennie na problemy z siecią (oczekiwanie na ładowanie, spisywanie na kartce, ponowne wpisywanie).

50 handlowców × 30 minut × 220 dni roboczych = 5 500 godzin rocznie. Przy koszcie godziny handlowca 80 PLN = 440 000 PLN rocznie stracone na oczekiwanie na internet.

ElectricSQL eliminuje 100% tego problemu. Nie 50%, nie "zmniejsza" - eliminuje. Aplikacja działa offline tak samo jak online. Zero oczekiwania.

Niepoliczalny zysk

  • Ile zamówień straciliście, bo handlowiec nie miał dostępu do cennika u klienta?
  • Ile błędów powstało przez przepisywanie z kartki do systemu?
  • Ile czasu klienci czekali na potwierdzenie, bo handlowiec musiał wrócić do biura?
  • Jak wpływa na morale zespołu sytuacja, gdy narzędzie pracy zawodzi w krytycznym momencie?

Przewaga konkurencyjna

Twoja konkurencja każe handlowcom wracać do biura. Twoi handlowcy składają zamówienia przy kliencie, z cenami i stanami w ręku. Klient dostaje potwierdzenie, zanim handlowiec wyjdzie z magazynu. To jest realna, odczuwalna różnica w jakości obsługi.

Stack kompletny

ElectricSQL zamyka ostatnią lukę w naszym stacku technologicznym:

WarstwaTechnologiaRola
Logika biznesowaElixir na BEAMWspółbieżność, niezawodność, real-time
Interfejs onlinePhoenix LiveViewDashboardy, panele admin, real-time UI
Interfejs offlineElectricSQLAplikacje mobilne, praca w terenie
Baza danychPostgreSQLŹródło prawdy, kolejki (Oban), cache
Wydajność krytycznaRust przez RustlerKryptografia, parsowanie, obliczenia
Zadania w tleObanEmaile, PDF-y, synchronizacje, cron

Sześć technologii, które razem pokrywają każdy scenariusz: od dashboardu zarządu w biurze po tablet handlowca w piwnicy magazynu bez zasięgu. Wszystko zsynchronizowane, wszystko spójne, wszystko na jednym PostgreSQL.

Masz zespół pracujący w terenie i potrzebujesz aplikacji, która działa bez internetu? Porozmawiajmy - pokażemy prototyp offline-first na Twoich danych.