Leyla foto yerləşdirdi və səhifəni yenilədi — foto yoxdur. Bir-iki saniyə sonra o, özü göründü. Bu, tətbiqin xətası deyil, əsas serverdən bir qədər geri qalan verilənlər bazası replikasının salamıdır. Tətbiq serverlərini çoxaltmağı bacarırıq, oxumaları keşləyirik, amma baza hələ də birdir. Bazanı necə miqyaslamaq lazım olduğunu öyrənək: hansı ardıcıllıqla hərəkət etmək, oxuma replikaları necə işləyir və niyə geri qalır, partisiyalama şardinqdən nə ilə fərqlənir, şard açarı necə seçilir və PgBouncer nə üçün lazımdır.
Əvvəl sadə addımlar: bazanın miqyaslanma pilləkəni
Hər növbəti pillə əvvəlkindən mürəkkəb və bahalıdır, ona görə aşağı pillə tükənəndə yuxarı qalxılır:
- Sorğuları ölçmək və düzəltmək.
EXPLAIN ANALYZE,pg_stat_statements, indekslər («İndekslər necə işləyir: B-tree və CREATE INDEX» dərsi), N+1-in aradan qaldırılması. - Keş (əvvəlki dərs) oxumaların böyük hissəsini götürür.
- Daha güclü server (şaquli miqyaslanma): sadədir və çox vaxt kifayətdir.
- Oxuma replikaları və bağlantı hovuzu.
- Böyük cədvəllərin partisiyalanması.
- Şardinq — ən sonda.
Prowid Photos rəqəmləri: foto metadata ilə ildə təxminən 36 GB (birinci dərsdəki hesab), bəyənmə və şərhlərlə təxminən 100 GB. İstifadəçi gündə daha təxminən 5 bəyənmə və şərh yazırsa, bu, orta hesabla saniyədə ≈ 60, pikdə ≈ 200 yazıdır. Bir PostgreSQL bunu böyük ehtiyatla daşıyır: şardinq bu gün lazım deyil, replikalar isə dözümlülük və oxumaların yükünü azaltmaq üçün faydalıdır. Amma 100 dəfə artımı təsəvvür edin:
| Bu gün | Artım ×100 | |
|---|---|---|
| Aktiv istifadəçilər | 1 mln | 100 mln |
| API piki | 350 RPS | 35 000 RPS |
| İllik metadata | ≈ 100 GB | ≈ 10 TB |
| Pikdə yazı | ≈ 200/s | ≈ 20 000/s |
Burada bir server yazı və həcm üzrə həddə çatır, şardinq isə ciddi söhbətə çevrilir.
Oxuma replikaları
Prowid Photos-da oxumalar yazılardan yüz dəfə çoxdur — replikalar üçün ideal haldır. Primary (lider) bütün yazıları qəbul edir. Replikalar (followers) dəyişiklik axınını alır və oxumalara cavab verir. PostgreSQL-də bu, öncədən yazma jurnalının (WAL) axınla ötürülməsidir (streaming replication). Replikasiya iki cür olur:
- Asinxron (susmaya görə): primary yazını replikanı gözləmədən təsdiqləyir. Sürətlidir, amma replika geri qalır, primary çökəndə isə son təsdiqlənmiş yazılar itə bilər.
- Sinxron (
synchronous_standby_names): primary replikanın təsdiqini gözləyir. Məlumat itmir, amma yazı yavaşdır, çökmüş replika isə yazını dayandıra bilər.
Replikalar oxumanı miqyaslayır, yazını və həcmi yox: hər biri tam nüsxə saxlayır. Replika ehtiyat nüsxə də deyil: DROP TABLE replikalara dərhal yayılır. Tətbiqdə iki bağlantı hovuzu kifayətdir (Prisma-nın @prisma/extension-read-replicas genişlənməsi eyni işi $primary() və $replica() ilə görür):
import pg from 'pg';
const { Pool } = pg;
const primary = new Pool({ connectionString: process.env.PRIMARY_URL });
const replica = new Pool({ connectionString: process.env.REPLICA_URL });
export const db = {
write: (sql, params) => primary.query(sql, params),
read: (sql, params) => replica.query(sql, params),
};