Panelden düğmeye bas, veritabanın açılsın. Sistem sunucunun belleğini ölçer, o veritabanına ne kadar ayıracağını ve iç ayarlarını kendisi hesaplar. Sen hiçbir teknik değer girmezsin.
Ana kopya çökerse yedeğe kendisi geçer — uygulamanın bağlantı adresi değişmeden.
Sayfa iki bölgeden oluşur. Üstte şu an açık olanlar kart hâlinde: ne kadar bellek aldıkları, hangi porttan bağlanılacağı, yedek kopyası ve otomatik devri var mı. Altta kapalı olanlar tek satır — hiç kaynak harcamıyorlar, bir kartı hak etmiyorlar. Satıra tıklayınca ne işe yaradığı, tahmini belleği ve lisansı açılır.
Üst bardaki birincil sayı gerçek kullanımdır, altındaki satırda ise iki ayrı büyüklük durur: baştan ayrılan (motorun açılışta gerçekten aldığı bellek) ve üst sınır (docker limiti). İkisini ayırmak şart, çünkü docker limiti bir tavandır, rezervasyon değil — tavanları toplayıp RAM ile kıyaslamak, yoldaki arabaların azami hızlarını toplayıp "yol kapasitesi aşıldı" demeye benzer. Ölçülen bir örnek: 16 GB'lık sunucuda tavan toplamı 14 GB görünürken gerçek kullanım 2 GB, çekirdeğin bellek baskısı ise sıfırdı. Ayrıntı: Bellek otomatik hesaplanır.
Gündelik işler kartın yüzünde (paneli aç, bağlantı bilgisi). Geri dönüşü zor olanlar — kapatmak, yedek kopyayı kaldırmak, otomatik devri kapatmak — kapalı bir bölümde ve her biri ne olacağını tek cümleyle söylüyor. Yan yana duran altı düğme arasından yanlışına basmak bu üründe mümkün değil.
Yedekler kendi sayfasında (Araçlar → Yedekler): gecelik turun saati ve kaç
gün saklanacağı, her motorun kaç yedeği olduğu, en yenisinin ne zaman alındığı
ve her dosyanın kaynağı — elle, zamanlı ya da dış (komut satırı).
Kurtarma da buradan: Son yedeğe dön ya da dosya listesinden belirli bir
güne. Geri yükleme veriyi silip yerine koyduğu için onay penceresi motor adını
yazdırır — tek tıkla olacak bir iş değil.
Her motorun satırında bir de prova rozeti var: prova geçti 17 dakika önce · 10 sn. Bu, o yedeğin tek kullanımlık bir container'da gerçekten geri
yüklendiği ve kaç saniye sürdüğü demek — vaat değil ölçüm. Provayı panelden,
haftalık zamanlayıcıdan ya da komut satırından çalıştırmanız fark etmez:
sonuç aynı deftere, kaynağı yazılı olarak düşer.
İzleme ayrı bir kurulum değil, panelden açılan bir modül: İzleme aç deyince Prometheus + Grafana kalkar, açık olan her motorun exporter'ı hedef listesine kendiliğinden eklenir ve 11 hazır pano gelir — hepsi Türkçe ve "her şey yolunda mı?" sorusuna cevap verecek şekilde yazılmış. Tek bir PromQL sorgusu yazmanız gerekmez; kapattığınızda da hedef listesinden kendiliğinden çıkar.
Panelin altında ne olduğu yazıyor: hangi motor ne zaman açıldı, ne kadar
bellek ayrıldı, hangi ayar hesaplandı, bir aktivasyon neden reddedildi, otomatik
devir ne zaman ve hangi sebeple çalıştı. Sunucuya girip docker logs
okumadan olan biteni buradan takip edersiniz.
İç ağda alan adı olmadığı için TLS sertifikasını sunucu kendisi üretir. Tarayıcının "güvenli değil" uyarısını kaldırmak için tek seferlik bu rehber adım adım anlatır — sertifikayı kurduktan sonra sayfa sizi otomatik panele geçirir. Panele bir kez girdiğinizde bütün yönetim ekranları (phpMyAdmin, pgAdmin, Grafana…) parola sormadan açılır; dışarıdan doğrudan gelen biri ise hâlâ parola ekranıyla karşılaşır.
Önkoşul: Docker (yoksa install.sh size kurulum komutunu verir):
curl -fsSL https://get.docker.com | sudo sh && sudo usermod -aG docker $USER && newgrp dockerSonra:
sudo mkdir -p /opt/databases && sudo chown $USER:$USER /opt/databases
git clone https://github.com/halilibrahimd27/databases-stack.git /opt/databases
cd /opt/databases && ./install.shBu kadar. Soru sormaz. Parolaları, TLS sertifikalarını ve sunucu adresini kendisi
üretir/algılar, sonuçları ekrana ve credentials.txte yazar.
Sonra tarayıcıdan https://<sunucu-ip>/ adresine gidin ve ihtiyacınız olan
veritabanının satırındaki Aktif Et düğmesine basın.
Tarayıcı "güvenli değil" diyorsa:
http://<sunucu-ip>/ca.crtadresinden sertifikayı indirip bilgisayarınıza kurun, uyarı kalkar. Bu iç ağa özel bir sertifika otoritesidir — alan adı (domain) gerektirmez, internete çıkmaz.
Kurulumdan sonra hiçbir veritabanı çalışmıyor. Sadece üç küçük servis ayakta: giriş kapısı (nginx), kontrol servisi ve Adminer. Toplam ~450 MB.
Panelde bir veritabanını açtığınızda arka planda şunlar olur:
"Aktif Et" → Kontrol servisi sunucuyu ölçer
├─ Toplam RAM, çekirdeğin MemAvailable'ı, boş disk, CPU
├─ Açık motorların REZERVESİ (açılışta gerçekten ayırdıkları)
├─ Açık container'ların TAVANI (docker --memory)
└─ /proc/pressure/memory — çekirdek baskı altında mı?
↓
Üç kapı da geçiliyor mu?
├─ HAYIR → açmaz, HANGİ kapıya takıldığını söyler
└─ EVET → tavanı ve motorun iç ayarlarını hesaplar
(buffer pool, JVM heap, WiredTiger cache,
max_connections, work_mem …)
↓
docker compose --profile <motor> up -d
Kapalı bir veritabanı hiç container yaratmaz — sıfır RAM, sıfır CPU tüketir. Kapatmak verileri silmez; diskte kalır, tekrar açtığınızda her şey yerindedir.
Belleğin iki ayrı büyüklüğü var ve bu ürünün en pahalı hatası ikisini aynı şey sanmaktı.
| Rezerve (taban) | Tavan (limit) | |
|---|---|---|
| Ne demek? | Motorun açılışta gerçekten ayırdığı bellek | docker --memory: aşılırsa çekirdek container'ı öldürür (OOM) |
| Nereden gelir? | PostgreSQL shared_buffers, MariaDB innodb_buffer_pool_size, JVM motorlarında -Xms |
Kontrol servisinin motora verdiği üst sınır |
| Redis · MSSQL · MinIO · ClickHouse | ~0 — boş başlarlar, tavana doğru büyürler | Büyüyebilecekleri son nokta |
| Toplamı | Dağıtılabilir belleği asla aşamaz | Dağıtılabiliri aşabilir (varsayılan sınır: 1,5 katı) |
Dağıtılabilir bellek = toplam RAM − işletim sistemi payı − çekirdek servislerin payı.
16 GB'lık bir test sunucusunda ölçülen tablo:
| Container | Tavan | Gerçek kullanım |
|---|---|---|
| mariadb | 3196 MB | 243 MB (%7) |
| mariadb-replica | 3196 MB | 213 MB (%6) |
| postgresql | 2397 MB | 98 MB (%4) |
| redis | 1278 MB | 5 MB (%0) |
Aynı makinede toplam RAM 15984 MB, tavanların toplamı 15087 MB, dağıtılabilir bellek ise 12340 MB (15984 − 3196 işletim sistemi − 448 çekirdek servisler). Yani tavan toplamı dağıtılabilirin %122'si. Buna karşılık:
free -m→ kullanılan 1508 MB, available 13987 MB (makine %91 boş),/proc/pressure/memory→some avg10=0.00 · avg60=0.00,full avg10=0.00(çekirdek tek bir görevi bile bellek için bekletmiyor),- motorların gerçekten ayırdığı toplam: 2516 MB — dağıtılabilirin %20'si.
Tavanları toplayıp RAM ile kıyaslamak, yoldaki arabaların azami hızlarını toplayıp "yol kapasitesi aşıldı" demeye benzer. Ürün bir süre tam olarak bunu yapıyordu: panel "AYRILAN BELLEK 15 GB / 12 GB · %122 aşım" yazıyor ve kapalı motorların hepsinde "bellek yetmiyor" diyordu — boş bir makinede.
- Rezerve kapısı — sert.
Σ rezerve + yeni motorun rezervesi ≤ dağıtılabilir. Asla esnetilmez: rezerve, motorun ayıracağı gerçek bellektir; sığmıyorsa açmak OOM'a davetiyedir. Sığmayan istek önce küçültülür (daha küçük tavan → daha küçük rezerve); asgari tavanda bile sığmıyorsa reddedilir. - Tavan kapısı — yumuşak.
Σ tavan + yeni tavan ≤ dağıtılabilir × 1,5. Tavanların hepsi aynı anda dolmaz. Katsayı kontrol servisininOVERCOMMIT_LIMITortam değişkeniyle değişir;1.0yazmak aşırı taahhüdü kapatır, yani yukarıdaki eski davranışa döner. - Çekirdek kemeri.
/proc/meminfodakiMemAvailableyeni rezerveyi + emniyet payını karşılıyor mu;/proc/pressure/memorybaskı bildiriyor mu? Defter ne derse desin çekirdeğin gerçeği bağlayıcıdır. PSI'ı olmayan eski çekirdeklerde baskı kapısı atlanır — ölçemediğimiz bir şeyi gerekçe gösterip motor açtırmamak kullanıcıya yalan söylemek olurdu.
Reddedilen aktivasyon hangi kapıya takıldığını yazar; hepsine birden "bellek yetmiyor" demez.
Tavan toplamı politika sınırını geçtiğinde (bir motor elle büyütüldü,
sunucudan RAM eksildi, ya da OVERCOMMIT_LIMIT düşürüldü) kontrol servisi
bunu bildirir. Yeniden dengeleme — panelden ya da POST /api/rebalance —
açık motorların tavanlarını yeniden hesaplar ve docker update ile canlı
uygular:
- Container'lar yeniden başlatılmaz. Açık bağlantılar kopmaz, InnoDB
kurtarma çalışmaz, kesinti olmaz. (Kolay yol
compose up -dile motoru yeniden yaratmak olurdu; o yol çalışan veritabanını kapatır.) - Yalnız tavan değişir. Motorun açılışta ayırdığı bellek çalışırken küçültülemez — buffer pool'u geri veremezsiniz. Bu yüzden yeniden dengeleme hiçbir tavanı motorun mevcut rezervesinin altına indirmez; o ayar ancak motor yeniden başlatıldığında yeni tavana göre hesaplanır.
./scripts/e2e/sizing.sh bunu ölçüyor: aşırı taahhüt durumunu kendisi
yaratıyor, yeniden dengelemeyi çağırıyor, tavanın gerçekten düştüğünü
cgroup'tan okuyor ve .State.StartedAt ile hiçbir container'ın yeniden
başlatılmadığını doğruluyor.
Ayrıntı, formüller ve motor başına rezerve tablosu: docs/BELLEK.md
Aynı hesabın somut karşılığı — boş bir sunucuda, ./stack.sh plan çıktısı:
| Sunucu | MariaDB açılırsa | Elasticsearch açılırsa |
|---|---|---|
| 512 MB | açılmaz — 512 MB tavan + 320 MB panel/exporter dağıtılabilire sığmıyor | açılmaz |
| 2 GB | tavan 512 MB · rezerve (buffer pool) 307 MB | açılmaz |
| 4 GB | tavan 819 MB · rezerve 491 MB | tavan 1024 MB · rezerve (JVM heap) 512 MB |
| 16 GB | tavan 3276 MB · rezerve 1965 MB | tavan 2949 MB · rezerve 1474 MB |
| 128 GB | tavan 16 GB (tek motor sunucuyu yutmasın) · rezerve 9830 MB | tavan 16 GB · rezerve 8192 MB |
4 GB'lık makinede 16 GB'lık veritabanı açılmaya çalışılmaz; 128 GB'lık makinede
de varsayılan değerlerde kalınmaz. Kapılardan biri kapalıysa kart pasifleşir ve
hangi kapı olduğunu yazar. Tavan kapısına takılan bir ret, ekranda boş
belleği gördüğü için haklı olarak "ama yer var" diyen kullanıcıya şunu söyler:
"Bu bir TAVAN sıkışmasıdır, belleğin dolu olduğu anlamına GELMEZ" — ve o
andaki çekirdek boş bellek ölçümünü, baskı seviyesini, çözüm yollarını
(yeniden dengele / bir motoru durdur / OVERCOMMIT_LIMIT'i yükselt) sıralar.
Hesabı görmek için: ./stack.sh plan mongodb
Hepsi kapalı gelir; yalnız kullandıklarınız açılır.
| Veritabanı | Ne için | Panel | |
|---|---|---|---|
| 🐬 | MariaDB | Klasik tablolu veri — kullanıcılar, siparişler, ürünler | phpMyAdmin |
| 🐘 | PostgreSQL | Aynı iş + JSON, konum verisi, karmaşık sorgular | pgAdmin |
| 🍃 | MongoDB | Sabit şeması olmayan kayıtlar | Mongo Express |
| 🔴 | Redis | Önbellek, oturum, kuyruk (kalıcı depo değil) | RedisInsight |
| 🟥 | SQL Server | .NET / Windows tabanlı kurumsal uygulamalar | Adminer |
| 🌀 | Cassandra | Çok yüksek yazma hacmi, lineer ölçek | cqlsh |
| 🔎 | Elasticsearch | Site içi arama, log analizi | Kibana |
| 📨 | Kafka | Servisler arası olay akışı | Kafka UI |
| 🐰 | RabbitMQ | Basit iş kuyruğu (Kafka'dan çok daha kolay) | Management UI |
| 📊 | ClickHouse | Rapor ve analiz sorguları (OLAP) | Play UI |
| 🕸️ | Neo4j | İlişki ağırlıklı veri, öneri motorları | Neo4j Browser |
| 🪣 | MinIO | Dosya/görsel depolama (S3 uyumlu) | MinIO Console |
Ne seçeceğinizi bilmiyorsanız: PostgreSQL (verileriniz için) + Redis (hız için) çoğu proje için doğru başlangıçtır.
Tek giriş kapısı var; hiçbir panelin portu doğrudan dışarı açılmaz. Hepsi TLS + parola arkasından geçer.
| Adres | Ne |
|---|---|
https://<sunucu>/ |
Yönetim paneli |
https://<sunucu>/yedekler |
Yedekler ve geri yükleme |
https://<sunucu>:8081…8091 |
Veritabanı panelleri (kapalıysa "pasif" sayfası) |
https://<sunucu>:9443/metrics/<motor> |
Prometheus metrikleri |
<sunucu>:3306, 5432, 27017 … |
Uygulamanızın bağlanacağı veritabanı portları |
Veritabanı portları da gateway üzerinden geçer. Bu iki şey sağlar: devirde bağlantı adresiniz değişmez, ve container'lar host'a doğrudan port açmaz.
Bağlantı bilgisini panelden Bağlantı bilgisi düğmesiyle ya da
./stack.sh conn postgresql ile kopyalayabilirsiniz.
Panelin yaptığı her şeyi yapar; aynı otomatik boyutlandırma çalışır.
./stack.sh list # motorlar, durumları, tahmini bellek
./stack.sh enable postgresql # aç
./stack.sh plan elasticsearch # açılsa ne kadar ayrılırdı?
./stack.sh disable redis # kapat (veri silinmez)
./stack.sh conn mariadb # bağlantı bilgisi
./stack.sh replica on postgresql # yedek kopya kur
./stack.sh backup # aktif motorların hepsini yedekle
./stack.sh app-user # uygulama için kısıtlı kullanıcı
./stack.sh doctor # kurulum sağlık kontrolüPanelde kendi sayfası var: https://<sunucu>/yedekler. Panelin üstündeki
Araçlar satırındaki Yedekler bağlantısından açılır; sayfanın başındaki
← Yönetim paneli ile geri dönersiniz. Aynı parolanın, aynı kapının
arkasındadır — panel neyse o.
Ayrı sayfa olmasının sebebi şu: yedek listesi motor başına dosya dosya büyüyen bir liste. Yönetim panelinin altına sıkıştırıldığında, "dün gece yedek alındı mı?" sorusunun cevabı on iki kartın altında, sayfanın görünmeyen kısmında kalıyordu.
Bir yedeğin sağlam görünmesi, geri yüklenebileceği anlamına gelmez. Bu ürün o boşluğu kapatıyor: prova, yedeği tek kullanımlık bir container ve tek kullanımlık bir hacimde gerçekten geri yükler, süreyi ölçer, tablo/satır sayar ve üretimle karşılaştırır. Sonra kendini siler.
./scripts/restore-drill.sh mariadb # en yeni yedekle provaÜretime, üretim hacmine ve gateway'e dokunmaz — bu bir niyet beyanı değil,
prova container'ı --network none ile ve ayrı bir hacimle açılıyor.
Ölçülmüş bir koşum:
[✓] Geri yükleme tamamlandı — ölçülen RTO: 13 sn
geri yüklenen kopya: 1 tablo / 1 satır
üretim (mariadb-replica): 1 tablo / 1 satır
{"engine":"mariadb","ok":true,"seconds":13,"match":true,"cleanup":true, …}
Panelde Yedekler sayfasında motor satırında üç durumdan biri görünür:
| Rozet | Anlamı |
|---|---|
prova geçti 2 saat önce · 13 sn |
Bu yedek gerçekten geri yüklendi, ölçülen süre bu |
PROVA KALDI |
Elde geri yüklenemeyen bir yedek var — felaket gününden önce öğrenildi |
prova yapılmadı |
Yedeğiniz var ama geri yüklenip yüklenmeyeceği bilinmiyor |
Üçüncüsü bilerek görünür: bilmediğimiz bir şeyi sessizce boş bırakmak,
olmayan bir güvence hissettirir. Gecelik yedeğin ardından haftada bir
kendiliğinden koşar (DRILL_EVERY_DAYS), düşerse olay kritik seviyede
kaydedilir ve webhook'a düşer.
Yeni bir veritabanı açmak kolay; asıl mesele verinin zaten başka yerde
olması. import.sh bir dump dosyasını ya da uzaktaki canlı bir kaynağı
içeri alır — ve yanlış şeyi yapmayı reddeder:
./scripts/import.sh mariadb dump.sql.gz # yerel dosya
./scripts/import.sh postgresql --kaynak postgres://… # uzak canlı kaynak
./scripts/import.sh mariadb dump.sql.gz --kuru # ne olacağını göster, yazma| Durum | Davranış |
|---|---|
| Hedef boş değil | Reddeder ve ne bulduğunu söyler ("1 şema, 1 tablo, 16 KB") |
--uzerine-yaz verildi |
Önce güvenlik yedeği alır, dosya adını yazar |
| Dosya başka motorun dump'ı | Reddeder — biçimi tanır, doğru komutu yazar |
| Motor kapalı | Reddeder, nasıl açılacağını söyler |
Desteklenen biçimler: MariaDB/MySQL .sql(.gz), PostgreSQL .sql(.gz) ve
-Fc arşivi, MongoDB .archive(.gz), Redis .rdb(.gz), SQL Server .bak.
Günlük yedek saati ve saklama süresi bu sayfadan ayarlanır; açıp kapatmak tek düğme. Zamanlayıcı controller'ın içinde çalışır — host'ta root yetkisi, cron kurulumu ya da systemd birimi gerektirmez. Son koşumun sonucu (başarılıysa ne zaman, başarısızsa sebebiyle birlikte) ve sıradakinin zamanı aynı yerde yazar.
Bu, ölçülmüş bir arızanın sonucudur. Önceden
install.shyalnızstate/crontabdosyasını üretiyor ve "yüklemek içincrontab state/crontab" diyordu. Kimse yapmıyordu: test sunucusundacrontab -l | grep backupsıfır satır,backups/altındaki klasörler boştu. Yani yedek alındığı sanılırken hiç alınmıyordu — bir yedekleme sisteminin verebileceği en kötü sonuç../stack.sh doctorartık zamanlama kapalıysa bunu açıkça söylüyor.
Saklama süresinden eski yedekler temizlik turunda silinir, ama her motorun en yeni birkaç kopyası yaşı ne olursa olsun korunur: kapalı kalmış bir motorun yedeği yenilenmediği için tarih eşiğini geçiyor ve eski sürüm onun son kurtarma noktasını da siliyordu.
Her motor satırında Yedek al düğmesi var (aynısı yönetim panelindeki kartlarda da duruyor). Elle alınan yedek gecelik turu iptal etmez, ek bir kurtarma noktası oluşturur. Motor kapalıyken düğme tıklanmaz: döküm araçları veritabanına bağlanır, kapalı motorda yapacakları bir şey yoktur.
Listede her dosyanın tarihi, boyutu ve kaynağı yazar: elle, zamanlı
ya da dış (host cron'u veya komut satırı). Kaynak dosya adından tahmin
edilmez — controller kendi başlattığı koşumdan sonra oluşan dosyaları
deftere yazar, deftere girmemiş dosya dıştır.
Artık panelden yapılabiliyor. Motorun satırındaki Son yedeğe dön düğmesi en yeni kopyaya döner; belirli bir güne dönmek için Yedekleri göster ile dosya listesini açıp o satırdaki Bu yedeğe dön düğmesini kullanırsınız. "Son yedek" her zaman istenen yedek değildir — veriyi bozan işlem dün öğlen olmuşsa dönülecek yer ondan önceki kopyadır.
Düğme işi hemen başlatmaz. Açılan onay penceresi ne olacağını yazar —
mevcut veriler silinir, veritabanı o dosyadaki hâline döner, o tarihten
sonra yazılan her şey kaybolur — ve dönülecek dosyanın adını, tarihini,
yaşını, boyutunu gösterir. Devam etmek için motorun adını elinizle
yazmanız gerekir; Geri Yükle düğmesi doğru yazılana kadar kapalıdır.
Terminalde evet yazarak verdiğiniz onayın panel karşılığı budur: yan yana
duran düğmeler arasından yanlışına basarak yapılabilecek bir işlem değil.
Dosya, veriye dokunulmadan önce doğrulanır. Bozuk ya da yarım bir yedekle başlanan geri yükleme veriyi geri getirmez, yalnızca yok eder.
Otomatik geri yükleme beş motorda vardır: MariaDB, PostgreSQL, MongoDB, Redis, SQL Server. Diğerlerinin yedeği alınır ama geri dönüş motora özgü elle bir işlemdir; hangisinde ne yapılacağı docs/BACKUP.md içinde yazıyor.
Panel ne yapıyorsa aynısı; ikisi de aynı scripts/backup.shi çağırır.
./stack.sh backup # aktif motorlar (kapalı olanlar atlanır)
./stack.sh backup mariadb # tek motor
./scripts/backup.sh list # yedekleri listele
./stack.sh restore mariadb backups/mariadb/full/mariadb_full_20260901.sql.gzCron'dan koşuyorsanız izinlere dikkat.
state/velogs/altına iki ayrı kimlik yazar: controller container'ın içinde root olarak (docker soketine erişmek zorunda), siz ve cron ise sunucudaki yönetici olarak. Kim önce yazarsa dosya onun olur; root'un açtığı0644/root:rootbir dosyaya yönetici bir daha yazamaz. Sonucu sessizdir: gece işleri "Kilit dosyası açılamadı" ile düşer, panel kendi yolundan çalışmayı sürdürdüğü için hata görünmez. Kurulum bunustate/velogs/'u setgid (2775) yaparak veumask 0002ile çözüyor. Eski bir kurulumda:./stack.sh doctor # yazamadığınız dosyaları sahibiyle listeler sudo ./stack.sh doctor --duzelt
doctordosyanın moduna değil, gerçekten yazılabilir olup olmadığına bakar — mod doğru görünüp grup üyeliği eksikken de yazamazsınız.
Host cron'unu tercih ederseniz state/crontab hâlâ üretiliyor; ikisi birlikte
koşarsa backup.sh kendi kilidiyle çakışmayı önler. Aynı kilit geri yüklemede
de tutulur — 02:00 turu, yarım geri yüklenmiş bir veritabanını "geçerli yedek"
diye döküp uzağa senkronlamasın diye.
Ayrıntı ve motor başına yöntemler: docs/BACKUP.md. Uzak depo (Google Drive / S3 / SFTP): docs/GOOGLE-DRIVE.md.
"Dünkü yedeğe dön" çoğu zaman istenen şey değildir: veriyi bozan UPDATE
dün öğlen çalıştıysa, dönülecek yer o andan bir dakika öncesidir. Tam
yedek + o andan sonraki WAL/binlog kayıtları bunu mümkün kılar.
./scripts/pitr.sh durum # ne kadar geriye dönebilirim?
./scripts/pitr.sh kur postgresql # arşivlemeyi aç
./scripts/pitr.sh taban postgresql # taban yedeği al
./scripts/pitr.sh don mariadb "2026-09-02 15:30:00" --provaPostgreSQL WAL'ı archive_command ile kendi arşivler. MariaDB'de böyle
bir mekanizma yoktur: binlog arşive yalnız pitr.sh arsivle çalışınca düşer.
O satır olmadan durum yine bir pencere yazar, ama pencerenin üst sınırı
en son elle arşivlenen ana çakılı kalır — ve bunu öğrendiğiniz gün, kurtarmaya
muhtaç olduğunuz gündür.
scripts/crontab.template (dolayısıyla install.sh'ın ürettiği
state/crontab) bunu zamanlıyor:
*/15 * * * * scripts/pitr.sh arsivle # RPO üst sınırı: 15 dakika
0 1 * * * scripts/pitr.sh taban # WAL tek başına veri değildir
15 3 * * * scripts/pitr.sh temizle # arşiv sonsuza kadar büyümesinMotor adı vermeden çağırınca PITR'li motorların hepsinde çalışır; kapalı motor atlanır (çıkış 3) ve alarm üretmez — her sabah alarm veren bir cron, bakılmayan bir crondur. Motorları crontab'a tek tek yazmamamızın sebebi de bu: yığına üçüncü bir PITR motoru eklendiği gün sessizce arşivsiz kalırdı.
--prova üretime dokunmaz: tek kullanımlık bir kopyada dener. Pencere
tahmin edilmiyor, arşivden hesaplanıyor — en eski kullanılabilir taban
ile arşivdeki son kayıt arası; arşivde boşluk varsa üst sınır boşluktan
öncesine çekiliyor. Aralık dışına dönme denemesi reddediliyor.
Ölçülmüş kanıt (sunucuda, gerçek MariaDB):
T1'de A satırı yazıldı · T2'de B satırı yazıldı · aradaki bir ana dönüldü
[GEÇTİ] A-VAR-B-YOK — 15:30:00 anına dönüldü, kopyada yalnız A var (13 sn)
PostgreSQL ve MariaDB destekleniyor. Diğerlerinde neden desteklenmediği yazılı: Redis'in AOF'unda zaman damgası yok ("14:32'ye dön" ifade edilemez), MSSQL işlem günlüğü yedeği ister, MongoDB oplog ile mümkün ama bu turda yapılmadı. Ayrıntı: docs/PITR.md.
Yedekler uzak depoya (Google Drive / S3 / SFTP) gönderiliyorsa şifresiz
gitmemeli: o hesabı ele geçiren biri bütün veriyi okur. .env'de bir anahtar
verirseniz yedekler openssl aes-256-cbc + PBKDF2 (600.000 tur) ile
şifrelenir.
Anahtarı kaybederseniz yedekler AÇILAMAZ. Anahtar, yedeklerden ayrı bir yerde saklanmalı — aynı diskte tutmak, kilidi kapının üstünde bırakmaktır.
Geriye uyumlu: şifresiz eski yedekler çalışmaya devam eder, listeleme ve
geri yükleme ikisini de tanır. Şifreleme açıkken uzak depoya şifresiz dosya
gönderilmez. openssl enc bütünlük etiketi (AEAD) taşımaz — gizlilik sağlar,
kurcalanmaya karşı imza sağlamaz; bu bilinerek seçildi ve
docs/BACKUP.md'de yazılı.
Kurtarma provasının ikizi. "Yüksek erişilebilirlik var" demek yerine "geçen hafta 6 saniyede devrettik ve tek satır kaybetmedik" demek:
./scripts/failover-drill.sh mariadb --onaylaGerçek bir devir yapar ve uygulamanın gördüğü adresten yazma yeniden mümkün olana kadar geçen süreyi ölçer — container'ın içinden değil, gateway portundan. Devir öncesi commit edilen kanıt satırının kaybolmadığını doğrular. Panelden de başlatılabilir ama gövdede açık onay ister: gerçek bir kesinti oluşur, yanlışlıkla tıklanacak bir düğme olamaz.
Sil-yaz döngüsü tabloları şişirir: PostgreSQL'de autovacuum yetişemediğinde ölü satırlar birikir, InnoDB'de silinen satırların yeri geri verilmez. Disk sessizce dolar.
./scripts/maintenance.sh durum # ölç, hiçbir şey değiştirme
./scripts/maintenance.sh bakim postgresql # güvenli: tabloyu KİLİTLEMEZ
./scripts/maintenance.sh bakim postgresql --agresif --onayla # yeri geri verir, KİLİTLERAgresif bakımın kilit süresi tahmin ediliyor ve tahmin kendi kendini kalibre ediyor: her bakımdan sonra gerçekleşen hız ölçülüp kaydediliyor (ölçülen: 47 MB/sn). Kullanıcı o sayıya bakıp kesintiyi kabul edip etmeyeceğine karar veriyor. Panel yalnız güvenli bakımı sunar.
"Veritabanım yavaş" herkesin derdi ama kimse EXPLAIN okumak istemiyor. Bu
araç en pahalı sorguları bulur ve mümkün olduğunda ne yapılacağını söyler:
./stack.sh sorgu kur postgresql # ölçümü aç (yeniden başlatma gerekir, söyler)
./stack.sh sorgu durum # en pahalı sorgular
./stack.sh sorgu oneri postgresql # indeks / kullanılmayan indeks önerileriSıralama toplam süreye göre, ortalamaya göre değil — ve bu karar ölçülerek verildi. Gerçek koşumdan:
| sorgu | çağrı | toplam | ortalama | sıra |
|---|---|---|---|---|
| sık çağrılan | 400 | 56.7 ms | 0.142 ms | 2 |
| nadir ama ağır | 2 | 14.2 ms | 7.1 ms | 3 |
Ortalamaya göre sıralansaydı ikinci sorgu 50 kat üstte çıkardı; oysa sunucunun CPU'sundan 4 kat fazlasını yiyen birincisi. Yanlış sorguyu optimize etmek, hiçbir şey yapmamaktan pahalıdır.
Öneriler uygulanmaz, gösterilir: indeks eklemek yazma yolunu yavaşlatır ve bu kararı veritabanının sahibi vermeli. Gizlilik: sorgu metinleri veri içerebilir; PostgreSQL parametreleri zaten maskeler, MariaDB slow log ham sorgu yazdığı için araç sabitleri maskeler ve bunu söyler. Ayrıntı: docs/SLOWLOG.md.
Açtığınız veritabanlarının nasıl çalıştığını grafiklerle gösterir. Kurulum ya da yapılandırma gerektirmez:
./stack.sh enable monitoring # ya da panelden "İzleme" kartındaki Aktif Et
./stack.sh panel monitoring # adresi yazarMotor başına hazır panolar gelir: kaç bağlantı var, saniyede kaç işlem düşüyor, önbellek işe yarıyor mu, yedek kopya geride mi. Her panelin altında ne anlama geldiği ve ne zaman endişelenmeniz gerektiği yazar — veritabanı yönetmeyi bilmeden de okunabilsin diye.
Genel Bakış panosu bu ürün için ayrıca önemli: yığın belleği otomatik hesaplayıp dağıtıyor, bu panoda her container'ın gerçekte ne kadar kullandığını ayrılan limitle yan yana görürsünüz — yani hesabın doğru olup olmadığını gözünüzle doğrularsınız.
Hedef listesi elle yazılmaz: bir motoru açıp kapattığınızda liste kendiliğinden güncellenir. Kapalı motor listede olmadığı için "erişilemiyor" uyarısı da yağmaz — kapalı olmak arıza değildir.
Kapalıyken hiçbir container çalışmaz. Açıkken ~830 MB RAM ister; sunucuda yer yoksa diğer motorlar gibi açılmaz ve sebebini söyler.
Ayrıntı: docs/MONITORING.md.
Panelde ilgili kartın altındaki Replika kur düğmesi, ya da:
./stack.sh replica on postgresql| Motor | Yöntem | Kesinti |
|---|---|---|
| PostgreSQL | streaming replication (pg_basebackup) |
yok |
| MariaDB | GTID tabanlı asenkron replikasyon | yok |
| Redis | replicaof |
yok |
| MongoDB | replica set (rs0) | var — primary yeniden başlar |
| Cassandra / Kafka / Elasticsearch | motorun kendi kümeleme mantığı | — |
Ayrıntı: docs/REPLICATION.md
Yedek kopyayı kurduktan sonra tek bir düğme daha:
./stack.sh failover on postgresqlSistem ana kopyayı 10 saniyede bir yoklar. Üst üste 3 kez yanıt alamazsa:
- Eski ana kopyayı durdurur — iki kopyanın aynı anda yazı kabul edip verilerin ayrışmasını (split-brain) önlemek için zorunlu adım
- Yedeği ana kopya yapar (yazmaya açar)
- Yönlendirmeyi günceller — uygulamanız aynı adrese bağlanmaya devam eder
- Olayı kaydeder ve tanımlıysa webhook bildirimi gönderir
Bunun çalışabilmesi için tüm veritabanı portları gateway üzerinden geçer:
uygulama → gateway:5432 → (o an ana kopya olan neyse)
Uygulamanız doğrudan container'a bağlansaydı, devirden sonra ölü sunucuya bağlanmaya devam ederdi — yani devir otomatik olmazdı.
./stack.sh failover status # durum ve devir geçmişi
./stack.sh failover now <motor> # elle devir (bakım/test)
./stack.sh failover rebuild <m> # eski kopyayı yedek olarak geri al
./stack.sh events # olay akışı| Motor | Devir yöntemi |
|---|---|
| PostgreSQL | pg_ctl promote |
| MariaDB | relay log boşaltılır → RESET SLAVE ALL → yazmaya açılır |
| Redis | REPLICAOF NO ONE |
| MongoDB | replica set kendi seçimini yapar (arbiter ile 3 oy) |
Ayrıntı, test yöntemi ve sınırlar: docs/FAILOVER.md
Aynı ürün, aynı mantık: "aktif et" = StatefulSet'i 0'dan 1 replikaya ölçeklemek. Manifestler katalogdan üretilir, elle yazılmaz.
python3 scripts/gen-k8s.py --with-secrets
kubectl apply -k k8s/baseKontrol servisinin K8s'teki tek yetkisi StatefulSet'leri okumak ve ölçeklemek/boyutlandırmaktır — Docker kurulumundaki docker soketi erişiminden (host'ta tam yetki) belirgin şekilde dardır.
Ayrıntı: docs/KUBERNETES.md
databases-stack/
├── install.sh Tek komutluk kurulum
├── stack.sh Günlük kullanım CLI'ı
├── catalog.json ⭐ Motor kataloğu — TEK YETKİ KAYNAĞI
├── docker-compose.yml Tüm motorlar, profil tabanlı
├── controller/ Kontrol düzlemi (aktivasyon + boyutlandırma)
├── gateway/ nginx: TLS, auth, reverse proxy, dashboard
├── config/ Motor konfigürasyonları (my.cnf vb.)
├── scripts/ backup, sync, kullanıcı, sertifika, replikasyon, devir
├── overrides/ Duruma göre yüklenen compose parçaları
├── k8s/ Üretilmiş Kubernetes manifestleri
└── docs/ Ayrıntılı belgeler
Yeni bir veritabanı eklemek = catalog.jsona bir kayıt + docker-compose.ymle
aynı profile sahip servisler. ./scripts/check-catalog.sh ikisinin ayrışmadığını
doğrular; ./stack.sh doctor bunu otomatik çağırır.
- Panel portları host'a açılmaz; tek giriş kapısı TLS + basic auth arkasındadır
- Her motorun ayrı parolası vardır (tek sızıntı 12 motoru açmaz)
- Kontrol servisinin portu yoktur; yalnız gateway'den, paylaşılan token ile erişilir
- Parolalar hiçbir betikte komut satırına yazılmaz (
psçıktısında görünmez) ./stack.sh app-userile uygulamanız içinDROPyetkisi olmayan kullanıcı
⚠️ Bu ürün iç ağ / VPN arkası kullanım için tasarlandı. Veritabanı portlarını internete açmayın.
Ayrıntı ve sertleştirme adımları: docs/SECURITY.md
Bu ürünün iddiaları ölçülüyor. Depoda iki katman var:
./stack.sh selftest # docker gerektirmez — boyutlandırma, API, nginx, betikler
./stack.sh e2e # ÇALIŞAN kuruluma karşı — on dört paket
./stack.sh e2e --hepsi # Kubernetes dahilselftest docker'ı taklit eder; hızlıdır ve mantık hatalarını yakalar. Ama
taklit edilen bir docker, gerçek bir container'ın yapmadığını yapmaz. Bu
yüzden ikinci katman var ve çalışan sisteme soruyor: veri yazılıp geri
okunuyor mu, ana kopya öldürülünce uygulama aynı adrese yazmaya devam
ediyor mu, alınan yedek gerçekten geri yükleniyor mu, her panonun her sorgusu
veri döndürüyor mu.
| Paket | Ne kanıtlar |
|---|---|
security |
Panel/API/metrik parolası, tek oturum, çapraz-site koruması |
sizing |
Hesaplanan limit gerçekten uygulanmış mı, bütçe dolunca reddediyor mu |
replication |
Replika akıyor, salt-okunur, kapatınca kalıntı bırakmıyor |
failover |
Ölüm → devir → aynı adresten yazma → veri kaybı yok |
backup |
Yedek al → veriyi sil → geri yükle → veri geri geldi |
monitoring |
Hedefler, metrikler, her panonun her sorgusu |
lifecycle |
Aç/kapat/aç — kapatınca veri silinmiyor |
k8s |
Ayarlar pod içinde uygulanıyor mu (k3s açar ve sonunda kapatır) |
Sonuç türü üç değil dört:
| Anlamı | Çıkış koduna etkisi | |
|---|---|---|
GEÇTİ / BAŞARISIZ |
Ölçtük | — / 1 |
ATLANDI |
Ön koşul yok (motor kapalı) — meşru | yok |
ÖLÇÜLEMEDİ |
Sorgu düştü, docker cevap vermedi | 1 |
Son satır kasıtlı: "bilmeyi başaramadık" ile "iyi durumda" aynı şey değildir. Aynı sebeple hiçbir kontrol çalışmadıysa çıkış 2, ölçülenden çok atlama varsa çıkış 3 — "0/0 geçti" diyerek yeşil görünen bir test, hiç olmayan testten kötüdür.
Bu paket yazıldığı gün ürünün kendisinde altı gerçek hata buldu; dördü ancak sıfırdan kurulmuş, gerçekten çalışan bir sistemde görülebilirdi. Her biri için teste kalıcı bir koruma eklendi ve korumanın bozuk hâli gerçekten yakaladığı ayrıca sınandı.
Otomatik devir süreç düzeyindeki arızaları karşılar: veritabanının çökmesi, kilitlenmesi, OOM ile öldürülmesi, veri dosyasının bozulması. Bunlar pratikte en sık yaşanan arızalardır ve sistem bunları ~30 saniyede kendisi kapatır.
Aynı ürün, ikinci bir makine eklendiğinde host arızasını da karşılar: yedek kopyayı uzak bir Docker host'unda ya da Kubernetes'te node anti-affinity ile çalıştırın (bkz. docs/FAILOVER.md). Tek makinede çalıştırdığınız sürece, makinenin tamamı düşerse iki kopya da düşer — bu bir eksiklik değil, tek makine olmanın tanımıdır.
Bilmeniz gerekenler:
- Replikasyon asenkrondur. Devirde, ana kopyanın göndermeye yetişemediği son işlemler kaybolabilir (tipik olarak milisaniyeler). Sıfır kayıp için senkron replikasyon nasıl açılır: docs/FAILOVER.md
- Devir yedeğin yerini tutmaz. Yanlışlıkla silinen veri replikaya da anında yansır. Düzenli yedek şart → docs/BACKUP.md
- Veritabanı portları gateway üzerinden geçtiği için motorlar istemcinin
gerçek IP'sini değil gateway'in IP'sini görür — host tabanlı yetkilendirme
(
user@'192.168.1.5') kullanıyorsanız buna göre ayarlayın. - Neo4j Community'de çevrimiçi yedek yoktur — yedek almak veritabanını durdurur.
- Kafka yedeklenmez (log'dur, veritabanı değil);
replication.factorkullanın. - MongoDB replica set açmak ana kopyayı yeniden başlatır (kısa kesinti).
Bu proje MIT'tir ve motorları yeniden dağıtmaz — resmi kayıt defterlerinden çeker. Yani her motorun lisansı doğrudan sizinle motorun sahibi arasındadır. Panelde her kartın altında lisans görünür, kısıtlı olanlarda aktivasyon onayında uyarı çıkar.
./stack.sh licensesÇoğu motor iç kullanımda sorunsuzdur. İki başlık dikkat ister:
- SQL Server varsayılan olarak ücretsiz Express sürümüyle gelir ve
üretimde de kullanılabilir; sınırı veritabanı başına 10 GB ve ~1.4 GB tampon
havuzudur. Tüm özellikler gerekiyorsa
MSSQL_PID=Developerücretsizdir ama yalnız geliştirme/test içindir; üretimde Standard/Enterprise lisansı ister. - MongoDB (SSPL), Elasticsearch (ELv2/SSPL), Redis, Neo4j, MinIO (AGPL) copyleft lisanslıdır. Kendi uygulamanız için kullanmak serbesttir; bu motorları üçüncü taraflara yönetilen servis olarak satarsanız kaynak açma yükümlülüğü doğabilir.
İzleme modülü için: Prometheus ve node-exporter Apache-2.0 (kısıtsız), Grafana OSS ise AGPL-3.0'dır. Grafana'yı olduğu gibi çalıştırmak serbesttir; AGPL yükümlülüğü ancak Grafana'yı DEĞİŞTİRİP ağ üzerinden üçüncü taraflara sunarsanız doğar. İç ağda kendi panolarınızı kullanmak bu kapsama girmez.
Copyleft istemiyorsanız imajlar değiştirilebilir — Redis yerine BSD-3 lisanslı Valkey birebir geçer:
REDIS_IMAGE=valkey/valkey:8-alpine # .envAynı mekanizma kapalı ağda kendi registry aynanız için de kullanılır. Ayrıntı: docs/LICENSING.md
MIT — LICENSE





