← cd ../blog

Sincronizare 3-way: Cum evităm conflictele în Septica

Sincronizare 3-way: Cum evităm conflictele în Septica — imagine de copertă

Atunci când dezvoltăm o aplicație de tip joc, cum este proiectul meu Septica, sincronizarea datelor între dispozitivul local și cloud devine rapid o provocare. Dacă nu ești atent, riști să suprascrii progresul utilizatorului sau să pierzi starea sesiunilor. Recent, am implementat un algoritm de three-way merge care rezolvă aceste probleme fără dureri de cap.

Strategia de bază: Izolarea datelor

Prima regulă pe care am stabilit-o în arhitectura datelor pentru Septica este separarea logică a rândurilor. Nu am vrut un singur obiect JSON gigantic, deoarece asta duce la conflicte inutile. Dacă un utilizator joacă o rundă de Cruce pe telefon și una de Sedma pe tabletă, datele nu trebuie să se bată cap în cap.

Am structurat stocarea astfel încât fiecare tip de joc să aibă propriul său spațiu în baza de date, folosind o listă de chei permise (whitelist) pentru sincronizare.

const SYNC_WHITELIST = [
  'stats.cruce',
  'stats.sedma',
  'settings.theme',
  'user.profile'
];

Merge în trei căi (3-Way Merge)

Pentru a evita suprascrierile, folosim un snapshot al stării anterioare (base), starea locală și starea din cloud. Logica este simplă: comparăm ce s-a schimbat în cloud față de bază și aplicăm doar acele modificări. Dacă ambele părți au modificat aceeași cheie, folosim updatedAt pentru a decide câștigătorul, iar în caz de egalitate, cloud-ul are prioritate.

Iată cum arată implementarea simplificată a funcției de merge:

function mergeStates(local, cloud, base) {
  const result = { ...local };

  SYNC_WHITELIST.forEach(key => {
    if (cloud[key] !== base[key]) {
      // S-a schimbat ceva în cloud
      if (local[key] === base[key]) {
        // Local nu a fost atins, adoptăm cloud
        result[key] = cloud[key];
      } else {
        // Conflict: ambele au modificat, verificăm timestamp-ul
        if (cloud[key].updatedAt >= local[key].updatedAt) {
          result[key] = cloud[key];
        }
      }
    }
  });

  return result;
}

Modul Oaspete: Un sanctuar local

Un aspect crucial al UX-ului în Septica este modul oaspete (guest mode). Am decis că acest mod trebuie să rămână strict local. Nu trimitem nimic către cloud, nu primim nimic. Această izolare este implementată printr-un flag simplu în managerul de sincronizare:

function shouldSync(userType) {
  if (userType === 'GUEST') return false;
  return true;
}

De ce funcționează această abordare?

Prin utilizarea unui whitelist și a unui snapshot de referință, am eliminat complet riscul de a șterge progresul unui utilizator dintr-un joc doar pentru că a deschis altul. În plus, prioritatea acordată cloud-ului în cazul egalităților de tip updatedAt ne oferă o sursă unică de adevăr previzibilă.

Sincronizarea nu este un proces de „înlocuire”, ci unul de „integrare”. Dacă lucrezi la un proiect similar, îți recomand să nu te grăbești să faci un overwrite complet. Începe cu un sistem de diffing per-cheie și vei vedea cum frustrarea utilizatorilor scade considerabil.

Ai implementat vreodată un algoritm de merge custom pentru jocurile tale? Sunt curios să aud ce strategii ai folosit pentru a gestiona conflictele de date!