Hücreler (veri yerleşimi EU/TR): kurulum, bölge taşıma, temizlik, DR
ADR-0032. Hücre, kendi Postgres/Redis/ClickHouse/S3'ü ve API/worker dağıtımı olan tam bir Pushizo kurulumudur. Bir hücre (CONTROL_PLANE_MODE=true) aynı zamanda kontrol düzlemidir: org → hücre eşlemesi (org_regions),…
docs/adr/0032-hucre-mimarisi-white-label-bayi.md. Hücre, kendi Postgres/Redis/ClickHouse/S3'ü ve API/worker
dağıtımı olan tam bir Pushizo kurulumudur. Bir hücre (CONTROL_PLANE_MODE=true) aynı zamanda kontrol düzlemidir:
org → hücre eşlemesi (org_regions), giriş yönlendirmesi ve özel alan adlarının global kaydı (custom_domain_registry)
orada tutulur. CELL_ID boşsa kurulum tek hücrelidir ve bu runbook'un hiçbir adımı gerekmez.
Yapılandırma özeti
| Değişken | Nerede | Not |
|---|---|---|
CELL_ID | her hücre | eu, tr … (küçük harf/rakam/tire) |
CELLS | her hücre (aynı değer) | JSON kayıt defteri: id, name, api_url, dashboard_url, internal_url |
CONTROL_PLANE_MODE | yalnızca bir hücrede true | |
CONTROL_PLANE_URL, CONTROL_PLANE_DASHBOARD_URL | diğer hücreler | kontrol düzlemi API iç adresi / dashboard adresi |
CELL_INTERNAL_SECRET | her hücre (aynı değer, ≥ 32 karakter) | gizli: Secret / ExternalSecret; loglanmaz |
CUSTOM_DOMAIN_CNAME_TARGET | her hücre (hücreye özel) | müşteri CNAME hedefi, ör. edge.eu.example.com |
CUSTOM_DOMAIN_ASK_SECRET | her hücre | gizli; uç katman (Caddy) ask çağrısı |
Helm: her hücre ayrı ad alanında ayrı release — infra/k8s/pushizo/values-cell-eu.yaml (kontrol düzlemi) ve
values-cell-tr.yaml. cell.* değerleri ConfigMap'e yazılır; CELL_INTERNAL_SECRET ve CUSTOM_DOMAIN_ASK_SECRET
externalSecrets.data ile ortak kasa belgesinden (pushizo/cells) okunur. validate.yaml tutarsız yapılandırmayı
(kayıt defterinde olmayan cell.id, kontrol düzlemi adresi eksik, birden çok hücrede gizli anahtar eşlemesi yok, ağ
politikası açıkken diğer hücre için cell.networkPolicy.peers kaydı yok) reddeder.
Ağ: API pod'larının çıkışı yalnızca diğer hücrelerin API iç adresine (cell.networkPolicy.peers, 443) açılır; başka
hücrenin Postgres/Redis/ClickHouse/S3'üne kural yoktur. Böylece EU verisi ağ katmanında da TR veri katmanına
yazılamaz. Uygulama katmanında CellGuard, başka hücrenin org'una gelen isteği 421 HS_WRONG_CELL ile reddeder
(kanıt testi: apps/api/src/__tests__/phase21.int.test.ts, "EU verisi TR veritabanına yazılmaz").
Yeni hücre ekleme
- Bölgede veri katmanını kurun (yönetilen Postgres 16, Redis 7, ClickHouse, S3 bucket); uygulama rolü
NOSUPERUSER NOBYPASSRLS(docs/runbooks/postgres-roles.md). - Kasa belgesi
pushizo/production-<hücre>:DATABASE_URL,REDIS_URL,MASTER_KEY(hücreye özel, yeni üretin),SESSION_SECRET,S3_*,CLICKHOUSE_PASSWORD… values-cell-<hücre>.yaml:cell.id,controlPlaneUrl, kayıt defteri (yeni hücre dahil),networkPolicy.peers,customDomains.cnameTarget, ingress ana makine adları.- Tüm mevcut hücrelerin values dosyasında
cell.registrylistesine yeni hücreyi venetworkPolicy.peers'a iç adresini ekleyin; önce mevcut hücreleri, sonra yeni hücreyi dağıtın (kayıt defteri her yerde aynı olmalı, aksi halde imzalı çağrılarbilinmeyen hücreile 401 alır). helm upgrade --install pushizo-<hücre> …→ migration hook'u şemayı kurar. ClickHouse:npm run ch:migrate.- Doğrulama: kontrol düzleminde
GET /internal/v1/cellsyeni bölgeyi listeler; panelde yeni org oluştururken bölge seçilebilir; yeni hücrede oluşturulan org kontrol düzlemindeorg_regions'ta görünür.
Bölge taşıma (platform admin işlemi)
Org'u bir hücreden diğerine kopyalar ve doğrular; kaynak ayrı adımda temizlenir. Bayi hiyerarşisindeki (bayi veya alt org) bir org tek başına taşınamaz.
Ön koşullar: iki hücre aynı migration sürümünde; worker'lar çalışıyor (ClickHouse outbox boşalır); bakım penceresi
duyuruldu (taşıma süresince org askıda, istekler HS_ORG_SUSPENDED).
# Kaynak hücrenin ortamıyla (DATABASE_URL, MASTER_KEY, S3_*, CLICKHOUSE_* kaynak hücreyi gösterir):
export CELL_DATABASE_URLS='{"tr":"<hedef Postgres bağlantısı>"}' # veya REGION_MOVE_TARGET_DATABASE_URL
export CONTROL_PLANE_DATABASE_URL='<kontrol düzlemi Postgres>' # kaynak kontrol düzlemiyse gerekmez
export REGION_MOVE_TARGET_MASTER_KEY='<hedef hücre MASTER_KEY>' # farklıysa şifreli değerler yeniden sarılır
export REGION_MOVE_TARGET_S3_ENDPOINT=… REGION_MOVE_TARGET_S3_BUCKET=… REGION_MOVE_TARGET_S3_REGION=… \
REGION_MOVE_TARGET_S3_ACCESS_KEY=… REGION_MOVE_TARGET_S3_SECRET_KEY=…
export REGION_MOVE_TARGET_CLICKHOUSE_URL=… REGION_MOVE_TARGET_CLICKHOUSE_DATABASE=… \
REGION_MOVE_TARGET_CLICKHOUSE_USER=… REGION_MOVE_TARGET_CLICKHOUSE_PASSWORD=…
npm run region:move -- --org=org_… --to=tr --report=tasima-org_….json
```text
Akış ve kapsam:
1. Kaynak org askıya alınır (`suspended_reason = region_move`), `--settle-seconds` (varsayılan 5) beklenir.
2. ClickHouse açıksa org app'lerinin `analytics_outbox` satırları boşalana kadar beklenir (en çok 60 sn; boşalmazsa
`npm run ch:drain --workspace @pushizo/db` ile aktarın ve yeniden deneyin).
3. **Postgres**: org kapsamlı tüm tablolar tek `REPEATABLE READ` anlık görüntüsünden hedefe tek transaction'da
kopyalanır; tablo başına satır sayısı + sıradan bağımsız md5 ile doğrulanır. `v1.` şifreli değerler hedef ana
anahtarla yeniden şifrelenir, `v2.` zarf değerlerinin veri anahtarları hedefe yeniden sarılır (`retired`).
4. **ClickHouse**: `deliveries`, `delivery_events`, `events`, `conversions` satırları (FINAL, ay ay, sayfalı) hedefe
kopyalanır; satır sayısı + `cityHash64` toplamı karşılaştırılır. Hedefte bu app'lerin satırı varsa (yarım kalmış önceki
taşıma) işlem iptal olur.
5. **Nesne depolama**: `org/<org>/` (dışa/içe aktarım, denetim NDJSON), `branding/<org>/` (logo) ve eski biçimli
dışa aktarım anahtarları kopyalanır, hedefte yeniden okunup sha256 ile doğrulanır. Kaynak ve hedef aynı depo/bucket
ise kopya atlanır. Logo adresi veritabanında hücreden bağımsız göreli yol olarak saklandığından hedef hücre kendi
API kökünden sunar.
6. Herhangi bir doğrulama hatasında Postgres transaction'ı geri alınır, hedefe yazılan ClickHouse satırları ve
nesneler silinir, kaynak org eski durumuna döner.
7. Başarıda hedef org eski durumunu ve `cell_id = <hedef>`'i alır; kontrol düzleminde `org_regions` ve
`custom_domain_registry` yeni hücreyi gösterir. Kaynak org askıda kalır ve istekler 421 ile hedef bölgeye yönlenir.
Hedef depo yapılandırılmadıysa `--skip-objects`, ClickHouse taşınmayacaksa `--skip-clickhouse` açıkça verilmelidir
(sessizce atlanmaz).
Taşıma sonrası:
- **Redis taşınmaz** (kota sayaçları, önbellekler, hız sınırları, nonce'lar geçicidir): hedefte bu sayaçlar sıfırdan
başlar. Kalıcı kullanım kayıtları (`usage_daily` vb.) Postgres'le birlikte taşınır.
- Hesap oturumları hesaba bağlıdır ve taşınmaz: kullanıcılar panelde hedef bölgeye oturum devriyle (`/handoff`) geçer.
- Özel alan adları: müşterinin CNAME'i yeni hücrenin `CUSTOM_DOMAIN_CNAME_TARGET`'ına güncellenmelidir; güncellenene
kadar eski hücrenin uç katmanı sertifika vermez (alan adı orada yok) — müşteriye taşıma öncesi bildirin.
- SDK'lar: bölgesel API adresi `GET /sdk/v1/apps/:appId/config` yanıtındaki `api_url`'dir; eski adrese gelen istek 421 +
`details.api_url` alır. Web, Android ve iOS SDK'ları bu adresi benimser (`init`'te açıkça özel `apiUrl` verilmediyse);
diğer köprüler ve sunucu SDK'larında bölgesel adres yapılandırmada verilmelidir.
## Kaynağı temizleme (`--purge-source`)
Yalnızca taşıma raporu incelendikten ve hedef bölgede org birkaç gün sorunsuz çalıştıktan sonra:
```bash
npm run region:move -- --org=org_… --to=tr --purge-source
```text
Kaynaktaki her satırın birincil anahtarı hedefte, her nesne hedefte aynı içerikle (sha256), ClickHouse satırları hedefte
en az kaynak kadar olmalıdır; biri eksikse **hiçbir şey silinmez**. Silme ters FK sırasıyla tek transaction'dadır;
ardından kaynak ClickHouse satırları (lightweight DELETE) ve nesneler silinir. Kaynak veritabanı yedeklerinde org verisi
yedek saklama süresi boyunca kalır (KVKK/GDPR kayıtlarına not düşün).
## DR (hücre başına)
- Her hücrenin yedekleri kendi bölgesinde tutulur (`docs/runbooks/restore.md`); yedek başka bölgeye kopyalanmaz (veri
yerleşimi). RTO/RPO hedefleri hücre başınadır (plan 10: Postgres RPO 1 sn, ClickHouse 15 dk).
- Bir hücrenin kaybı diğerlerini etkilemez; yalnızca o bölgenin org'ları kesintidedir. Kontrol düzlemi hücresi
kaybedilirse diğer hücreler mevcut org'larını sunmaya devam eder (konum önbelleği + yerel `cell_id`), ancak yeni org
oluşturma, giriş yönlendirmesi ve özel alan adı ekleme kontrol düzlemi dönene kadar 503 alır.
- Kontrol düzlemi geri yüklenince `org_regions` tutarlılığı: her hücrede
`select id, cell_id from organizations where deleted_at is null` çıktısı kontrol düzlemindeki `org_regions` ile
karşılaştırılır; eksik satırlar kontrol düzlemi veritabanına `insert into org_regions (org_id, cell_id, name, slug,
parent_org_id) … on conflict (org_id) do update` ile elle yeniden yazılır.
- Tatbikat: yılda en az iki kez her hücre için geri yükleme + bir test org'unun bölge taşıması (rapor dosyası saklanır).
## Sorun giderme
| Belirti | Neden / çözüm |
|---------|---------------|
| `HS_WRONG_CELL` (421) | Org başka hücrede; `details.api_url` / `dashboard_url` doğru adres. Taşıma sonrası beklenir |
| `/cell/v1/*` 401 | İmza: saat farkı > 60 sn (NTP), farklı `CELL_INTERNAL_SECRET`, kayıt defterinde olmayan çağıran, yeniden kullanılan nonce |
| Bölgeli org oluşturma 503 | Hedef hücrenin iç adresine erişim yok (NetworkPolicy `peers`, DNS, iç yük dengeleyici) |
| Özel alan adı `pending` kalıyor | TXT `_pushizo-challenge.<alan>` veya CNAME; worker `verify-custom-domains` işi 5 dk'da bir dener, 72 saatte `failed` |
| Özel alan adı 409 | Alan adı başka bir org'da (herhangi bir hücrede) kayıtlı (`custom_domain_registry`) |Runbook: İşlemsel sentetik izleme
Faz 14 kabulü "staging'de 7 gün hatasız sentetik izleme" bu kontrolle ölçülür. .github/workflows/synthetic.yml 15 dakikada bir scripts/synthetic.ts çalıştırır:
ClickHouse analitik deposu ve Kafka ingest
ADR-0026. İkisi de isteğe bağlıdır: ayarlanmadığında sistem Postgres + BullMQ ile eskisi gibi çalışır.