Tek sunucuda çalışan bir MongoDB, o sunucu yeniden başladığı anda uygulamanızı da durdurur. Disk arızası, çekirdek güncellemesi, hatta yanlışlıkla verilen bir reboot komutu — hepsi aynı sonuca çıkar. MongoDB replica set kurulumu tam olarak bu tek noktalı arıza sorununu çözer: aynı veriyi eşzamanlı tutan birden fazla sunucu tanımlarsınız, biri düştüğünde kalanlar saniyeler içinde aralarında yeni bir birincil (primary) seçer ve uygulamanız kaldığı yerden devam eder.
Bu rehberde üç sunuculu, kimlik doğrulaması açık, üretime uygun bir replica set'i sıfırdan kuracağız. Ağ ve isim çözümleme hazırlığından başlayıp anahtar dosyası ile iç kimlik doğrulamasını kuracak, rs.initiate ile set'i başlatacak, yönetici kullanıcıyı oluşturacak, uygulamanın kullanacağı bağlantı dizesini yazacak ve en önemlisi yük devrini gerçekten test edeceğiz. Sonunda oplog boyutlandırması, yazma onayı seçimi ve sahada en sık karşılaştığım hatalar var. MongoDB'yi henüz kurmadıysanız önce MongoDB kurulumu yazısındaki adımları üç sunucunun her birinde uygulayın.
Replica Set Nedir, Neden En Az Üç Üye#
Bir replica set, aynı veri kümesini tutan mongod süreçlerinden oluşan bir gruptur. İçlerinden yalnızca biri birincildir ve tüm yazma işlemlerini o kabul eder; diğerleri ikincil (secondary) üyelerdir ve birincilin işlem günlüğünü (oplog) sürekli takip ederek kendilerini güncel tutar. Birincil erişilemez hâle geldiğinde kalan üyeler bir seçim başlatır ve çoğunluğun oyunu alan üye yeni birincil olur.
Buradaki anahtar kelime çoğunluktur ve üç üye şartının sebebi de budur. İki üyeli bir set'te biri düştüğünde kalan tek üye "çoğunluk" oluşturamaz — üç üyeli bir sette çoğunluk ikidir, iki üyeli bir sette de çoğunluk yine ikidir. Yani iki üyeli bir set, tek üye kaybında otomatik yük devri yapamaz ve salt okunur duruma düşer. Bu yüzden oy veren üye sayısı her zaman tek sayı olmalıdır: 3, 5, 7.
| Mimari | Üye | Bir üye kaybında | Not |
|---|---|---|---|
| Tek düğüm | 1 | Servis durur | Yalnızca geliştirme |
| İki veri düğümü | 2 | Yük devri yok | Kullanmayın |
| Üç veri düğümü | 3 | Otomatik yük devri | Önerilen standart |
| İki veri + hakem | 3 (biri hakem) | Otomatik yük devri | Ucuz ama riskli |
| Beş veri düğümü | 5 | İki üye kaybını tolere eder | Kritik sistemler |
Hakem (arbiter), veri tutmayan ama oy kullanan hafif bir üyedir. Üçüncü bir sunucunun maliyetinden kaçmak için cazip görünür ancak ciddi bir tuzağı vardır: iki veri düğümünden biri düştüğünde geriye tek veri kopyası kalır ve w: "majority" yazma onayı kullanan uygulamalarınız yazma yapamaz hâle gelir, çünkü veri çoğunluğu artık sağlanamamaktadır. Bütçeniz elveriyorsa hakem yerine gerçek bir üçüncü veri düğümü kullanın.
Ön Hazırlık: Ağ, İsim Çözümleme ve Saat#
Replica set üyeleri birbirlerine isimle ulaşır, IP ile değil. Bu yüzden set'i kurmadan önce üç sunucunun da birbirinin adını çözebildiğinden emin olun. Kendi DNS'iniz yoksa /etc/hosts dosyası yeterlidir ama üç sunucuda da aynı olmalıdır:
# Üç sunucunun HEPSİNDE /etc/hosts içine ekleyin
185.12.34.56 mongo1.firmaniz.com mongo1
185.12.34.57 mongo2.firmaniz.com mongo2
185.12.34.58 mongo3.firmaniz.com mongo3
# Doğrulayın: her sunucudan diğer ikisine bakın
ping -c 1 mongo2.firmaniz.com
getent hosts mongo3.firmaniz.com
İsim çözümlemesini rs.initiate içinde IP yazarak atlamayın. Sonradan sunucu taşımak zorunda kaldığınızda tüm set yapılandırmasını değiştirmeniz gerekir; isim kullanırsanız yalnızca DNS kaydını güncellersiniz.
İkinci hazırlık güvenlik duvarıdır. 27017 portu yalnızca set üyeleri ve uygulama sunucuları arasında açık olmalı, internete kesinlikle kapalı kalmalıdır:
# Her sunucuda: yalnızca diğer iki üye ve uygulama sunucusu
sudo ufw allow from 185.12.34.57 to any port 27017 proto tcp
sudo ufw allow from 185.12.34.58 to any port 27017 proto tcp
sudo ufw allow from 10.0.0.7 to any port 27017 proto tcp
sudo ufw enable
Üçüncüsü ve en çok atlanan hazırlık saat senkronizasyonudur. Seçim mekanizması ve oplog zaman damgaları saate duyarlıdır; sunucular arasında saniyeler mertebesinde kayma varsa tuhaf yük devri davranışları görürsünüz.
sudo apt install -y chrony
sudo systemctl enable --now chrony
chronyc tracking # "System time" sapması milisaniye seviyesinde olmalı
Anahtar Dosyası ile İç Kimlik Doğrulama#
Kimlik doğrulaması açık bir replica set'te üyelerin birbirini de doğrulaması gerekir. Bunun en basit yolu paylaşılan bir anahtar dosyasıdır. Dosyayı bir kez üretip üç sunucuya da aynı içerikle kopyalarsınız:
# Yalnızca BİR sunucuda üretin
openssl rand -base64 756 > /tmp/mongo-keyfile
# Üç sunucunun her birinde aynı içerikle yerleştirin
sudo mkdir -p /etc/mongodb
sudo install -o mongodb -g mongodb -m 400 /tmp/mongo-keyfile /etc/mongodb/keyfile
sudo rm /tmp/mongo-keyfile
# İzinleri doğrulayın: 400 ve mongodb sahipliği zorunludur
ls -l /etc/mongodb/keyfile
# -r-------- 1 mongodb mongodb 1024 Aug 25 10:12 /etc/mongodb/keyfile
İzinler tam olarak 400 olmalıdır. Grup ya da diğer kullanıcılar için okuma izni varsa mongod başlamayı reddeder ve günlüğe "permissions on keyfile are too open" satırını yazar. Bu, kurulumun en sık takıldığı noktadır; hata mesajı net olduğu için tanıması kolaydır ama scp ile kopyalarken izinler kolayca bozulur.
Yapılandırma ve Set'in Başlatılması#
Üç sunucunun /etc/mongod.conf dosyasını düzenleyin. Yalnızca bindIp satırındaki kendi adresleri farklı olacak, geri kalanı aynı:
# /etc/mongod.conf (mongo1 örneği)
storage:
dbPath: /var/lib/mongodb
net:
port: 27017
# Yerel arayüz + bu sunucunun özel ağ adresi. 0.0.0.0 YAZMAYIN.
bindIp: 127.0.0.1,185.12.34.56
security:
authorization: enabled
keyFile: /etc/mongodb/keyfile
replication:
replSetName: rs0
# Varsayılan, boş diskin %5'idir. Yazma hacminiz yüksekse elle büyütün.
oplogSizeMB: 8192
systemLog:
destination: file
path: /var/log/mongodb/mongod.log
logAppend: true
Üç sunucuda da servisi yeniden başlatın ve ayakta olduğunu doğrulayın:
sudo systemctl restart mongod
sudo systemctl status mongod --no-pager
sudo tail -n 20 /var/log/mongodb/mongod.log
Şimdi yalnızca birinci sunucuya bağlanıp set'i başlatın. Kimlik doğrulaması açık ama henüz kullanıcı olmadığı için MongoDB "localhost istisnası" sayesinde yerel bağlantıya izin verir:
// mongo1 üzerinde: mongosh
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1.firmaniz.com:27017", priority: 2 },
{ _id: 1, host: "mongo2.firmaniz.com:27017", priority: 1 },
{ _id: 2, host: "mongo3.firmaniz.com:27017", priority: 1 }
]
})
priority değeri, seçimlerde kimin öncelikli olacağını belirler. Birinci sunucuya 2 vererek "her şey normalse birincil bu olsun" demiş oluyoruz; bu, izleme ve yedekleme betiklerinin öngörülebilir davranması açısından işe yarar. Birkaç saniye sonra durumu kontrol edin:
rs.status().members.map(m => ({ ad: m.name, durum: m.stateStr, gecikme: m.optimeDate }))
// [ { ad: 'mongo1.firmaniz.com:27017', durum: 'PRIMARY', ... },
// { ad: 'mongo2.firmaniz.com:27017', durum: 'SECONDARY', ... },
// { ad: 'mongo3.firmaniz.com:27017', durum: 'SECONDARY', ... } ]
Bir üye STARTUP2 durumunda takılı kalıyorsa ilk eşitleme sürüyordur; büyük veri kümelerinde bu saatler alabilir. RECOVERING ya da UNREACHABLE görüyorsanız sorun genellikle güvenlik duvarı, isim çözümleme ya da anahtar dosyası izinlerindedir.
Kullanıcı Oluşturma ve Bağlantı Dizesi#
Set ayağa kalktıktan sonra birincil üye üzerinde yönetici kullanıcıyı oluşturun. Sırayı ters çevirmeyin: kullanıcıyı rs.initiate öncesinde oluşturursanız yalnızca o düğümde kalır.
// Birincil üzerinde
use admin
db.createUser({
user: "yonetici",
pwd: passwordPrompt(), // parolayı komut geçmişine yazmaz
roles: [ { role: "root", db: "admin" } ]
})
// Uygulama için dar yetkili kullanıcı
use uygulamadb
db.createUser({
user: "uygulama",
pwd: passwordPrompt(),
roles: [ { role: "readWrite", db: "uygulamadb" } ]
})
passwordPrompt() kullanmak, parolanın mongosh geçmişinde ve sunucu günlüklerinde görünmesini engeller. Uygulama tarafında bağlantı dizesine tüm üyeleri ve set adını yazın; sürücü hangisinin birincil olduğunu kendisi bulur ve yük devrinde otomatik olarak yeni birincile geçer:
mongodb://uygulama:[email protected]:27017,mongo2.firmaniz.com:27017,mongo3.firmaniz.com:27017/uygulamadb?replicaSet=rs0&authSource=uygulamadb&w=majority&retryWrites=true
Buradaki dört parametrenin hepsi bilinçli: replicaSet sürücünün topolojiyi keşfetmesini sağlar, authSource kullanıcının tanımlı olduğu veritabanını gösterir, w=majority yazmanın çoğunluğa ulaşmasını bekler, retryWrites=true ise yük devri sırasında düşen tek yazmanın otomatik tekrarlanmasını sağlar. Son ikisi sayesinde seçim süresi boyunca kullanıcı hata görmek yerine yalnızca birkaç yüz milisaniyelik gecikme yaşar.
Yazma Onayı, Okuma Tercihi ve Oplog#
Yazma onayı (write concern), bir yazmanın "başarılı" sayılması için kaç üyenin onaylaması gerektiğini belirler. Okuma tercihi (read preference) ise sorguların hangi üyeye gideceğini söyler. İkisi birlikte, tutarlılık ile performans arasındaki dengeyi kurar.
| Ayar | Anlamı | Ne zaman |
|---|---|---|
w: 1 | Yalnızca birincil onayladı | Kayıp tolere edilebilen günlük verisi |
w: "majority" | Çoğunluk onayladı | Varsayılan seçim, para ve sipariş verisi |
j: true | Diske (journal) yazıldı | Ani güç kesintisine karşı |
readPreference: primary | Her okuma birinciden | Kesin güncellik gerektiğinde |
readPreference: secondaryPreferred | Mümkünse ikincilden | Rapor ve analiz sorguları |
readPreference: nearest | En düşük gecikmeli üyeden | Coğrafi dağıtık kurulum |
İkincilden okuma cazip görünür ama bir bedeli vardır: ikincil üyeler birincilin gerisinde kalabilir, yani az önce yazdığınız kaydı hemen göremeyebilirsiniz. Kullanıcı bir formu kaydedip yönlendirildiği sayfada verisini bulamıyorsa sebep genellikle budur. Kural olarak kullanıcıya ait sıcak veriyi birincilden, raporları ikincilden okuyun.
Oplog, replikasyonun kalbidir ve boyutu doğrudan dayanıklılığınızı belirler. Bir ikincil üye, oplog penceresinden daha uzun süre çevrimdışı kalırsa artık aradaki farkı kapatamaz ve baştan tam eşitleme gerekir. Mevcut pencerenizi şöyle görürsünüz:
// Oplog boyutu ve kaç saatlik geçmiş tuttuğu
db.getSiblingDB("admin").runCommand({ replSetGetStatus: 1 }).ok
rs.printReplicationInfo()
// configured oplog size: 8192MB
// log length start to end: 92736secs (25.76hrs)
// İkincillerin ne kadar geride olduğu
db.printSecondaryReplicationInfo()
Pratik hedef en az 24 saatlik pencere tutmaktır; böylece bir üye bir gün boyunca bakımda kalsa bile geri döndüğünde tam eşitleme gerekmez. Yazma hacminiz yüksekse oplogSizeMB değerini artırın; bunu çalışan bir sette de yapabilirsiniz:
db.adminCommand({ replSetResizeOplog: 1, size: 16384 })
Yük Devri Testi, İzleme ve Sık Hatalar#
Kurulumu test etmeden üretime almayın. Yük devrini denetimli biçimde tetiklemenin en temiz yolu, birincile "çekil" demektir:
// Birincil üzerinde: 60 saniye boyunca birincil olma
rs.stepDown(60)
// Başka bir üyeden durumu izleyin
rs.status().members.map(m => m.name + " -> " + m.stateStr)
Ardından gerçek bir arıza senaryosunu da deneyin: birincilin ağını kesin ya da servisi durdurun ve uygulamanızın davranışını izleyin. Varsayılan seçim zaman aşımı yaklaşık on saniyedir, yani yeni birincil bu süre içinde belirlenir. Uygulamanız bu pencerede birkaç hata alıyorsa, sürücünün yeniden deneme ayarlarını ve retryWrites parametresini kontrol edin.
# Birincil sunucuda servisi durdur ve süreyi ölç
sudo systemctl stop mongod
# ... uygulamanın yeniden yazabildiği anı gözleyin ...
sudo systemctl start mongod
Sahada en sık gördüğüm hatalar şunlar:
bindIpdeğerini0.0.0.0yapmak. Kurulum "çalışsın diye" yapılan bu değişiklik, MongoDB'yi internete açar. Kimlik doğrulaması bile açık olsa bu kabul edilemez; yalnızca gerekli arayüzleri yazın.- Anahtar dosyasını farklı içerikle üretmek. Her sunucuda ayrı ayrı
openssl randçalıştırmak, üyelerin birbirini doğrulayamamasına yol açar. Dosya bir kez üretilir, kopyalanır. - Set adını yapılandırmada değiştirip
rs.reconfigyapmayı unutmak.replSetNameyalnızca başlangıçta anlamlıdır; sonradan değiştirmek yeni ve boş bir set demektir. - Yedeklemeyi replikasyon sanmak. Replica set, yanlışlıkla silinen bir koleksiyonu geri getirmez — silme işlemi de anında tüm üyelere yayılır. Ayrı bir yedekleme stratejisi şarttır; nasıl kurulacağını MongoDB yedekleme yazısında bulabilirsiniz.
- İzlemeyi atlamak. En az iki metriği toplayın: ikincillerin gecikmesi (
replicationLag) ve oplog penceresi. Gecikme büyümeye başladığında bunu saatler öncesinden görmek istersiniz.
İlişkisel dünyada aynı problemi nasıl çözdüğümüzü karşılaştırmalı okumak isterseniz MySQL replikasyon kurulumu yazısı iyi bir referanstır; mantık benzer, otomatik yük devri kısmı MongoDB'de yerleşiktir.
Sıkça Sorulan Sorular#
Replica set için kaç sunucu gerekir#
Otomatik yük devri için en az üç oy veren üye gerekir ve bu sayı tek olmalıdır. İki üyeli bir sette biri düştüğünde kalan üye çoğunluğu sağlayamaz ve set salt okunur duruma geçer. Bütçeniz üçüncü bir tam sunucuya elvermiyorsa hakem (arbiter) kullanabilirsiniz, ancak bu durumda tek veri kopyasıyla kalma riskini kabul etmiş olursunuz.
Replica set yedekleme yerine geçer mi#
Hayır. Replikasyon, sunucu arızasına karşı korur; insan hatasına ya da uygulama hatasına karşı korumaz. Yanlışlıkla çalıştırılan bir silme komutu saniyeler içinde tüm üyelere yayılır ve hiçbir kopyada eski veri kalmaz. Bu yüzden replica set'in yanında düzenli, sunucudan bağımsız ve geri yükleme testi yapılmış bir yedekleme mutlaka bulunmalıdır.
İkincil üyeden okumak güvenli mi#
Teknik olarak güvenlidir ama tutarlılık açısından dikkat ister. İkincil üyeler birincili takip ettiği için milisaniyelerden saniyelere uzanabilen bir gecikme vardır; az önce yazdığınız kaydı ikincilden okuduğunuzda bulamayabilirsiniz. Raporlama ve analiz sorgularını ikincile yönlendirmek mantıklıdır, kullanıcının hemen ardından göreceği veriyi ise birincilden okuyun.
Yük devri ne kadar sürer#
Varsayılan yapılandırmada seçim zaman aşımı yaklaşık on saniyedir, yani birincilin kaybı fark edildikten sonra yeni birincil genellikle on saniye içinde belirlenir. Uygulama tarafında bu süreyi kullanıcıya yansıtmamak için bağlantı dizesinde retryWrites=true kullanın; sürücü, seçim sırasında başarısız olan tek yazmayı yeni birincile otomatik olarak tekrar gönderir.
Oplog boyutunu nasıl belirlerim#
Ölçüt boyut değil süredir: oplog, en az 24 saatlik değişiklik geçmişini tutabilmelidir. rs.printReplicationInfo() çıktısındaki "log length" değeri size mevcut pencerenizi saat cinsinden verir. Bu değer bakım pencerelerinizden kısaysa replSetResizeOplog komutuyla çalışırken büyütebilirsiniz; küçük oplog, bakımdan dönen bir üyenin baştan tam eşitleme yapmasına yol açar.
Kurulumdan sonra yeni bir üye nasıl eklerim#
Yeni sunucuya MongoDB'yi kurun, aynı anahtar dosyasını ve aynı replSetName değerini verin, servisi başlatın. Ardından birincil üzerinde rs.add("mongo4.firmaniz.com:27017") komutunu çalıştırın. Yeni üye önce STARTUP2 durumuna geçip veriyi baştan kopyalar, eşitleme bittiğinde kendiliğinden SECONDARY olur. Veri hacmi büyükse bu işlem saatler sürebilir ve ağ trafiği yaratır, yoğun saatlerde başlatmayın.
Kapanış#
Replica set, MongoDB'de yüksek erişilebilirliğin standart yoludur ve doğru kurulduğunda yıllarca müdahale gerektirmeden çalışır. Aklınızda kalması gereken dört alışkanlık: oy veren üye sayısını her zaman tek tutun, üyeleri IP ile değil isimle tanımlayın, anahtar dosyasını bir kez üretip 400 izinle kopyalayın ve kurulumu rs.stepDown ile gerçekten test etmeden üretime almayın. Bağlantı dizesine tüm üyeleri, replicaSet adını ve retryWrites=true parametresini yazmayı da unutmayın; yük devrinin kullanıcıya yansımaması bu ayrıntıya bağlıdır.
Üç ayrı sunucuyla çalışacaksanız bu makinelerin aynı veri merkezinde, düşük gecikmeli bir özel ağda ve öngörülebilir kaynaklarla durması işinizi kolaylaştırır. VDS, sanal sunucu ve bulut sunucu paketlerimiz bu tür kümeler için uygundur; kurulum, izleme ve yük devri tatbikatlarını kendiniz yürütmek istemiyorsanız sunucu yönetimi hizmetimiz bu işi üstlenir, veri güvenliği tarafını da yedekleme çözümümüzle tamamlayabilirsiniz.