İçeriğe geç

Replikasyon ve Okuma Replikaları — Yazdım Ama Göremiyorum

İleri 9 dk Çok sık karşılaşılır

Önce şunu oku: MVCC ve Kilitler — Eski Satır Sürümleri Nerede Yaşar?

30 saniyede özet

Veritabanının kopyaları (replikalar) okuma yükünü paylaşır, ama asıl veritabanının biraz gerisinden gelir. Kullanıcı kendi yaptığı değişikliği göremeyebilir; asıl makine çökerse son yazılanlar kaybolabilir.

Kullanıcı profil adını değiştirdi, “kaydedildi” mesajını gördü ve sayfayı yeniledi: eski ad duruyordu. Tekrar kaydetti, yine eski ad. Destek kaydı açtığında ad çoktan düzelmişti.

Merkez güncellendi; şubeye haber biraz sonra ulaşıyor.
Adım adım oku
  1. Kullanıcının yeni adı yazma sunucusuna, yani primary'ye, merkeze gider.
  2. Replika değişikliği primary'nin günlüğünden biraz sonra alır; kurye yoldadır.
  3. Bu arada replikadan okuyan kullanıcı eski adı görür, üstelik kaydetme başarılı dönmüştür.
  4. Kullanıcı kendi yazdığını bir süre primary'den okursa her zaman güncel hâli görür.
  1. Bayt: Profil adımı değiştirdim, kaydedildi dedi. Sayfayı yeniledim: eski ad!

  2. Sen: Tekrar kaydetsen?

  3. Bayt: Kaydettim, yine eski ad. Destek kaydı açtığımda ise ad çoktan düzelmişti.

  4. Bayt: Merkez ile şubeler aynı anda mı güncellenir?

Biri yazar, diğerleri kopyalar

Yazmalar tek bir sunucuya, primary’ye gider. Replikalar primary’nin değişiklik günlüğünü alıp aynı değişiklikleri kendilerine uygular. Okumaları replikalara dağıtmak, primary’nin yükünü azaltır.

PostgreSQL’de streaming replication, SQL Server’da Always On availability groups, Oracle’da Data Guard bu işi yapar. Üçünde de varsayılan ya da en yaygın kurulum asenkrondur.

Kafam karıştı, daha basit anlat

Tek bir kişi deftere yazar, diğerleri onun yazdıklarını kendi defterlerine kopyalar. Okumak isteyenler kopyalara gider, böylece yazan kişi rahat çalışır.

Hızlı kontrolOrta

Okuma replikası eklemenin temel amacı nedir ve neyi çözmez?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Geriden gelmenin iki bedeli

Asenkron replikasyon. Kullanıcı 'kaydedildi' cevabını aldı ve primary hemen ardından çöktü. Replika yeni primary oldu. Kullanıcının değişikliği nerede? Cevabı göster

Kaybolmuş olabilir. Asenkron commit replikayı beklemez. Değişiklik replikaya gönderilmeden primary çöktüyse, yeni primary onu hiç görmemiştir.

Replikanın primary’nin gerisinde kaldığı süreye replication lagBir replikanın primary'deki değişiklikleri ne kadar geriden uyguladığı. Bu süre boyunca replikadan yapılan okumalar eski veriyi gösterir.Sözlükte gör → denir. Bu süre boyunca replika eski veriyi gösterir; lag büyürken primary çökerse aradaki yazmalar failoverPrimary çöktüğünde bir replikanın yeni primary olarak devralması. Asenkron replikasyonda replikaya ulaşmamış yazmalar bu sırada kaybolabilir.Sözlükte gör →’da kaybolur.

Senkron replikasyonun da iki derecesi var. “Replika diske yazsın” kaybı önler ama okumayı tazelemez. “Replika uygulasın” (PostgreSQL’de remote_apply) ikisini de sağlar, ama her commit en çok burada bekler.

Kafam karıştı, daha basit anlat

Kopyalar biraz geriden gelir. O arada kopyaya bakan kişi eski bilgiyi görür. Asıl defter tam o sırada kaybolursa, kopyalanmamış satırlar da kaybolur.

Hızlı kontrolİleri

Asenkron replikasyonda primary çöktü ve replika devraldı. Kullanıcılara 'kaydedildi' denmiş son birkaç sipariş yok. Neden?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

PostgreSQL'de `synchronous_commit = on` ve senkron bir replika var. Yine de replikadan okuyan kullanıcı az önce commit edilen değişikliği göremiyor. Neden?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Yazdım ama göremiyorum

Tohum 488310

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

Kullanıcı adını değiştirip sayfayı yeniliyor

Yazma primary'ye gidiyor. Okuma nereden yapılacak ve replika o ana kadar yetişmiş olacak mı?

Görevler0/3

  • Kullanıcının az önce kaydettiğini görmemesini sağlaaçık

    İpucu

    Okumayı gecikmeli replikaya gönder.

  • Kullanıcıya "kaydedildi" denmiş bir değişikliği failover'da kaybetaçık

    İpucu

    Commit replikayı beklemesin; replika geride olsun.

  • Replika gecikmeliyken bile replikadan taze oku ve çökmede kayıp yaşamaaçık

    İpucu

    Replikanın değişikliği görünür yapmasını bekle.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Okuma replikadan: kullanıcı eski adı görüyor.
  2. “Primary çöksün”ü aç. Onaylanmış yazma kayboldu.
  3. Replikasyonu “diske yazsın” yap. Kayıp yok, ama okuma hâlâ eski.
  4. “Uygulasın” yap. Taze ve güvenli; commit bekledi.
  5. Asenkrona dön, okumayı “yazan primary’den okusun” yap. Okuma taze, ama çökme kaybı duruyor.
Hızlı kontrolİleri

Simülatörde asenkron replikasyon ve 'yazan kullanıcı primary'den okusun' seçiliyken okuma taze çıktı, ama çökme açılınca yazma yine kayboldu. Buradan çıkan ders ne?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendi yazdığını okumak

Her okuma taze olmak zorunda değil; ama kullanıcının kendi değişikliğini görmesi gereken okumalar vardır. Buna read-your-writesBir kullanıcının kendi yaptığı değişikliği sonraki okumalarında mutlaka görmesi garantisi. Replikalardan okurken kendiliğinden sağlanmaz.Sözlükte gör → garantisi denir.

  • Yazan kullanıcıyı bir süre primary’ye yönlendir: en basit yol; oturumda “son yazma zamanı” tut.
  • Yazma konumunu bekle: primary’nin döndürdüğü log konumunu sakla, replika o konumu uygulamadan ondan okuma.
  • Kritik okumaları hep primary’den yap: bakiye, stok, ödeme durumu.

Bir mobil bankacılık uygulamasında bu üç yol yan yana nasıl duruyor, birlikte bakalım. Müşteri FAST ile para gönderiyor ve hemen hesap sayfasına dönüyor.

Derinleş · Hesap sayfası: hangi okuma hangi kopyadan? 4 dosya · ~70 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/com/bank/account/ AccountQueryService.java Bakiye hep primary'den; hesap hareketleri replikadan, ama müşteri az önce para gönderdiyse primary'den.

src/main/java/com/bank/account/AccountQueryService.java
@Service
class AccountQueryService {
private final JdbcClient primary;
private final JdbcClient replica;
private final LastWriteTracker lastWrite; // TransferService marks the customer after commit
AccountQueryService(@Qualifier("primary") JdbcClient primary,
@Qualifier("replica") JdbcClient replica,
LastWriteTracker lastWrite) {
this.primary = primary;
this.replica = replica;
this.lastWrite = lastWrite;
}
// The balance decides the next transfer, so it always comes from the primary.
BigDecimal balance(String iban) {
return primary.sql("SELECT balance FROM account WHERE iban = :iban")
.param("iban", iban)
.query(BigDecimal.class)
.single();
}
// Movements may lag a little, except right after this customer's own transfer.
List<Movement> recentMovements(long customerId, String iban) {
JdbcClient source = lastWrite.wroteWithin(customerId, Duration.ofSeconds(30)) ? primary : replica;
return source.sql("""
SELECT booked_at, amount, currency, description
FROM account_movement
WHERE iban = :iban
ORDER BY booked_at DESC
LIMIT 20
""")
.param("iban", iban)
.query(Movement.class)
.list();
}
}

src/main/resources/ application.yml İki bağlantı havuzu: biri yazılabilir sunucuyu, diğeri tercihen bir replikayı bulur.

src/main/resources/application.yml
bank:
datasource:
# Bound to two HikariDataSource beans with @ConfigurationProperties.
primary:
# The driver connects to whichever host accepts writes, so a failover needs no config change.
jdbc-url: jdbc:postgresql://pg-a:5432,pg-b:5432/corebank?targetServerType=primary
replica:
jdbc-url: jdbc:postgresql://pg-a:5432,pg-b:5432/corebank?targetServerType=preferSecondary&loadBalanceHosts=true
read-only: true

ops/postgres/ read-your-writes.sql PostgreSQL'de yazma konumunu bekleme yolu ve replikaların ne kadar geride olduğunu gösteren sorgu.

ops/postgres/read-your-writes.sql
-- On the primary, right after the transfer commits: remember this WAL position
-- in the customer's session.
SELECT pg_current_wal_lsn();
-- On a replica, before reading movements: has it replayed at least that far?
SELECT pg_last_wal_replay_lsn() >= CAST(:saved_lsn AS pg_lsn) AS caught_up;
-- On the primary: how far behind each replica is. Alert on replay_lag.
SELECT application_name, sync_state, write_lag, flush_lag, replay_lag
FROM pg_stat_replication;

counter-example/ BalanceController.java Şöyle de yazılabilirdi: bakiye readOnly transaction ile okunuyor; bak, gönderimden hemen sonra ne görünüyor.

counter-example/BalanceController.java
@RestController
class BalanceController {
// A routing DataSource sends readOnly transactions to the replica.
@Transactional(readOnly = true)
@GetMapping("/accounts/{iban}/balance")
BalanceView balance(@PathVariable String iban) {
// Called right after a FAST transfer: the replica may not have the debit yet,
// so the old balance shows up. The customer thinks the transfer failed
// and presses "send" again.
return accounts.findBalance(iban);
}
}
Kafam karıştı, daha basit anlat

Kullanıcı az önce yazdığını hemen görmek ister. En kolay yol, yazdıktan sonra kısa bir süre onun okumalarını asıl deftere yönlendirmektir.

Hızlı kontrolİleri

Kullanıcılar profillerini güncelledikten sonra eski bilgiyi görüyor. Okumalar replikalardan yapılıyor. En az bedelli çözüm hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her okumayı replikadan yapılabilir ya da primary'den yapılmalı olarak ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Replika yeterli

Birkaç saniyelik eski veri kimseyi yanıltmaz.

    Primary

    Eski veri yanlış bir karara ya da şaşkınlığa yol açar.

      Tuzaklar

      Replikalar arasında zıplamak. Farklı gecikmeli iki replikadan sırayla okuyan kullanıcı, zamanda geriye gidebilir: yorum görünür, sonra kaybolur.

      Senkron replika düşerse. PostgreSQL’de senkron replika yoksa commit’ler bekler; yazmalar durur. Birden çok aday replika ya da bilinçli bir geri düşme planı gerekir.

      readOnly ile yönlendirme. Spring’de @Transactional(readOnly = true) okumaları replikaya gönderiyorsa, yazmadan hemen sonra aynı istekte yapılan okuma da oraya gidebilir.

      Gecikmeyi izlememek. PostgreSQL’de pg_stat_replication, SQL Server’da redo kuyruğu, Oracle’da apply lag izlenmeli.

      Hızlı kontrolİleri

      Okumalar `readOnly` transaction'lar üzerinden replikaya yönlendiriliyor. Kullanıcı yorum ekleyince yorum listede görünmüyor. Hatalı satır hangisi?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Hatalı satıra dokun, sonra kontrol et.

      CommentController.java
      Java 21UTF-8LF

      Kendini sına

      Şimşek turu1/5

      Yazmalar primary'ye gider, replikalar değişiklikleri sonradan uygular.

      Soru 1/2İleri

      Bir yük dengeleyici okumaları iki replikaya rastgele dağıtıyor; biri az, diğeri çok geride. Kullanıcı yeni bir yorumu görüyor, sayfayı yenileyince yorum kayboluyor. Hangi garanti eksik?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Asenkron replikasyonda kayıt, kopyayı beklemeden tamamlanır. Kopyadan okumak eski veri gösterebilir; asıl makine çökerse kopyaya ulaşmamış yazmalar kaybolur.
      2. 2 Senkron replikasyonun da dereceleri var: 'kopya diske yazsın' kaybı önler ama okumayı tazelemez; 'kopya uygulasın' ikisini de sağlar, bedeli en çok beklemektir.
      3. 3 Kullanıcının kendi yazdığını görmesi gereken okumaları asıl veritabanına ya da o yazmayı almış bir kopyaya yönlendir.
      Sonraki kapı Üç kopyanın ikisine yazdın, birinden okudun. Yazdığını göreceğin garanti mi? Tutarlılık Seviyeleri ve Quorum — CAP'ı Doğru Okumak · 9 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.