PushizoDocs
OperasyonRunbook'lar

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şkenNeredeNot
CELL_IDher hücreeu, tr … (küçük harf/rakam/tire)
CELLSher hücre (aynı değer)JSON kayıt defteri: id, name, api_url, dashboard_url, internal_url
CONTROL_PLANE_MODEyalnızca bir hücrede true
CONTROL_PLANE_URL, CONTROL_PLANE_DASHBOARD_URLdiğer hücrelerkontrol düzlemi API iç adresi / dashboard adresi
CELL_INTERNAL_SECRETher hücre (aynı değer, ≥ 32 karakter)gizli: Secret / ExternalSecret; loglanmaz
CUSTOM_DOMAIN_CNAME_TARGETher hücre (hücreye özel)müşteri CNAME hedefi, ör. edge.eu.example.com
CUSTOM_DOMAIN_ASK_SECRETher hücregizli; 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

  1. Bölgede veri katmanını kurun (yönetilen Postgres 16, Redis 7, ClickHouse, S3 bucket); uygulama rolü NOSUPERUSER NOBYPASSRLS (docs/runbooks/postgres-roles.md).
  2. Kasa belgesi pushizo/production-<hücre>: DATABASE_URL, REDIS_URL, MASTER_KEY (hücreye özel, yeni üretin), SESSION_SECRET, S3_*, CLICKHOUSE_PASSWORD …
  3. values-cell-<hücre>.yaml: cell.id, controlPlaneUrl, kayıt defteri (yeni hücre dahil), networkPolicy.peers, customDomains.cnameTarget, ingress ana makine adları.
  4. Tüm mevcut hücrelerin values dosyasında cell.registry listesine yeni hücreyi ve networkPolicy.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ılar bilinmeyen hücre ile 401 alır).
  5. helm upgrade --install pushizo-<hücre> … → migration hook'u şemayı kurar. ClickHouse: npm run ch:migrate.
  6. Doğrulama: kontrol düzleminde GET /internal/v1/cells yeni bölgeyi listeler; panelde yeni org oluştururken bölge seçilebilir; yeni hücrede oluşturulan org kontrol düzleminde org_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`) |

Bu sayfada