logologo
Ai badge logo

Bu madde yapay zeka desteği ile üretilmiştir.

BlogGeçmiş
Blog
Avatar
Ana YazarYakup Hamit Hancı10 Mayıs 2025 07:06

Patroni ile PostgreSQL High Availability: "Kesinti Olmazsa Olmaz, Patroni Varsa Sorun Olmaz!"

Bilişim Ve İletişim Teknolojileri+1 Daha
fav gif
Kaydet
viki star outline

"PostgreSQL harika bir veritabanı... ama kesinti yaşandığında? Patroni burada devreye giriyor."

Giriş: Dağıtık Sistemlerde PostgreSQL'in Derdi

PostgreSQL, tek başına son derece güvenilir ve performanslı bir veritabanıdır. Ancak işler büyüdüğünde, yani veritabanını dağıtık bir sistem içinde birden fazla sunucuda yüksek erişilebilir şekilde kullanmak istediğinizde, birtakım zorluklar ortaya çıkar:

  • Lider (primary) sunucu arızalanırsa ne olacak?
  • Yedek sunucular (replica) ne zaman devreye girmeli?
  • Manuel müdahale mi gerekiyor?
  • Veri tutarlılığı ve erişilebilirlik arasında denge nasıl sağlanacak?

İşte bu gibi durumlarda Patroni hayat kurtarıyor.

Patroni Nedir, Ne Yapar?

Patroni, PostgreSQL için otomatik failover, leader election (lider seçimi), health check ve cluster yönetimi sağlayan bir HA (High Availability) yöneticisidir. Aslında Patroni, PostgreSQL'e HA kazandıran akıllı bir düzenleyici (orchestrator)'dir. Basit bir anlatımla Patroni:

  • Etcd veya Consul gibi bir dağıtık KV store ile küme durumunu tutar,
  • Sunucuların sağlığını izler,
  • Lider düştüğünde otomatik olarak yeni bir lider seçer ve şekli değiştirir,
  • PostgreSQL'in replikasyon ayarlarını dinamik olarak düzenler.
Bu yazıda HAProxy veya PgBouncer gibi ara katmanlardan bahsetmeyeceğiz. Onlara özel yazılar yakında yayınlanacak, takipte kalın!

CAP Teoremi ve Patroni

CAP teoremi der ki: Üç şeyden sadece ikisini seçebilirsin:

1. Consistency (Tutarlılık)

2. Availability (Erişilebilirlik)

3. Partition Tolerance (Bölünebilirlik toleransı)

Dağıtık sistemlerde ağ bölünmeleri (partition) kaçınılmaz olduğuna göre, geriye tutarlılık (Consistency) ile erişilebilirlik (Availability) arasında bir seçim yapmak kalıyor. Patroni, "Consistency + Partition Tolerance" tarafını seçer. Neden?

  • Birden fazla primary (yazılabilir) sunucuya izin vermez.
  • Yazılacak verilerin tutarlığını korumaya öncelik verir.
  • Ancak bu durum, bazen sistemi yazma işlemleri için geçici olarak erişilemez hale getirebilir.

Etcd ve Consul: Patroni'nin Dayandığı Taşlar

Patroni, bir liderin kim olduğunu ve cluster'ın genel sağlık durumunu merkezi olarak tutmak için bir KV Store'a ihtiyaç duyar. Bu genellikle:

Bu sistemler:

  • RAFT protokolü ile dağıtık bir consensus sağlar,
  • Yüksek erişilebilirlik sağlamak için en az 3 node ile çalışırlar,
  • Patroni her sunucuda bu KV store'a yazma ve okuma işlemleri yapar.

Lider Seçimi Nasıl Gerçekleşirir?

Patroni, lider seçimini Etcd üzerinden gerçekleştirir. Süreç şuna benzer:

1. Her PostgreSQL örneği, bağlı olduğu Patroni botu aracılığıyla Etcd'de belirli bir anahtarın süresi dolmuş olup olmadığını kontrol eder.

2. Eğer liderlik anahtarı boştaysa, bot bu anahtarı belirli bir süreliğine almak için Etcd'ye başvurur.

3. Etcd, arka planda RAFT protokolü kullanarak yalnızca bir örneğin bu anahtarı almasına izin verir. Böylece yarış durumları engellenmiş olur.

4. Anahtarı ilk alan örnek, sistemin lideri olarak kabul edilir ve Patroni botu bu PostgreSQL örneğini primary (lider) olacak şekilde yapılandırır.

5. Diğer örnekler, bu liderliği fark eder ve kendilerini otomatik olarak replica (yedek) moduna alır.

Bu sayede lider, merkezi bir kontrol noktası olmadan dağıtık biçimde ve tutarlı şekilde seçilir.

Kısıtlamalar ve Sınırlar

  • Single Primary mimarisi: Aynı anda yalnızca bir sunucu yazılabilir durumda olur.
  • Etcd/Consul'a bağımlılık: Bu sistemler de HA olmalı. Aksi halde Patroni de çalışamaz.
  • Split-brain önleme: Yanlış lider seçimi olmaması için karmaşık kontrol mekanizmaları gerekir.

Docker ile Patroni Kurulumu (Minimal Örnek)

Basit bir örnek için 3 PostgreSQL + Patroni konteyneri ve 3 etcd konteyneri kullanabiliriz:


Öncelikle geliştirici github reposundan proje dosyasını temin ediniz. Proje dosyasındaki Dockerfile'ı build ederek örnekte kullanacağımız patroni docker image'ını oluşturunuz.


# docker-compose.yml
version: "2"
networks:
    demo:
        external: true
services:
    etcd1: &etcd
        image: patroni
        networks:
            demo:
                ipv4_address: 10.0.0.11
        environment:
            ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
            ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
            ETCD_INITIAL_CLUSTER: etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
            ETCD_INITIAL_CLUSTER_STATE: new
            ETCD_INITIAL_CLUSTER_TOKEN: tutorial
            ETCD_UNSUPPORTED_ARCH: arm64
        container_name: demo-etcd1
        hostname: etcd1
        ports:
            - 1379:2379
        command: etcd -name etcd1 -initial-advertise-peer-urls http://etcd1:2380
    etcd2:
        <<: *etcd
        container_name: demo-etcd2
        hostname: etcd2
        networks:
            demo:
                ipv4_address: 10.0.0.12
        ports:
            - 2379:2379
        command: etcd -name etcd2 -initial-advertise-peer-urls http://etcd2:2380
    etcd3:
        <<: *etcd
        container_name: demo-etcd3
        hostname: etcd3
        networks:
            demo:
                ipv4_address: 10.0.0.13
        ports:
            - 3379:2379
        command: etcd -name etcd3 -initial-advertise-peer-urls http://etcd3:2380
    patroni1:
        image: patroni
        networks:
            demo:
                ipv4_address: 10.0.0.21
        env_file: docker/patroni.env
        hostname: patroni1
        container_name: demo-patroni1
        ports:
            - 1008:8008
            - 1432:5432
        volumes:
            - ./patroni1_istanbul.yml:/home/postgres/postgres0.yml
        environment: &patroni_env
            ETCDCTL_ENDPOINTS: http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
            PATRONI_ETCD3_HOSTS: "'etcd1:2379','etcd2:2379','etcd3:2379'"
            PATRONI_SCOPE: demo
            PATRONI_NAME: patroni1
    patroni2:
        image: patroni
        networks:
            demo:
                ipv4_address: 10.0.0.22
        env_file: docker/patroni.env
        hostname: patroni2
        container_name: demo-patroni2
        volumes:
            - ./patroni2_ankara.yml:/home/postgres/postgres0.yml
        ports:
            - 2008:8008
            - 2432:5432
        environment:
            <<: *patroni_env
            PATRONI_NAME: patroni2
    patroni3:
        image: patroni
        networks:
            demo:
                ipv4_address: 10.0.0.23
        env_file: docker/patroni.env
        hostname: patroni3
        container_name: demo-patroni3
        volumes:
            - ./patroni3_izmir.yml:/home/postgres/postgres0.yml
        ports:
            - 3008:8008
            - 3432:5432
        environment:
            <<: *patroni_env
            PATRONI_NAME: patroni3


# ---------- patroni_istanbul.yml ----------
restapi:
  listen: 0.0.0.0:8008
  connect_address: patroni1:8008
  authentication:
    username: admin
    password: admin
# etcd:
#   hosts:
#     - etcd1:2379
#     - etcd2:2379
#     - etvd3:2379
bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    primary_start_timeout: 300
    synchronous_mode: true
    synchronous_mode_strict: false
    synchronous_standby_names: "patroni2,patroni3"
    postgresql:
      use_pg_rewind: true
      pg_hba:
        - local all all trust
        - host replication replicator 0.0.0.0/0 md5
        - host all all all md5
      parameters:
        max_connections: 100
initdb:
  - locale: en_US.UTF-8
  - encoding: UTF8
  - data-checksums
postgresql:
  listen: 0.0.0.0:5432
  connect_address: patroni1:5432
  data_dir: /home/postgres/data
  authentication:
    replication:
      username: replicator
      password: rep-pass
    superuser:
      username: postgres
      password: patroni
    rewind:
      username: rewind_user
      password: rewind_password
  parameters:
    unix_socket_directories: '..'
tags:
  location: istanbul
  noloadbalance: false
  clonefrom: false
  nostream: false


# ---------- patroni_ankara.yml ----------
restapi:
  listen: 0.0.0.0:8008
  connect_address: patroni2:8008
  authentication:
    username: admin
    password: admin
# etcd:
#   hosts:
#     - etcd1:2379
#     - etcd2:2379
#     - etcd3:2379
bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    primary_start_timeout: 300
    synchronous_mode: true
    synchronous_mode_strict: false
    synchronous_standby_names: "patroni1,patroni3"
    postgresql:
      use_pg_rewind: true
      pg_hba:
        - local all all trust
        - host replication replicator 0.0.0.0/0 md5
        - host all all all md5
      parameters:
        max_connections: 100
initdb:
  - locale: en_US.UTF-8
  - encoding: UTF8
  - data-checksums
postgresql:
  listen: 0.0.0.0:5432
  connect_address: patroni2:5432
  data_dir: /home/postgres/data
  authentication:
    replication:
      username: replicator
      password: rep-pass
    superuser:
      username: postgres
      password: patroni
    rewind:
      username: rewind_user
      password: rewind_password
  parameters:
    unix_socket_directories: '..'
tags:
  location: ankara
  noloadbalance: false
  clonefrom: false
  nostream: false


# ---------- patroni_izmir.yml ----------
restapi:
  listen: 0.0.0.0:8008
  connect_address: patroni3:8008
  authentication:
    username: admin
    password: admin
# etcd:
#   hosts:
#     - etcd1:2379
#     - etcd2:2379
#     - etcd3:2379
bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    primary_start_timeout: 300
    synchronous_mode: true
    synchronous_mode_strict: false
    synchronous_standby_names: "patroni1,patroni2"
    postgresql:
      use_pg_rewind: true
      pg_hba:
        - local all all trust
        - host replication replicator 0.0.0.0/0 md5
        - host all all all md5
      parameters:
        max_connections: 100
initdb:
  - locale: en_US.UTF-8
  - encoding: UTF8
  - data-checksums
postgresql:
  listen: 0.0.0.0:5432
  connect_address: patroni3:5432
  data_dir: /home/postgres/data
  authentication:
    replication:
      username: replicator
      password: rep-pass
    superuser:
      username: postgres
      password: patroni
    rewind:
      username: rewind_user
      password: rewind_password
  parameters:
    unix_socket_directories: '..'
tags:
  location: izmir2
  nofailover: false
  noloadbalance: false
  clonefrom: false
  nostream: false

Konuya dair örnek (Yazar tarafından oluşturulmuştur)

Kapanış ve Son Sözler

Patroni, PostgreSQL'i HA yapıya taşımak için oldukça etkili ve esnek bir çözümdür.

Bir sonraki yazımızda, Patroni cluster'ına HAProxy ile yönlendirme yapmayı ve PgBouncer ile bağlantı havuzu oluşturmayı anlatacağız. Kaçırmayın! 


Takipte Kalın, Verileriniz Ayakta Kalsın!

Kaynakça

“Etcd Documentation.” etcd.io. Erişim tarihi: 10 Mayıs 2025. https://etcd.io/docs/.


“Consul Documentation.” HashiCorp. Erişim tarihi: 10 Mayıs 2025. https://developer.hashicorp.com/consul/docs.


“PostgreSQL Documentation: Streaming Replication.” PostgreSQL Global Development Group. Erişim tarihi: 10 Mayıs 2025. https://www.postgresql.org/docs/current/warm-standby.html.


“Patroni Documentation.” Read the Docs. Erişim tarihi: 10 Mayıs 2025. https://patroni.readthedocs.io/.


“Raft Consensus Algorithm Explained.” The Secret Lives of Data. Erişim tarihi: 10 Mayıs 2025. https://thesecretlivesofdata.com/raft/.


“Patroni Repository.” GitHub. Erişim tarihi: 10 Mayıs 2025. https://github.com/patroni/patroni.

Sen de Değerlendir!

0 Değerlendirme

Blog İşlemleri

KÜRE'ye Sor