Asosiy qismga o'tish

CDC: ma'lumotlar bazasi o'zgarishlarini ushlash

11-bob 12 kod bloki 2820 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

Bir mijozning telefon qo'ng'irog'i

Dushanba. RostUy da uy egasi Chilonzorda kvartira e'lon qildi: 5 million so'm.

Seshanba. Uyni ko'rgani ikki kishi keldi, ikkalasi ham rozi. Ega narxni ko'tardi: 6 million.

Chorshanba. Uy ijaraga berildi. Ega e'lonni o'chirdi.

Payshanba ertalab sizning telefoningiz jiringlaydi. Mijoz jahlda:

— Saytingizda Chilonzorda 5 millionlik uy turibdi. Qo'ng'iroq qildim, "u allaqachon berilgan" deyishdi. Nega saytda turibdi?

Siz omboringizni ochasiz. Haqiqatan ham: e'lon bor, holati faol, narxi 5 million.

Uch xato bitta qatorda. Qanday qilib?

Har kecha ishlaydigan "ishonchli" skript

Aybdorni topish qiyin emas. Har kecha soat 2 da shu so'rov ishlaydi:

sql1 qator
SELECT * FROM elon WHERE yangilandi > '<oxirgi tekshiruv>';

Bu usul polling deyiladi — "so'rab-surishtirish". Mantiq oddiy: "oxirgi marta tekshirganimdan keyin o'zgargan qatorlarni ber".

Nega u uchala xatoni ham qildi:

Narx 5 million qolgani. Dushanba kechasi skript ishladi va 5 millionlik e'lonni ko'chirdi. To'g'ri ishladi. Lekin seshanba kuni narx 6 ga ko'tarilganda... yo'q, seshanba kechasi ham ishlashi kerak edi. Ishladi ham. Demak muammo boshqa joyda.

E'lon hali "faol" turgani. Mana asosiy sabab. Chorshanba kuni ega e'lonni o'chirdi. Endi so'rovni o'ylang: SELECT ... WHERE yangilandi > .... O'chirilgan qator jadvalda yo'q. Yo'q qatorni SELECT qanday topsin?

Javob: hech qanday. O'chirish — polling uchun ko'rinmas hodisa. E'lon omborda abadiy "faol" bo'lib qoladi.

Narx tarixi yo'qolgani. Agar ega seshanba kuni narxni ikki marta o'zgartirgan bo'lsa (5 → 5.5 → 6), kechasi ishlagan skript faqat oxirgi qiymatni ko'radi: 6. Oraliq qadam hech qachon qayd etilmaydi. "Narx qanday o'zgarib bordi?" degan savolga javob yo'q.

Bu uchtasi jim o'tadi. Xato xabari yo'q, log da qizil qator yo'q. Faqat bir kuni mijoz qo'ng'iroq qiladi.

Postgres allaqachon hammasini yozib turibdi

Endi yaxshi xabar.

Do'kondagi kassirni tasavvur qiling. U har sotuvni avval daftarga yozadi, keyin pulni kassaga soladi. Nega shunday? Chunki svet o'chsa, kassa yopilsa, daftar qoladi — ertaga hammasini tiklash mumkin.

Postgres ham aynan shunday ishlaydi. Har bir o'zgarish avval WAL ga yoziladi (Write-Ahead Log — "oldindan yozish jurnali"), keyin jadvalga qo'llanadi. Sabab bir xil: server qulasa, Postgres WAL ni o'qib o'zini tiklaydi.

Mana eng muhim jumla: bu jurnal allaqachon mavjud. Siz uni yaratmaysiz, yoqmaysiz, qo'shimcha kod yozmaysiz. U hozir ham yozilmoqda.

CDC (Change Data Capture — "o'zgarishlarni ushlash") shunchaki o'sha jurnalni o'qiydi.

Shuning uchun CDC polling dan ustun: polling jadvalning hozirgi holatiga qaraydi, CDC esa nima sodir bo'lganiga. O'chirilgan qator jadvalda yo'q, lekin jurnalda "DELETE" yozuvi turibdi. Ikki marta o'zgargan narx jadvalda bitta qiymat, jurnalda esa ikkita yozuv.

Shu bobda ikkala usulni ham ishga tushiramiz va nechta o'zgarishni ko'rganini sanaymiz.

python17 qator
diagramma(
    {
        "Postgres\n(RostUy)": (0, 1.6, 2.2, 1.1, "manba"),
        "WAL\n(tranzaksiya jurnali)": (3.0, 1.6, 2.4, 1.1, "qayta"),
        "Replikatsiya\nsloti": (6.2, 1.6, 2.2, 1.1, "qayta"),
        "Kafka\n(10-bob)": (9.2, 1.6, 2.0, 1.1, "saqlash"),
        "Polling\n(SELECT ...)": (3.0, -0.2, 2.4, 1.0, "xizmat"),
    },
    [
        ("Postgres\n(RostUy)", "WAL\n(tranzaksiya jurnali)", "har commit"),
        ("WAL\n(tranzaksiya jurnali)", "Replikatsiya\nsloti"),
        ("Replikatsiya\nsloti", "Kafka\n(10-bob)"),
        ("Postgres\n(RostUy)", "Polling\n(SELECT ...)", "har N daqiqa"),
    ],
    nom="cdc_yol.png", kenglik=13,
    izoh="Ikki yo'l: pastdagisi oson va kamchilikli, yuqoridagisi WAL ni to'g'ridan o'qiydi.",
)

Ikki yo'l: pastdagisi oson va kamchilikli, yuqoridagisi WAL ni to'g'ridan o'qiydi.

Ikki yo'l: pastdagisi oson va kamchilikli, yuqoridagisi WAL ni to'g'ridan o'qiydi.

Bazani ko'taramiz

15455-port — sizdagi boshqa Postgres larga tegmaslik uchun.

bash11 qator
docker rm -f kitob-pg > /dev/null 2>&1
docker run -d --name kitob-pg -p 15455:5432 \
  -e POSTGRES_PASSWORD=kitob -e POSTGRES_DB=rostuy \
  postgres:16 -c wal_level=logical -c max_replication_slots=4 -c max_wal_senders=4 > /dev/null
echo "konteyner ishga tushdi"

for i in $(seq 1 60); do
  docker exec kitob-pg pg_isready -U postgres > /dev/null 2>&1 && { echo "baza ${i} soniyada tayyor"; break; }
  sleep 1
done
docker exec kitob-pg psql -U postgres -d rostuy -tAc "SHOW wal_level;" | sed 's/^/wal_level: /'
Chiqish (qaytish kodi 0, 1557 ms)
konteyner ishga tushdi
baza 2 soniyada tayyor
wal_level: logical

`wal_level=logical` bo'lmasa CDC yo'q

Postgres ning standart sozlamasi replica. Bu rejimda WAL da faqat "qaysi blok o'zgardi" yoziladi — qaysi qator va qanday qiymat emas.

logical ga o'tkazish uchun serverni qayta ishga tushirish kerak va WAL hajmi ortadi. Ishlab chiqarishda buni DBA bilan kelishmasdan qilmang; lekin bu qadamsiz hech qanday CDC vositasi (Debezium ham) ishlamaydi.

Jadval va boshlang'ich ma'lumot

Haqiqiy RostUy e'lonlaridan 2000 tasini yuklaymiz. elon_id birlamchi kalit bo'lgani uchun takroriy id larni oldindan tashlaymiz — aks holda INSERT duplicate key bilan to'xtaydi.

python23 qator
python3 -c "
import psycopg2, pandas as pd

d = pd.read_csv('../datasets/ijara.csv').drop_duplicates('elon_id').head(2000)
c = psycopg2.connect(host='localhost', port=15455, user='postgres', password='kitob', dbname='rostuy')
c.autocommit = True
k = c.cursor()
k.execute('''
DROP TABLE IF EXISTS elon;
CREATE TABLE elon (
  elon_id     text PRIMARY KEY,
  tuman       text,
  narx_mln    numeric,
  xonalar     int,
  holat       text DEFAULT 'faol',
  yangilandi  timestamp DEFAULT now()
);''')
qatorlar = [(r.elon_id, r.tuman, float(r.narx_mln), int(r.xonalar)) for r in d.itertuples()]
k.executemany('INSERT INTO elon (elon_id,tuman,narx_mln,xonalar) VALUES (%s,%s,%s,%s)', qatorlar)
k.execute('SELECT count(*) FROM elon')
print('jadvaldagi e\\'lonlar:', k.fetchone()[0])
c.close()
"
Chiqish (qaytish kodi 0, 5693 ms)
jadvaldagi e'lonlar: 2000

Slot ochamiz

Bitta muammo bor va uni hal qilmasak, butun g'oya ishlamaydi.

WAL cheksiz o'smaydi. Postgres uni vaqti-vaqti bilan tozalab turadi — aks holda disk to'lib qolardi. Mantiq oddiy: "bu qism allaqachon jadvalga qo'llangan, endi kerak emas, o'chiraman".

Lekin bizga kerak. Biz uni hali o'qimadik.

Gazeta obunasini tasavvur qiling. Kioskda gazetalar bir hafta turadi, keyin tashlab yuboriladi. Siz ta'tilga chiqsangiz, qaytganingizda eski sonlar yo'q.

Yechim — obuna ochish: "menga saqlab turing, men kelib olaman". Kioskchi endi sizning nomingizga qo'yib boradi.

Postgres da bu obuna replikatsiya sloti deb ataladi. U aytadi: "men shu nuqtadan o'qiyapman — mendan oldingi WAL ni o'chirma".

Ikki natija:

  1. Siz xotirjam o'qiysiz, hech narsa yo'qolmaydi.
  2. Postgres endi sizni kutadi — va bu keyinroq xavfli bo'lib chiqadi (bob oxirida ko'rasiz).

Muhim nozik joy: slot ochilgandan keyingi o'zgarishlar saqlanadi. Undan oldingisi yo'q — siz obunani bugun ochsangiz, kechagi gazeta baribir yo'q.

python15 qator
python3 -c "
import psycopg2
c = psycopg2.connect(host='localhost', port=15455, user='postgres', password='kitob', dbname='rostuy')
c.autocommit = True
k = c.cursor()
k.execute(\"SELECT count(*) FROM pg_replication_slots WHERE slot_name='kitob_slot'\")
if k.fetchone()[0] == 0:
    k.execute(\"SELECT pg_create_logical_replication_slot('kitob_slot','test_decoding')\")
    print('slot yaratildi:', k.fetchone()[0])
else:
    print('slot allaqachon bor')
k.execute('SELECT slot_name, plugin, active FROM pg_replication_slots')
print('slotlar:', k.fetchall())
c.close()
"
Chiqish (qaytish kodi 0, 88 ms)
slot yaratildi: (kitob_slot,0/19B9258)
slotlar: [('kitob_slot', 'test_decoding', False)]

test_decoding — Postgres ichiga qurilgan, o'qishga qulay plagin. Ishlab chiqarishda pgoutput yoki Debezium ishlatiladi, lekin mexanizm aynan shu.

Polling nimani o'tkazib yuboradi

Endi asosiy tajriba. Ikki "tekshiruv" orasida tipik kun bo'ladi: yangi e'lonlar qo'shiladi, narxlar o'zgaradi, ba'zilari o'chiriladi. Ba'zi e'lon ikki marta yangilanadi.

Keyin ikkala usulni ham ishlatamiz va nechta o'zgarishni ko'rganini sanaymiz.

python38 qator
python3 -c "
import psycopg2, collections
c = psycopg2.connect(host='localhost', port=15455, user='postgres', password='kitob', dbname='rostuy')
c.autocommit = True
k = c.cursor()

k.execute('SELECT now()')
oxirgi_tekshiruv = k.fetchone()[0]

# --- ikki tekshiruv orasidagi kun ---
k.execute('''INSERT INTO elon (elon_id,tuman,narx_mln,xonalar)
             SELECT 'Y'||g, 'Yunusobod', 5.0, 2 FROM generate_series(1,50) g''')
k.execute(\"UPDATE elon SET narx_mln=narx_mln*1.1, yangilandi=now() WHERE elon_id IN (SELECT elon_id FROM elon ORDER BY elon_id LIMIT 30)\")
k.execute(\"UPDATE elon SET narx_mln=narx_mln*1.2, yangilandi=now() WHERE elon_id IN (SELECT elon_id FROM elon ORDER BY elon_id LIMIT 30)\")
k.execute(\"DELETE FROM elon WHERE elon_id IN (SELECT elon_id FROM elon ORDER BY elon_id DESC LIMIT 20)\")

haqiqiy = 50 + 30 + 30 + 20
print(f'haqiqatda sodir bo\\'lgan o\\'zgarishlar: {haqiqiy}')
print('  50 INSERT, 30 UPDATE, yana 30 UPDATE (bir xil qatorlar), 20 DELETE')
print()

# --- 1-usul: polling ---
k.execute('SELECT count(*) FROM elon WHERE yangilandi > %s', (oxirgi_tekshiruv,))
polling = k.fetchone()[0]
print(f'POLLING ko\\'rdi : {polling} qator')

# --- 2-usul: CDC ---
k.execute(\"SELECT data FROM pg_logical_slot_get_changes('kitob_slot',NULL,NULL)\")
hodisalar = [d for (d,) in k.fetchall()]
tur = collections.Counter()
for h in hodisalar:
    for t in ('INSERT','UPDATE','DELETE'):
        if f': {t}:' in h: tur[t] += 1
print(f'CDC     ko\\'rdi : {sum(tur.values())} o\\'zgarish -> {dict(tur)}')
print()
print(f'polling o\\'tkazib yuborgan: {haqiqiy - polling} ta o\\'zgarish')
c.close()
"
Chiqish (qaytish kodi 0, 105 ms)
haqiqatda sodir bo'lgan o'zgarishlar: 130
  50 INSERT, 30 UPDATE, yana 30 UPDATE (bir xil qatorlar), 20 DELETE

POLLING ko'rdi : 60 qator
CDC     ko'rdi : 130 o'zgarish -> {'INSERT': 50, 'UPDATE': 60, 'DELETE': 20}

polling o'tkazib yuborgan: 70 ta o'zgarish

Endi bob boshidagi telefon qo'ng'irog'iga qayting. Mijoz nega jahli chiqqanini raqamlar tushuntiradi.

Polling uchta narsani boy berdi:

  1. DELETE lar butunlay ko'rinmaydi. O'chirilgan qator yo'q — uni SELECT topa olmaydi. Omborda o'sha e'lon abadiy "faol" bo'lib qoladi.
  2. Oraliq UPDATE lar yo'qoladi. Bir qator ikki marta yangilangan bo'lsa, polling faqat oxirgi holatni ko'radi. Narx 5.0 → 5.5 → 6.6 bo'lgani bilinmaydi.
  3. Polling qator sanaydi, CDC esa o'zgarish sanaydi. Tahlil uchun farqi katta.

Chilonzordagi kvartira aynan 1-sabab tufayli omborda qolib ketgan edi. Ega uni o'chirgan, lekin SELECT o'chirilgan qatorni ko'rmaydi — shuning uchun ombor uni hech qachon "berilgan" deb belgilamagan. CDC da esa o'sha o'chirish DELETE hodisasi bo'lib turibdi.

"Biz soft-delete ishlatamiz, muammo yo'q"

To'g'ri, holat='ochirildi' qo'yish DELETE muammosini hal qiladi. Lekin qolgan ikkitasi qoladi: oraliq o'zgarishlar baribir yo'qoladi, va endi butun jamoa hech qachon DELETE yozmasligiga ishonasiz. Bitta DELETE yetadi.

Soft-delete — intizom talab qiladigan kelishuv. CDC — mexanizm. Mexanizm ishonchliroq.

O'zgarishlarni Kafka ga uzatamiz

Hozir CDC ishlayapti, lekin quvur hali tugamagan. Bitta muhim savol qoldi:

O'qilgan o'zgarish qayerga boradi?

Hozircha u ekranga chiqmoqda. Lekin omborga, hisobotga va modelga ham kerak — ya'ni bir nechta iste'molchiga.

10-bobdagi rasmni eslang: uchta manba, to'rtta iste'molchi, o'n ikkita chiziq. Aynan o'sha muammo shu yerda qaytadi.

Va bundan tashqari, CDC ning o'z xususiyati bor: slotdan o'qilgan o'zgarish yo'qoladi. pg_logical_slot_get_changes — bu "o'qi va o'chir" degani. Ikkinchi marta chaqirsangiz, o'sha hodisalar qaytmaydi.

Demak agar siz WAL dan to'g'ridan-to'g'ri omborga yozsangiz va yozish paytida xato bo'lsa — hodisa butunlay yo'qoladi. Uni hech qayerdan tiklab bo'lmaydi.

Shuning uchun CDC deyarli har doim Kafka bilan juftlanadi:

  • CDC slotdan bir marta o'qiydi va darhol Kafka ga yozadi;
  • Kafka esa uni saqlab turadi (10-bob: jurnal, navbat emas);
  • iste'molchilar o'z tezligida o'qiydi, kerak bo'lsa qaytadan.

Ya'ni Kafka bu yerda "bufer" emas — u xavfsizlik to'ri. Endi ikkalasini ulaymiz.

bash18 qator
docker rm -f kitob-kafka > /dev/null 2>&1
docker run -d --name kitob-kafka -p 19092:9092 \
  -e KAFKA_NODE_ID=1 -e KAFKA_PROCESS_ROLES=broker,controller \
  -e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
  -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:19092 \
  -e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \
  -e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \
  -e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \
  -e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
  -e KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1 \
  -e KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1 \
  -e KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS=0 \
  apache/kafka:3.9.0 > /dev/null
for i in $(seq 1 60); do
  python3 -c "from kafka import KafkaAdminClient; KafkaAdminClient(bootstrap_servers='localhost:19092')" > /dev/null 2>&1 \
    && { echo "kafka ${i} soniyada tayyor"; break; }
  sleep 1
done
Chiqish (qaytish kodi 0, 4068 ms)
kafka 1 soniyada tayyor

Quvurni yozamiz: WAL dan o'qib, tahlil qilib, Kafka ga yuboradi.

shu faylni yaratingsrc/quvur/cdc.py

python41 qator
"""Postgres WAL -> Kafka. Minimal, lekin haqiqiy CDC quvuri."""
import json
import re
from typing import Any, Iterator

QATOR = re.compile(r"table (\S+): (INSERT|UPDATE|DELETE): (.*)")
MAYDON = re.compile(r"(\w+)\[[\w ]+\]:('(?:[^']|'')*'|\S+)")


def tahlil(qator: str) -> dict[str, Any] | None:
    """test_decoding matnini dict ga aylantiradi. BEGIN/COMMIT -> None."""
    m = QATOR.match(qator)
    if not m:
        return None
    jadval, amal, qolgan = m.groups()
    qiymat = {}
    for nom, xom in MAYDON.findall(qolgan):
        if xom.startswith("'"):
            qiymat[nom] = xom[1:-1].replace("''", "'")
        elif xom == "null":
            qiymat[nom] = None
        else:
            qiymat[nom] = xom
    return {"jadval": jadval, "amal": amal, "qiymat": qiymat}


def oqim(kursor, slot: str, limit: int | None = None) -> Iterator[dict[str, Any]]:
    """Slotdagi o'zgarishlarni o'qiydi va tahlil qilib beradi."""
    kursor.execute(
        "SELECT data FROM pg_logical_slot_get_changes(%s, NULL, %s)", (slot, limit)
    )
    for (xom,) in kursor.fetchall():
        h = tahlil(xom)
        if h:
            yield h


def kafka_xabari(hodisa: dict[str, Any]) -> tuple[str, bytes]:
    """Kalit = birlamchi kalit, shuning uchun bir e'lon bitta partitionda qoladi."""
    kalit = hodisa["qiymat"].get("elon_id", "")
    return kalit, json.dumps(hodisa, ensure_ascii=False).encode()
python24 qator
python3 -c "
import sys, collections; sys.path.insert(0, 'src')
import psycopg2
from kafka import KafkaProducer
from quvur.cdc import oqim, kafka_xabari

c = psycopg2.connect(host='localhost', port=15455, user='postgres', password='kitob', dbname='rostuy')
c.autocommit = True
k = c.cursor()

# yangi o'zgarishlar yaratamiz
k.execute(\"UPDATE elon SET holat='sotildi' WHERE tuman='Chilonzor'\")
k.execute(\"DELETE FROM elon WHERE elon_id LIKE 'Y%'\")

p = KafkaProducer(bootstrap_servers='localhost:19092')
tur = collections.Counter()
for h in oqim(k, 'kitob_slot'):
    kalit, qiymat = kafka_xabari(h)
    p.send('elon-cdc', key=kalit.encode(), value=qiymat)
    tur[h['amal']] += 1
p.flush()
print('Kafka ga yuborildi:', dict(tur), '-> jami', sum(tur.values()))
c.close()
" 2>&1 | grep -v Warning
Chiqish (qaytish kodi 0, 850 ms)
Kafka ga yuborildi: {'UPDATE': 185, 'DELETE': 30} -> jami 215

O'qib ko'ramiz — o'zgarishlar haqiqatan yetib borganmi:

python21 qator
python3 -c "
import json, collections
from kafka import KafkaConsumer

c = KafkaConsumer('elon-cdc', bootstrap_servers='localhost:19092',
                  auto_offset_reset='earliest', group_id='ombor', consumer_timeout_ms=5000)
tur = collections.Counter()
namuna = {}
for m in c:
    h = json.loads(m.value)
    tur[h['amal']] += 1
    namuna.setdefault(h['amal'], h)
c.close()

print('Kafka dan o\\'qildi:', dict(tur))
print()
for amal in ('UPDATE', 'DELETE'):
    if amal in namuna:
        q = namuna[amal]['qiymat']
        print(f'{amal:7s} namuna: {dict(list(q.items())[:4])}')
" 2>&1 | grep -v Warning
Chiqish (qaytish kodi 0, 6598 ms)
Kafka dan o'qildi: {'UPDATE': 185, 'DELETE': 30}

UPDATE  namuna: {'elon_id': 'E345049', 'tuman': 'Chilonzor', 'narx_mln': '5.69', 'xonalar': '2'}
DELETE  namuna: {'elon_id': 'Y14'}

DELETE hodisasida faqat birlamchi kalit borligiga e'tibor bering — Postgres o'chirilgan qatorning qolgan ustunlarini WAL ga yozmaydi (agar REPLICA IDENTITY FULL qo'yilmagan bo'lsa).

Snapshot va oqim

Bitta savol qoladi: slot ochilgunga qadar jadvalda turgan e'lonlar-chi? Ular WAL da yo'q.

Javob — ikki bosqich:

python7 qator
import pandas as pd
show(pd.DataFrame([
    {"Bosqich": "1. Snapshot", "Nima qiladi": "Jadvalni to'liq o'qib ko'chiradi",
     "Qachon": "Bir marta, boshida", "Manba": "SELECT *"},
    {"Bosqich": "2. Oqim", "Nima qiladi": "Faqat o'zgarishlarni uzatadi",
     "Qachon": "Doimiy", "Manba": "WAL sloti"},
]))
BosqichNima qiladiQachonManba
1. SnapshotJadvalni to'liq o'qib ko'chiradiBir marta, boshidaSELECT *
2. OqimFaqat o'zgarishlarni uzatadiDoimiyWAL sloti

Tartib muhim: avval slot ochiladi, keyin snapshot olinadi. Teskari qilsangiz, ikkalasi orasidagi o'zgarishlar yo'qoladi. Biz ham shu tartibda ishladik.

Debezium nima qiladi

Bu bobda quvurni qo'lda yozdik, chunki mexanizmni ko'rish kerak edi. Ishlab chiqarishda Debezium ishlatiladi va u xuddi shu ishni qiladi: slot ochadi, snapshot oladi, WAL ni o'qiydi, Kafka ga yozadi.

Debezium qo'shadigan narsa — qulash va qayta tiklanish (offset saqlash), sxema o'zgarishini kuzatish, ko'p jadval, va pgoutput plaginidan foydalanish. Ya'ni bu bobdagi 40 qator kod ishlab chiqarishga tayyor emas, lekin u nima bo'layotganini ko'rsatadi.

Tozalash

bash10 qator
python3 -c "
import psycopg2
c = psycopg2.connect(host='localhost', port=15455, user='postgres', password='kitob', dbname='rostuy')
c.autocommit = True
c.cursor().execute(\"SELECT pg_drop_replication_slot('kitob_slot')\")
c.close()
print('slot o\\'chirildi')
" 2>/dev/null || echo "slot allaqachon yo'q"
docker rm -f kitob-pg kitob-kafka > /dev/null 2>&1 && echo "konteynerlar o'chirildi"
docker ps --format '{{.Names}}'
Chiqish (qaytish kodi 0, 1027 ms)
slot o'chirildi
konteynerlar o'chirildi
novavivo-postgres
novavivo-redis
stratix-med-frontend-1

Slotni o'chirishni unutmang

Bu eng qimmat xato. Faol bo'lmagan slot Postgres ga "WAL ni saqlab tur" deb turaveradi. Iste'molchi o'chgan bo'lsa ham.

Natija: disk asta-sekin to'ladi va bir kuni baza yozishni to'xtatadi. Sabab topilguncha soatlar ketadi. pg_replication_slots dagi active=false slotlarni muntazam tekshiring.

Xulosa

Tushuncha Asosiy fikr
Polling Oson, lekin DELETE va oraliq o'zgarishlarni boy beradi
WAL Har commit yoziladigan tranzaksiya jurnali
wal_level=logical Qatorlar va qiymatlar WAL ga yozilsin (restart kerak)
Replikatsiya sloti O'qish o'rnini saqlaydi; WAL o'chirilmaydi
Kalit Birlamchi kalit → bir qator bitta partitionda
DELETE hodisasi Faqat kalit keladi (REPLICA IDENTITY ga bog'liq)
Tartib Avval slot, keyin snapshot — aks holda bo'shliq qoladi
Debezium Shu mexanizm + qulashga chidamlilik + sxema kuzatuvi
Unutilgan slot Diskni to'ldiradi va bazani to'xtatadi

Talaba ishi

  1. ALTER TABLE elon REPLICA IDENTITY FULL qo'ying va DELETE ni qayta o'lchang. Hodisada endi nima bor?
  2. Slotni ochmasdan 100 ta o'zgarish qiling, keyin slot oching. Nechta hodisa keladi? Nega?
  3. pg_logical_slot_peek_changes ni sinab ko'ring. get dan farqi nima va qachon kerak?
  4. Ikki jadval qo'shing va quvurni ikkalasini ham uzatadigan qiling. Kafka topiclarini qanday ajratasiz?

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar