Nihai Tutarlılık: Stratejiler, Takaslar ve Kullanıcı Deneyimi Modelleri
Bir kullanıcı profilini günceller, "Kaydet"i tıklar ve eski adını görmeye devam eder. Sayfayı yeniden yükleyin: şimdi yeni adı görüyorsunuz. Ortada: olaya dayalı bir sistem duruyordu güncellemenin hizmetler arasında yayılması. Bunihai tutarlılık eylem halinde - ve yanlış cevap, kullanıcının sistemin sahip olduğunu düşündüğü cevaptır bir hata. Mimarın doğru cevabı sistemi öyle tasarlamaktır ki bu tutarsızlık penceresi görünmez veya kabul edilebilirdir.
Olası tutarlılık saklanacak bir uzlaşma değil: mimari bir seçimdir gerçek faydaların (kullanılabilirlik, ölçeklenebilirlik, dayanıklılık) ve gerçek zorlukların farkında (karmaşıklık, test etme, kullanıcı deneyimi). Yönetime yönelik tutarlılık modellerini ve stratejilerini anlayın Çalışan dağıtılmış sistemler oluşturmak için geçici tutarsızlıklar önemlidir gerçekten üretimde.
Ne Öğreneceksiniz
- CAP Teoremi ve BAZ ve ASİT: teorik çerçeve
- Tutarlılık kalıpları: güçlüden nihaiye
- Read-Your-Writes: eski verileri yazma işleminden hemen sonra göstermeme stratejisi
- İyimser kullanıcı arayüzü: Sunucu yanıt vermeden önce arayüzü günceller
- Eş zamanlı olarak değiştirilen veriler için çakışma tespiti ve mutabakat
- Farklılıkları tespit etmek için vektör saatleriyle versiyonlama
- Kullanıcının kafasını karıştırmadan olası tutarlılık nasıl aktarılır?
CAP Teoremi ve BASE: Teorik Çerçeve
Il CAP Teoremi bölümleri olan dağıtılmış bir sistemde olduğunu belirtir ağ (üretimde P kaçınılmaz), ikisinden birine sahip olabilirsiniz Cısrarcılık (tüm düğümler aynı verileri görür) o Akullanılabilirlik (sistem her zaman yanıt verir), ancak bölümleme sırasında her ikisi de aynı anda yanıt vermez.
| Mülk | ASİT (ilişkisel DB) | BASIC (dağıtılmış sistemler) |
|---|---|---|
| Tutarlılık | Güçlü: Her okunan son yazılanı görür | Nihai: Düğümler zamanla birleşir |
| Kullanılabilirlik | Bölümleme sırasında garanti edilmez | Yüksek: Sistem her zaman yanıt verir |
| Kalmak | Atomik: ya hep ya hiç | Yumuşak durum: Durum herhangi bir giriş yapılmadan değişebilir |
| Örnekler | PostgreSQL, MySQL, Oracle | DynamoDB, Cassandra, CouchDB |
Tutarlılık Modelleri: Spektrum
Güçlü tutarlılık ile nihai tutarlılık arasında birçok ara model vardır. Bunları bilmek, her kullanım durumu için doğru tercihi seçmenizi sağlar:
// Spettro dei modelli di consistenza (dal piu forte al piu debole)
// 1. STRONG (Linearizable) Consistency
// Ogni operazione sembra atomica e globalmente ordinata
// Esempio: PostgreSQL con una singola istanza
// Trade-off: latenza alta, disponibilita ridotta
// 2. SEQUENTIAL Consistency
// Le operazioni appaiono nell'ordine in cui vengono eseguite
// ma non necessariamente in tempo reale
// Esempio: spanner.google.com (TrueTime)
// 3. CAUSAL Consistency
// Le operazioni causalmente correlate sono viste nell'ordine corretto
// Le operazioni non correlate possono essere viste in ordine diverso
// Esempio: MongoDB con causally consistent sessions
// 4. READ-YOUR-WRITES (Session Consistency)
// Un client vede sempre le sue ultime scritture
// Altri client potrebbero vedere dati vecchi
// Esempio: DynamoDB con sticky sessions
// 5. MONOTONIC READ Consistency
// Una volta letto un valore, non vedrai mai un valore piu vecchio
// Non garantisce di vedere le proprie ultime scritture
// 6. EVENTUAL Consistency
// I nodi convergono allo stesso stato "eventualmente"
// Nessuna garanzia su quando o nell'ordine delle operazioni
// Esempio: DNS, DynamoDB default, AWS S3
Yazdıklarını Oku: Eski Verileri Yazdıktan Hemen Sonra Gösterme
Üretimde nihai tutarlılıkla ilgili en sık karşılaşılan sorun: kullanıcı bir şeyi günceller, ayrıntı sayfasına yönlendirilir ve tekrar görür kopya henüz değişikliği yaymadığı için eski değerdir. Yazdıklarınızı Okuyun tutarlılığı müşterinin görmesini sağlar her zaman en son yazılarını.
// Strategia 1: Sticky routing — leggi sempre dallo stesso nodo
// Il client viene indirizzato sempre alla replica primaria per
// un periodo dopo la scrittura (es. 1-5 secondi)
// Implementazione con un token di versione
interface WriteResult {
entityId: string;
version: number; // numero di versione dopo la scrittura
timestamp: number; // timestamp della scrittura
}
// Il client salva il WriteResult e lo invia nelle successive letture
interface ReadRequest {
entityId: string;
minVersion?: number; // "voglio almeno questa versione"
}
// Il server: attendi che la replica raggiunga la versione richiesta
class ConsistentReadService {
async readWithVersion(
entityId: string,
minVersion?: number,
timeoutMs = 5000
): Promise<Entity> {
const startTime = Date.now();
while (true) {
const entity = await this.replica.findById(entityId);
if (!minVersion || entity.version >= minVersion) {
return entity;
}
if (Date.now() - startTime > timeoutMs) {
// Timeout: leggi dal primario come fallback
return await this.primary.findById(entityId);
}
await this.sleep(50); // Breve attesa prima del retry
}
}
private sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
// Strategia 2: Redirect al primario per N secondi dopo scrittura
// Il CDN/LB indirizza le letture del client alla primary replica
// per 2-3 secondi dopo una scrittura
// Strategia 3: Cache invalidation
// Dopo la scrittura, invalida la cache del client
// La prossima lettura va direttamente alla primary
İyimser Kullanıcı Arayüzü: Önce Güncelleyin, Daha Sonra Uzlaştırın
L'İyimser kullanıcı arayüzü ve sistemler için temel UX modeli nihai tutarlılık için: arayüzü hemen güncelleyin (varsayarak) yazmanın başarılı olacağını), ardından sunucuyla uzlaşır. yapar sistem, yüksek ağ gecikmesinde bile anlık olarak algılanır.
// Optimistic UI con React e reconciliazione
import { useState, useOptimistic } from 'react';
interface Todo {
id: string;
text: string;
completed: boolean;
synced: boolean; // false = in attesa di conferma server
}
function TodoList({ initialTodos }: { initialTodos: Todo[] }) {
const [todos, setTodos] = useState(initialTodos);
// useOptimistic: hook React 19 per Optimistic UI
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state: Todo[], newTodo: Todo) => [...state, newTodo]
);
async function handleToggle(todoId: string): Promise<void> {
const todo = todos.find((t) => t.id === todoId);
if (!todo) return;
const optimisticUpdate = { ...todo, completed: !todo.completed, synced: false };
// 1. Aggiorna UI immediatamente (ottimistico)
addOptimisticTodo(optimisticUpdate);
try {
// 2. Invia al server
const confirmed = await api.toggleTodo(todoId);
// 3. Sostituisci con il dato confermato dal server
setTodos((prev) =>
prev.map((t) => (t.id === todoId ? { ...confirmed, synced: true } : t))
);
} catch (error) {
// 4. ROLLBACK: ripristina lo stato originale se la scrittura fallisce
setTodos((prev) => prev.map((t) => (t.id === todoId ? todo : t)));
// Mostra feedback all'utente
showErrorToast('Impossibile aggiornare il task. Riprova.');
}
}
return (
<ul>
{optimisticTodos.map((todo) => (
<li key={todo.id} className={todo.synced ? '' : 'pending'}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => handleToggle(todo.id)}
/>
{todo.text}
{!todo.synced && <span className="sync-indicator">Salvataggio...</span>}
</li>
))}
</ul>
);
}
Vektör Saatleri ile Çakışma Tespiti
Sistemlerde iki kullanıcı aynı verileri aynı anda değiştirdiğinde çoklu çoğaltma ile bir çakışma yaratılır. THE Vektör Saatler Farklılıkları otomatik olarak tespit etmenize ve ne yapacağınıza karar vermenize olanak tanır otomatik birleştirme veya çakışmayı kullanıcıya sunma.
// Vector Clock: implementazione base in TypeScript
type VectorClock = Record<string, number>;
function increment(clock: VectorClock, nodeId: string): VectorClock {
return { ...clock, [nodeId]: (clock[nodeId] ?? 0) + 1 };
}
function merge(a: VectorClock, b: VectorClock): VectorClock {
const result: VectorClock = { ...a };
for (const [node, time] of Object.entries(b)) {
result[node] = Math.max(result[node] ?? 0, time);
}
return result;
}
type CompareResult = 'before' | 'after' | 'concurrent' | 'equal';
function compare(a: VectorClock, b: VectorClock): CompareResult {
const allNodes = new Set([...Object.keys(a), ...Object.keys(b)]);
let aGreater = false;
let bGreater = false;
for (const node of allNodes) {
const aTime = a[node] ?? 0;
const bTime = b[node] ?? 0;
if (aTime > bTime) aGreater = true;
if (bTime > aTime) bGreater = true;
}
if (aGreater && bGreater) return 'concurrent'; // conflitto
if (aGreater) return 'after';
if (bGreater) return 'before';
return 'equal';
}
// Utilizzo per rilevare conflitti di scrittura concorrente
interface Document {
id: string;
content: string;
vectorClock: VectorClock;
lastModifiedBy: string;
}
async function updateDocument(
docId: string,
newContent: string,
clientClock: VectorClock,
nodeId: string
): Promise<{ success: boolean; conflict?: { local: Document; remote: Document } }> {
const current = await db.findById(docId);
const comparison = compare(clientClock, current.vectorClock);
if (comparison === 'concurrent') {
// Conflitto! Entrambe le versioni hanno avanzato indipendentemente
return {
success: false,
conflict: {
local: { ...current, content: newContent, vectorClock: clientClock },
remote: current,
},
};
}
if (comparison === 'before') {
// Il client ha una versione stale: rifiuta e richiedi re-fetch
throw new Error('Stale write: fetch the latest version first');
}
// OK: scrivi con clock aggiornato
const newClock = increment(merge(clientClock, current.vectorClock), nodeId);
await db.update(docId, newContent, newClock, nodeId);
return { success: true };
}
Uzlaşma Stratejileri
Bir çatışma tespit ettiğinizde kullanabileceğiniz çeşitli uzlaşma stratejileri vardır. her biri farklı veri türleri için uygundur:
// Strategie di reconciliazione dei conflitti
// 1. Last-Write-Wins (LWW)
// Il timestamp piu recente vince. Semplice ma perde dati.
// Usato da: AWS DynamoDB (default), Apache Cassandra
function resolveWithLWW(a: Document, b: Document): Document {
return a.updatedAt > b.updatedAt ? a : b;
}
// 2. Merge automatico per strutture dati CRDT-friendly
// Counter: somma i delta (non i valori assoluti)
interface Counter {
nodeIncrements: Record<string, number>; // per ogni nodo: totale incrementi
}
function mergeCounters(a: Counter, b: Counter): Counter {
const result: Record<string, number> = { ...a.nodeIncrements };
for (const [node, count] of Object.entries(b.nodeIncrements)) {
result[node] = Math.max(result[node] ?? 0, count);
}
return { nodeIncrements: result };
}
function getCounterValue(counter: Counter): number {
return Object.values(counter.nodeIncrements).reduce((sum, v) => sum + v, 0);
}
// 3. Three-way merge (come Git)
// Confronta le due versioni divergenti con il loro antenato comune
function threeWayMerge(
ancestor: string,
versionA: string,
versionB: string
): { merged: string; hasConflicts: boolean } {
// Usa diff3 o similar per testi
// Per strutture dati: merge field-by-field
// Se un campo e stato modificato in A ma non in B: accetta la modifica di A
// Se un campo e stato modificato in entrambi: conflitto da risolvere manualmente
// Implementazione completa dipende dal tipo di dato
return { merged: versionA, hasConflicts: true }; // placeholder
}
// 4. User-driven conflict resolution
// Presenta entrambe le versioni all'utente e lascia scegliere
async function presentConflictToUser(
conflict: { local: Document; remote: Document }
): Promise<Document> {
const resolution = await showConflictDialog({
localVersion: conflict.local,
remoteVersion: conflict.remote,
message: 'Il documento e stato modificato da un altro utente. Quale versione vuoi mantenere?',
});
return resolution.chosen === 'local' ? conflict.local : conflict.remote;
}
Nihai Tutarlılık için UX Modelleri
Nihai tutarlılığın en hafife alınan kısmı kullanıcıyla iletişimdir. Kullanıcılar tutarlılık modellerini anlamıyorlar (anlamamaları da gerekiyor) ancak algılıyorlar bir şey bekledikleri gibi gitmediğinde hemen.
Önerilen UX Kalıpları
- Eski Göstergeler: gecikmeyi gizlemek yerine "2 dakika önce güncellendi" ifadesini gösterir. Kullanıcılar verilerin en güncel olmayabileceğinin farkındadır.
- Bekleyen Durum Görsel: Bir işlemin devam ettiğini göstermek için döndürücüleri, gri göstergeleri veya "Kaydediliyor..." metnini kullanın.
- Başarı Onayı: yalnızca sunucu onayından sonra bir onay mesajı/başlığı gösterir, kritik işlemler için pek iyimser değildir.
- Otomatik Yeniden Deneme: kritik olmayan işlemler için sessiz yeniden deneme yapın. Hatayı yalnızca tüm yeniden denemeler başarısız olursa göster.
- Kalıcı Silme'den önce Geçici Silme: öğeyi hemen silinmiş olarak gösterin, ancak gerçek silme işlemini yalnızca sunucu onayından sonra gerçekleştirin.
// Pattern: Stale-While-Revalidate per dati non critici
async function useStaleWhileRevalidate<T>(
key: string,
fetcher: () => Promise<T>
): Promise<{ data: T; isStale: boolean }> {
// 1. Servi immediatamente i dati in cache (potenzialmente stale)
const cached = await cache.get<{ data: T; timestamp: number }>(key);
if (cached) {
const isStale = Date.now() - cached.timestamp > STALE_THRESHOLD_MS;
if (isStale) {
// 2. Avvia revalidation in background (non bloccante)
fetcher()
.then((freshData) => {
cache.set(key, { data: freshData, timestamp: Date.now() });
})
.catch(console.error);
}
return { data: cached.data, isStale };
}
// 3. Nessuna cache: fetch bloccante
const freshData = await fetcher();
await cache.set(key, { data: freshData, timestamp: Date.now() });
return { data: freshData, isStale: false };
}
// Utilizzo nel frontend:
// const { data: userProfile, isStale } = await useStaleWhileRevalidate(
// `profile:${userId}`,
// () => api.getUserProfile(userId)
// );
// if (isStale) showBanner('Dati potenzialmente non aggiornati');
Dikkate Alınması Gereken Takas
Tüm veriler nihai tutarlılığa tolerans göstermez. Ürün fiyatları, satışlar cari hesapların durumu, uçak biletlerinin mevcudiyeti: bu işlemler gerektirir güçlü tutarlılık. Nihai tutarlılık sosyal yayınlar ve sayaçlar için uygundur beğeniler, analiz verileri, kullanıcı profilleri, kritik olmayan envanterler. Tanımla açıkça hangi verinin mimarinizde hangi düzeyde tutarlılık gerektirdiğini belirler.
Sonuçlar: Bilinçli Bir Seçim Olarak Nihai Tutarlılık
Nihai tutarlılık dağıtılmış sistemlerin zayıflığı değildir: bu bir seçimdir ACID sistemlerinin ölçeklenebilirliğini, kullanılabilirliğini ve esnekliğini sağlayan kasıtlı teklif edemezler. Önemli olan sistemi tutarsızlıkları ortadan kaldıracak şekilde tasarlamaktır. geçici kullanıcı tarafından görülemez (İyimser Kullanıcı Arayüzü, Yazdıklarını Oku) veya kaçınılmaz olduğunda açıkça iletilir.
Bu makale Olay Odaklı Mimari serisini sonlandırıyor. Artık bir anlayışa sahipsiniz Güvenilir dağıtılmış sistemler oluşturmak için araçlar ve kalıplarla tamamlayın: etki alanı olaylarından Giden Kutusu Desenine, DLQ'dan tüketici bağımsızlığına, üretimdeki her türlü tutarlılığı yönetme stratejilerine kadar.
Tüm Serinin Olay Odaklı Mimarisi
- EDA Temelleri: Etki Alanı Olayları, Komutlar ve Mesaj Veriyolu
- Olay Kaynak Kullanımı: Değişmez Bir Olaylar Dizisi Olarak Durum
- CQRS: Ayrı Okuma ve Yazma
- Saga Modeli: Dağıtılmış İşlemler
- AWS EventBridge: Sunucusuz Olay Veri Yolu
- Teslim Edilmeyen Mektup Sırası ve Esneklik
- Tüketicilerde İdempotans
- CDC ile Giden Kutusu Deseni







