Zbudowałem taki system dla aplikacji około 8 lat temu i mogę podzielić się kilkoma sposobami, które ewoluowały wraz z rozwojem aplikacji.
Zacząłem od zalogowania każdej zmiany (wstawienia, aktualizacji lub usunięcia) z dowolnego urządzenia do tabeli „historii”. Jeśli na przykład ktoś zmieni swój numer telefonu w tabeli „kontakt”, system dokona edycji pola contact.phone, a także doda rekord historii z działaniem = aktualizacja, pole = telefon, rekord = [identyfikator kontaktu], wartość = [nowy numer telefonu]. Następnie, gdy urządzenie synchronizuje, pobiera elementy historii od ostatniej synchronizacji i stosuje je do swojej lokalnej bazy danych. Brzmi to jak opisany powyżej wzorzec „replikacji transakcji”.
Jednym z problemów jest unikanie identyfikatorów, gdy elementy można tworzyć na różnych urządzeniach. Kiedy zaczynałem, nie wiedziałem o identyfikatorach UUID, więc użyłem automatycznej inkrementacji identyfikatorów i napisałem skomplikowany kod, który działa na centralnym serwerze, aby sprawdzić nowe identyfikatory przesłane z urządzeń, zmienić je na unikalne identyfikatory, jeśli wystąpi konflikt, i powiedz urządzeniu źródłowemu, aby zmieniło identyfikator w lokalnej bazie danych. Po prostu zmiana identyfikatorów nowych rekordów nie była taka zła, ale jeśli utworzę na przykład nowy element w tabeli kontaktów, a następnie utworzę nowy powiązany element w tabeli zdarzeń, teraz mam klucze obce, które muszę również sprawdź i zaktualizuj.
W końcu dowiedziałem się, że UUID może tego uniknąć, ale do tego czasu moja baza danych robiła się dość duża i bałam się, że pełna implementacja UUID może spowodować problem z wydajnością. Zamiast korzystać z pełnych identyfikatorów UUID, zacząłem używać losowo generowanych 8-znakowych klawiszy alfanumerycznych jako identyfikatorów i pozostawiłem istniejący kod na miejscu, aby poradzić sobie z konfliktami. Gdzieś pomiędzy moimi obecnymi 8-znakowymi kluczami a 36 znakami UUID musi być słaby punkt, który wyeliminowałby konflikty bez niepotrzebnego wzdęcia, ale ponieważ mam już kod rozwiązywania konfliktów, eksperymentowanie z tym nie było priorytetem .
Kolejnym problemem było to, że tabela historii była około 10 razy większa niż cała reszta bazy danych. To sprawia, że przechowywanie jest drogie, a wszelkie czynności konserwacyjne w tabeli historii mogą być bolesne. Zachowanie całego stołu pozwala użytkownikom wycofać wszelkie poprzednie zmiany, ale zaczęło to wydawać się przesadą. Dodałem więc procedurę do procesu synchronizacji, w której jeśli element historii ostatnio pobrany przez urządzenie już nie istnieje w tabeli historii, serwer nie podaje mu ostatnich elementów historii, ale zamiast tego przekazuje mu plik zawierający wszystkie dane To konto. Następnie dodałem cronjob, aby usunąć elementy historii starsze niż 90 dni. Oznacza to, że użytkownicy mogą nadal wycofywać zmiany starsze niż 90 dni, a jeśli zsynchronizują co najmniej raz na 90 dni, aktualizacje będą przyrostowe, jak poprzednio. Ale jeśli czekają dłużej niż 90 dni,
Ta zmiana zmniejszyła rozmiar tabeli historii o prawie 90%, więc teraz utrzymanie tabeli historii sprawia, że baza danych jest tylko dwa razy większa niż dziesięć razy większa. Kolejną zaletą tego systemu jest to, że synchronizacja może nadal działać bez tabeli historii, jeśli zajdzie taka potrzeba - na przykład gdybym musiał przeprowadzić pewne czynności konserwacyjne, które tymczasowo przełączyły ją w tryb offline. Lub mógłbym zaoferować różne okresy wycofania dla kont w różnych punktach cenowych. A jeśli do pobrania potrzeba więcej niż 90 dni, cały plik jest zwykle bardziej wydajny niż format przyrostowy.
Gdybym dzisiaj zaczynał od nowa, pomijałbym sprawdzanie konfliktu identyfikatorów i dążyłbym do uzyskania klucza o długości wystarczającej do wyeliminowania konfliktów, z pewnym rodzajem sprawdzania błędów na wszelki wypadek. Ale tabela historii i kombinacja przyrostowych pobrań dla ostatnich aktualizacji lub pełnego pobierania w razie potrzeby działa dobrze.