Mundarija (37)
- Bu darsda
- 1. Nega bu kerak?
- 2. Saqlash siyosati
- 2.1 Muddatdan mexanizmga
- 2.2 Nega SQL funksiya?
- 3. Yumshoq o'chirilganlarni tozalash
- 3.1 Funksiya
- 3.2 Nega paketlab?
- 3.3 Sinab ko'ramiz
- 4. Auth qoldiqlari
- 5. Arxivlash: eski bo'limni chiqarib olish
- 5.1 DROP va arxiv
- 5.2 DETACH → COPY → DROP
- 5.3 Kanon tozalovchisi
- 6. Anonimlashtirish, psevdonim va o'chirish
- 6.1 Uchta usul
- 6.2 Psevdonim: HMAC bilan
- 6.3 Kanon tanlovi: qator qoladi, shaxsiy ma'lumot ketadi
- 7. Hisobni anonimlashtirish funksiyasi
- 7.1 Jadvallar ro'yxati — katalogdan
- 7.2 Funksiya
- 7.3 Haqiqiy hodisa: NOT NULL
- 7.4 Ishga tushiramiz
- 8. API: DELETE /v1/auth/men
- 8.1 Controller
- 8.2 Hujumchi nigohi
- 9. Rejali tozalash ishi
- 10. Zaxira nusxalar va unutilish huquqi
- 11. Ko'p uchraydigan xatolar
- 12. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): nima tozalanadi?
- 3-mashq (qiyin): k-anonimlik
- 13. Loyiha qadami: saqlash muddati va hisobni o'chirish
- 14. Real ishda
- Xulosa
- Manbalar
Ma'lumot saqlash muddati: arxivlash, anonimlashtirish va hisobni o'chirish
Qisqacha: Saqlash muddati (retention) — har ma'lumot uchun "qancha saqlaymiz va keyin nima qilamiz" degan qoida. Uni qog'ozda yozish yetmaydi — kod har kuni o'zi bajaradi.
vazifalar-apida uchta SQL funksiya bor: yumshoq o'chirilgan vazifalarni 30 kundan keyin paketlab o'chirish, eski sessiya va kirish jurnalini tozalash, hisobni anonimlashtirish. Hisob o'chirilganda foydalanuvchi qatori qoladi, lekin unga bog'liq hamma shaxsiy ma'lumot ketadi — jadvallar ro'yxati bazaning katalogidan olinadi.
Bu darsda
- Saqlash siyosatini aniq muddat va mexanizmga aylantirasiz.
- Katta
DELETEni paketlab bajarish nega kerakligini tushunasiz. - Eski bo'limni
DETACH→COPY→DROPbilan arxivlaysiz. - Anonimlashtirish, psevdonimlashtirish va o'chirish farqini kod bilan ko'rasiz.
- Hisobni o'chirishni katalogga tayanib, keyin qo'shiladigan jadvallarni ham qamraydigan qilib yozasiz.
DELETE /v1/auth/menva rejali tozalash ishini loyihaga qo'shib, commit qilasiz.
Oldin bilishingiz kerak: Shaxsiy ma'lumotlar qonunchiligi, Soft delete, timestamps va tarix jadvallari, Funksiyalar va PL/pgSQL, Ma'lumotni qisman ko'chirish, Partitioning.
Oldingi darsda docs/MAXFIYLIK.md ni yozdik va ma'lumotlar xaritasining har qatoriga muddat qo'ydik. Hujjat — va'da. Bu darsda va'dani kod bajaradi.
1. Nega bu kerak?
Azizga kelgan xatni eslang: "Hisobimni o'chiring". Aziz admin panelida tugmani bosadi: DELETE FROM foydalanuvchilar WHERE id = 7. Va xatoga duch keladi — yoki, undan yomoni, xatosiz o'tadi.
Birinchi holat: chet kalit cheklovi o'chirishga ruxsat bermaydi, chunki statistika jadvali shu id'ga bog'langan. Ikkinchi holat: ON DELETE CASCADE hamma narsani o'chiradi — oylik faollik grafigi bir kecha ichida "teshik" bilan qoladi. Ikkalasi ham yaxshi emas.
Ikkinchi muammo — jim yig'ilish. Kanonda yumshoq o'chirish bor: "o'chirilgan" vazifa ochirilgan_vaqt bilan belgilanadi va bazada qoladi. Kirish jurnali har kirishda bir qator yozadi: IP, brauzer, vaqt. Hech kim ularni o'chirmasa, uch yildan keyin baza o'n barobar katta, unda eng ko'p qator — hech kimga kerak bo'lmagan va eng xavfli ma'lumot.
O'xshatish: kutubxona arxivi. Har kitobning saqlash muddati bor. Muddat tugaganlar yo yo'q qilinadi, yo arxiv omboriga ketadi, yo statistika uchun faqat "nechta kitob bor edi" degan yozuv qoladi. Bu ishni har kuni kutubxonachi qiladi. Bizda — rejali vazifa.
2. Saqlash siyosati
2.1 Muddatdan mexanizmga
Saqlash siyosati (retention policy) — har ma'lumot turi uchun uchta javob: qancha saqlanadi, nima bilan tugaydi va buni kim bajaradi. Oldingi darsdagi xarita kanonda shunday kodga aylandi:
| Ma'lumot | Muddat | Mexanizm |
|---|---|---|
| Yumshoq o'chirilgan vazifalar | 30 kun | ochirilganlarni_tozala() — haqiqiy DELETE |
| Sessiya, token, OTP | tugagandan 7 kun keyin | auth_tozala() |
| Kirish hodisalari (IP, brauzer) | 180 kun | auth_tozala() |
| Vazifa hodisalari (statistika) | 13 oy | bo'limni DROP |
| Foydalanuvchi hisobi | o'chirguncha | hisob_anonimlashtir() |
Raqamlar qayerdan? 30 kun — "savat"dan tiklash uchun foydalanuvchiga yetarli vaqt. 180 kun — hisobni o'g'irlash tekshiruvi uchun kirish tarixi kerak bo'ladigan oraliq. 13 oy — o'tgan yilning shu oyi bilan solishtirish uchun. Bular biznes qarori: boshqa loyihada boshqacha bo'ladi. Muhimi — muddat yozilgan va kod uni bajaradi.
2.2 Nega SQL funksiya?
Tozalashni Node kodida ham yozish mumkin edi. Kanon SQL funksiyani tanladi, uch sababga ko'ra:
- Huquq. Funksiya
SECURITY DEFINER— u migratsiya egasi nomidan ishlaydi. Ishchi jarayonning roli (vazifalar_ishchi) jadvallarga kengDELETEhuquqisiz, faqat shu funksiyani chaqira oladi (GRANT EXECUTE). Rollar va huquqlar darsidagi "eng kam huquq" tamoyili. - Bitta joy. API, ishchi, testlar va qo'lda
psql— hammasi bitta funksiyani chaqiradi. - Tranzaksiya. Funksiya ichidagi hamma o'chirish bitta tranzaksiyada.
SECURITY DEFINER funksiyada SET search_path majburiy — aks holda chaqiruvchi o'z sxemasida soxta vazifalar jadvalini yaratib, funksiyani aldashi mumkin. Kanonda har funksiyada search_path = pg_catalog, public qotirilgan.
3. Yumshoq o'chirilganlarni tozalash
3.1 Funksiya
Soft delete darsida vazifani savatga tashlashni qurgandik. Endi savatni bo'shatamiz. Kanon funksiyasi (telefon uchun qatorlarga bo'lindi):
CREATE FUNCTION ochirilganlarni_tozala(
saqlash interval DEFAULT interval '30 days',
paket integer DEFAULT 5000
) RETURNS bigint
LANGUAGE plpgsql SECURITY DEFINER
SET search_path = pg_catalog, public AS $$
DECLARE jami bigint := 0; n bigint;
BEGIN
LOOP -- paketlab: katta DELETE — uzun qulf, WAL to'lqini
DELETE FROM vazifalar WHERE id IN (
SELECT id FROM vazifalar
WHERE ochirilgan_vaqt < now() - saqlash
ORDER BY id LIMIT paket);
GET DIAGNOSTICS n = ROW_COUNT;
jami := jami + n;
EXIT WHEN n < paket;
END LOOP;
RETURN jami;
END $$;Asosiy g'oya — paketlab o'chirish (batch delete). Har aylanishda ko'pi bilan paket ta qator o'chiriladi. GET DIAGNOSTICS n = ROW_COUNT — oxirgi DELETE necha qatorga tegdi. Paket to'lmasa (n < paket), o'chiradigan narsa qolmagan — sikl tugaydi.
3.2 Nega paketlab?
Bir yillik yig'ilgan 2 million o'chirilgan vazifani bitta DELETE bilan o'chirsak nima bo'ladi?
- Qatorlar qulflanadi va tranzaksiya uzoq davom etadi.
- Har o'chirilgan qator WAL'ga yoziladi — bir zumda gigabaytlab. Replikatsiya darsida ko'rgandik: katta tranzaksiyada replika megabaytlab orqada qoladi.
- O'lik qatorlar (dead tuples) bir vaqtda paydo bo'ladi va autovacuum'ga katta yuk tushadi.
Kichik paketlar bu yukni vaqt bo'ylab yoyadi. Bitta muhim nuance: PL/pgSQL funksiyasi chaqiruvchining bitta tranzaksiyasi ichida ishlaydi. Ya'ni funksiya ichidagi paketlar qulfni qisqartiradi, lekin WAL va vacuum yukini butunlay bo'lmaydi. Juda katta hajmda paketlarni alohida tranzaksiyalarda chaqirish kerak — masalan, ishchi funksiyani paket bilan bir necha marta chaqiradi.
3.3 Sinab ko'ramiz
Funksiyani o'z bazamizda ishga tushiramiz (biz ishga tushirdik — vazifalar-api sxemasi va namuna ma'lumotlari, PostgreSQL 17.6). Avval uchta vazifani 40 kun oldin, bittasini 2 kun oldin "o'chirilgan" qilamiz:
UPDATE vazifalar SET ochirilgan_vaqt = now() - interval '40 days'
WHERE id IN (1, 2, 3);
UPDATE vazifalar SET ochirilgan_vaqt = now() - interval '2 days'
WHERE id = 4;
SELECT count(*) FILTER (WHERE ochirilgan_vaqt IS NOT NULL)
AS ochirilgan,
count(*) AS jami
FROM vazifalar;UPDATE 3
UPDATE 1
ochirilgan | jami
------------+------
5 | 48
(1 row)Beshta o'chirilgan: bizning to'rttamiz va namunadagi bittasi (u hozirgina savatga tashlangan). Endi 30 kunlik muddat bilan, ataylab kichik paket — 2 — bilan tozalaymiz:
SELECT ochirilganlarni_tozala(interval '30 days', 2) AS tozalandi;
SELECT count(*) FILTER (WHERE ochirilgan_vaqt IS NOT NULL)
AS ochirilgan,
count(*) AS jami
FROM vazifalar; tozalandi
-----------
3
(1 row)
ochirilgan | jami
------------+------
2 | 45
(1 row)Uchta qator ketdi — faqat 40 kunliklar. Paket 2 bo'lgani uchun sikl ikki marta aylandi: avval 2 ta, keyin 1 ta (1 < 2 — chiqish). 2 kunlik va yangi o'chirilgan vazifa savatda qoldi: foydalanuvchi ularni hali tiklay oladi.
Tekshirib ko'ring: Savatda aynan 4 ta eski vazifa bor va
paket = 2. Sikl necha marta aylanadi?
Javob
Uch marta. Birinchi aylanish — 2 ta, ikkinchisi — yana 2 ta. Ikkalasida n = paket, chiqish sharti (n < paket) bajarilmadi. Uchinchi aylanish hech narsa topmaydi: n = 0, 0 < 2 — sikl tugaydi. Bitta "bo'sh" aylanish — qoldiq sonni oldindan bilmaslikning arzon narxi.
DELETE CASCADE bilan ketadi: vazifaning teglar bog'lanishi, izohlari va fayl ilovalari ham o'chadi. Fayllarning o'zi esa S3 da — SQL uni o'chira olmaydi. Fayl qatori "yetim" bo'lib qoladi va 29-qismdagi ishchi uni keyingi tunda S3 dan tozalaydi.
4. Auth qoldiqlari
Ikkinchi funksiya — auth_tozala(). U to'rtta jadvalni tozalaydi: tugaganiga 7 kundan oshgan sessiyalar, bir martalik tokenlar, OTP kodlar va muddatidan eski kirish hodisalari. Sinab ko'ramiz: Dilnozaga 200 va 10 kunlik ikkita kirish hodisasi, bitta eski va bitta faol sessiya qo'shamiz:
INSERT INTO auth_hodisalar (foydalanuvchi_id, tur, ip, vaqt) VALUES
(2, 'kirish', '203.0.113.7', now() - interval '200 days'),
(2, 'kirish', '203.0.113.7', now() - interval '10 days');
INSERT INTO sessiyalar
(foydalanuvchi_id, token_hash, yaratilgan_vaqt, tugash_vaqti)
VALUES
(2, sha256('eski'), now() - interval '30 days',
now() - interval '20 days'),
(2, sha256('faol'), now(), now() + interval '7 days');
SELECT auth_tozala();INSERT 0 2
INSERT 0 2
auth_tozala
-----------------------------------------------------------------
{"otp": 0, "tokenlar": 0, "sessiyalar": 1, "auth_hodisalar": 1}
(1 row)Bitta sessiya (20 kun oldin tugagan) va bitta hodisa (200 kunlik) o'chdi. Faol sessiya va 10 kunlik hodisa qoldi. Funksiya natijani JSONB obyektda qaytaradi — ishchi uni logga yozadi va kuzatuvda "bugun nechta qator tozalandi" grafigi chiziladi.
Kalitlar tartibiga e'tibor bering: biz sessiyalar, tokenlar, otp, auth_hodisalar deb yozgan edik, chiqishda boshqa tartib. JSONB kalitlarni o'zicha saqlaydi — avval qisqa kalitlar. Kalitlar tartibiga tayanadigan kod yozmang.
5. Arxivlash: eski bo'limni chiqarib olish
5.1 DROP va arxiv
Partitioning darsida vazifa_hodisalari jadvalini oyma-oy bo'limlarga bo'lgan edik. Saqlash muddati uchun bu juda qulay: eski oy — alohida jadval. Uni o'chirish uchun millionlab qatorni DELETE qilish shart emas — butun bo'limni uzib tashlaymiz.
Kanon o'lchovi (3 million sintetik hodisa, bir oylik bo'limda 246 576 qator; bitta mashina, Docker'dagi PostgreSQL 18.6):
- DELETE (246 576 qator)100 %
- DETACH + DROP9.7 + 16.7 ms6 %
Manba: IlmHamroh kanon o'lchovi, 2026-11-02: PostgreSQL 18.6, 3 mln sintetik hodisa, 12 oylik bo'lim; DELETE 442.8 ms, DETACH 9.7 ms + DROP 16.7 ms; bitta mashina, bitta yugurish
Bo'limni uzish va tashlash DELETE ning taxminan 6 % ini oldi — 17 baravar tez. Va bu vaqt qatorlar soniga deyarli bog'liq emas: DROP faylni o'chiradi, har qatorni WAL'ga yozmaydi.
Lekin ba'zan ma'lumotni butunlay yo'qotib bo'lmaydi: hisobot, audit yoki "bir yil oldingi bilan solishtirish". Shunda arxivlash (archiving) — eski ma'lumotni arzon va sekin omborga ko'chirish: S3, Parquet fayl, alohida analitik baza. Kanon bu variantni kodga kiritmadi — 13 oydan eski analitika kerak emas deb qaror qildi. Lekin arxivlashning uchta qadamini o'z bazamizda sinab ko'ramiz.
5.2 DETACH → COPY → DROP
2024-yil yanvar uchun bo'lim yaratib, unga uchta hodisa yozamiz. hodisa_bolimi_yarat() — Partitioning darsidagi funksiya:
SELECT hodisa_bolimi_yarat('2024-01-01');
INSERT INTO vazifa_hodisalari (vaqt, egasi_id, vazifa_id, tur) VALUES
('2024-01-05 09:00+00', 1, 1, 'yaratildi'),
('2024-01-06 14:30+00', 1, 1, 'bajarildi'),
('2024-01-20 08:15+00', 2, 5, 'yaratildi');
SELECT tableoid::regclass AS bolim, count(*)
FROM vazifa_hodisalari
WHERE vaqt < '2025-01-01'
GROUP BY 1; hodisa_bolimi_yarat
---------------------------
vazifa_hodisalari_2024_01
(1 row)
INSERT 0 3
bolim | count
------------------------------------+-------
bolimlar.vazifa_hodisalari_2024_01 | 3
(1 row)tableoid::regclass — har qator qaysi jadvalda jismonan yotganini ko'rsatadi. Uchala hodisa 2024-yil yanvar bo'limiga tushdi. Endi arxivlaymiz:
ALTER TABLE vazifa_hodisalari
DETACH PARTITION bolimlar.vazifa_hodisalari_2024_01;
COPY bolimlar.vazifa_hodisalari_2024_01 TO STDOUT
WITH (FORMAT csv, HEADER);
DROP TABLE bolimlar.vazifa_hodisalari_2024_01;ALTER TABLE
vaqt,egasi_id,vazifa_id,tur,malumot
2024-01-05 09:00:00+00,1,1,yaratildi,{}
2024-01-06 14:30:00+00,1,1,bajarildi,{}
2024-01-20 08:15:00+00,2,5,yaratildi,{}
DROP TABLEUch qadam:
DETACH— bo'lim asosiy jadvaldan uziladi va oddiy jadvalga aylanadi. Endivazifa_hodisalaridan so'rov uni ko'rmaydi, lekin ma'lumot hali bor.COPY … TO STDOUT— jadvalni CSV ko'rinishida chiqaradi. Haqiqiy loyihada bu oqim faylga yoki S3 ga ketadi (psqldagi\copy— mijoz kompyuteridagi faylga yozadi).DROP— arxiv tekshirilgandan keyin bo'lim o'chiriladi.
Ikkinchi va uchinchi qadam orasida muhim tekshiruv bor: arxiv faylidagi qatorlar soni jadvaldagiga tengmi? Arxivni tekshirmasdan DROP qilish — zaxirani tiklab ko'rmasdan ishonish bilan bir xil xato (HA va falokatdan tiklash darsini eslang).
Arxivda ham shaxsiy ma'lumot bor: egasi_id. Arxiv ham saqlash siyosatiga kiradi — uning ham muddati bo'lishi kerak.
Tekshirib ko'ring: Nega
COPYdan oldinDETACHqilinadi? To'g'ridan bo'limni nusxalab, keyin uzsak bo'lmaydimi?
Javob
DETACH dan keyin bo'limga yangi yozuv tusha olmaydi — u asosiy jadvaldan uzilgan, trigger va ilova unga yozmaydi. Shuning uchun nusxa olinayotgan paytda jadval "muzlagan": arxivdagi qatorlar soni jadvaldagiga aniq teng bo'ladi. Teskari tartibda nusxa olish va uzish orasida kech kelgan hodisa bo'limga tushib qolishi mumkin. U arxivga kirmaydi, DROP bilan esa yo'qoladi.
5.3 Kanon tozalovchisi
Kanonda arxiv yo'q — bo'lim to'g'ridan DROP qilinadi. Buni hodisa_bolimlari_tozala() qiladi: u bo'lim nomidan oyni o'qiydi va muddatdan eskilarini uzib tashlaydi. Sinab ko'ramiz:
SELECT hodisa_bolimi_yarat('2024-02-01');
SELECT hodisa_bolimlari_tozala(interval '13 months'); hodisa_bolimi_yarat
---------------------------
vazifa_hodisalari_2024_02
(1 row)
hodisa_bolimlari_tozala
---------------------------
vazifa_hodisalari_2024_02
(1 row)2024-yil fevral 13 oydan eski — u o'chdi. Joriy va oldingi oylarning bo'limlariga funksiya tegmadi. Kanon testida ham xuddi shu tekshirilgan: tozala('13 months') faqat 2024-01 ni o'chirgan.
6. Anonimlashtirish, psevdonim va o'chirish
6.1 Uchta usul
Ma'lumotni qisman ko'chirish darsida prodakshn nusxasini dasturchiga berishdan oldin uni anonimlashtirgan edik. Endi shu tushunchalarni saqlash muddati nuqtai nazaridan qayta ko'ramiz:
| Usul | Nima qoladi | Qayta tiklash mumkinmi |
|---|---|---|
| O'chirish (deletion) | hech narsa | yo'q |
| Psevdonimlashtirish (pseudonymization) | kalit bilan almashtirilgan qiymat | kalit bilan — ha |
| Anonimlashtirish (anonymization) | odamni aniqlab bo'lmaydigan ma'lumot | yo'q |
Muhim nuance: GDPR nazarida psevdonimlangan ma'lumot hali ham shaxsiy ma'lumot. Kalit kimdadir bor ekan, odamni qayta topish mumkin. Faqat haqiqiy anonim ma'lumot qonun doirasidan chiqadi. Ikkalasi ham reglament matnida yozilgan: 4-moddaning 5-bandi psevdonimlashtirishni "qo'shimcha ma'lumotsiz aniq odamga bog'lab bo'lmaydigan" ishlov deb ta'riflaydi va o'sha qo'shimcha ma'lumot (kalit) alohida, himoyalangan holda saqlanishini talab qiladi. 26-kirish qismi (recital) esa anonim ma'lumotga reglament tamoyillari qo'llanmasligini aytadi.
6.2 Psevdonim: HMAC bilan
Psevdonimning eng sodda va xavfsiz shakli — kalitli xesh (HMAC). Parolni xavfsiz saqlash darsidan bilasiz: oddiy SHA-256 emaildan psevdonim yasashga yaramaydi — hujumchi ma'lum emaillar ro'yxatini xeshlab, solishtirib chiqadi. Kalit esa faqat serverda. Brauzerda sinab ko'ring:
async function psevdonim(kalit, qiymat) {
const enc = new TextEncoder();
const k = await crypto.subtle.importKey(
"raw", enc.encode(kalit),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"],
);
const imzo = await crypto.subtle.sign(
"HMAC", k, enc.encode(qiymat));
return [...new Uint8Array(imzo).slice(0, 8)]
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
const KALIT = "faqat-serverda-saqlanadi";
console.log(await psevdonim(KALIT, "aziz@example.com"));
console.log(await psevdonim(KALIT, "aziz@example.com"));
console.log(await psevdonim(KALIT, "dilnoza@example.com"));
console.log(await psevdonim("boshqa-kalit", "aziz@example.com"));Konsolda:
1e290e7882b387f5
1e290e7882b387f5
53bc7598beda68b6
c08dd694e1b0a490Bir xil email va kalit — bir xil psevdonim (birinchi ikki qator). Shuning uchun analitikada "bitta foydalanuvchining hodisalari"ni guruhlash mumkin, email esa ko'rinmaydi. Kalit almashsa — butunlay boshqa qiymat. Kalitni o'chirib tashlasangiz, psevdonimlar amalda anonimga aylanadi: endi hech kim ularni emailga qaytara olmaydi. Natijaning faqat birinchi 8 baytini oldik — o'qish uchun qisqa, analitika uchun yetarli.
Tekshirib ko'ring: Analitik jamoa psevdonimlangan eksportni oldi. HMAC kaliti ham ular bilan bitta serverda saqlanadi. Bu eksport shaxsiy ma'lumotmi?
Javob
Ha, va bu holatda himoya deyarli yo'q. Kalit qo'lida bo'lgan odam ma'lum emaillar ro'yxatini HMAC'lab, psevdonimlarni bir zumda emailga qaytaradi. Psevdonim faqat kalit alohida saqlansa va eksportni oluvchida bo'lmasa foyda beradi — GDPR ham aynan shu shartni qo'yadi. Kalit va ma'lumot bitta joyda — bu oddiy shaxsiy ma'lumot, faqat noqulay ko'rinishda.
6.3 Kanon tanlovi: qator qoladi, shaxsiy ma'lumot ketadi
Hisob o'chirilganda kanon foydalanuvchilar qatorini o'chirmaydi. Uning o'rniga:
- shaxsiy ustunlar almashtiriladi yoki tozalanadi: email —
ochirilgan-<id>@ochirilgan.invalid, ism — "O'chirilgan foydalanuvchi", parol, telefon, sozlamalar — bo'sh; - foydalanuvchiga chet kalit bilan bog'langan hamma jadvaldan uning qatorlari o'chiriladi;
- qator bloklanadi — u bilan kirib bo'lmaydi.
Nega qator qoladi? Uning id si vazifa_hodisalari dagi statistikada ishlatiladi. Qator qolsa, "oktyabrda 2 000 ta vazifa yaratildi" degan grafik o'zgarmaydi. id bu yerda psevdonim vazifasini bajaradi: unga bog'liq email ham, ism ham, vazifa matni ham qolmaydi.
Halol aytaylik: bu to'liq anonimlik emas. Hodisalarda id va vaqtlar bor. Agar kimdir boshqa manbadan "Aziz 5-yanvar soat 14:30 da vazifa bajargan" ni bilsa, nazariy jihatdan qatorni topishi mumkin. Kanon buni qabul qilingan xavf deb hisobladi: hodisalar 13 oydan keyin bo'lim bilan birga o'chadi.
.invalid domeniga ham e'tibor bering. Bu domen standart bo'yicha hech qachon mavjud bo'lmaydi — bunday manzilga xat ketib qolishi imkonsiz.
7. Hisobni anonimlashtirish funksiyasi
7.1 Jadvallar ro'yxati — katalogdan
Eng oddiy yechim — funksiyaga jadvallar ro'yxatini qo'lda yozish: DELETE FROM vazifalar …; DELETE FROM teglar …;. Muammo — kelajak. Yarim yildan keyin kimdir ai_sarf degan yangi jadval qo'shadi va ro'yxatga yozishni unutadi. Hisob "o'chirilgan", lekin AI sarfi qatorlari qoladi.
Kanon ro'yxatni katalogdan oladi: foydalanuvchilar ga ON DELETE CASCADE chet kaliti bilan bog'langan har jadval. O'sha so'rovni alohida bajaramiz:
SELECT c.conrelid::regclass AS jadval, a.attname AS ustun
FROM pg_constraint c
JOIN pg_attribute a
ON a.attrelid = c.conrelid AND a.attnum = c.conkey[1]
WHERE c.contype = 'f'
AND c.confrelid = 'public.foydalanuvchilar'::regclass
AND c.confdeltype = 'c'
ORDER BY 1; jadval | ustun
-----------------------+------------------
vazifalar | egasi_id
teglar | egasi_id
auth_hodisalar | foydalanuvchi_id
sessiyalar | foydalanuvchi_id
bir_martalik_tokenlar | foydalanuvchi_id
otp_kodlar | foydalanuvchi_id
tashqi_hisoblar | foydalanuvchi_id
mfa_totp | foydalanuvchi_id
zaxira_kodlar | foydalanuvchi_id
passkeylar | foydalanuvchi_id
api_kalitlar | foydalanuvchi_id
fayllar | egasi_id
bildirishnomalar | foydalanuvchi_id
telegram_ulanishlar | foydalanuvchi_id
push_obunalar | foydalanuvchi_id
webhook_obunalari | foydalanuvchi_id
(16 rows)O'n olti jadval. pg_constraint — bazadagi hamma cheklovlar; contype = 'f' — chet kalit, confrelid — u ko'rsatayotgan jadval, confdeltype = 'c' — ON DELETE CASCADE. conkey[1] — cheklovning birinchi ustuni. Tartib — regclass bo'yicha, ya'ni jadvallar yaratilgan tartibda.
Ro'yxatda yo'q jadvallar ham qiziq. izohlar.muallif_id da ON DELETE SET NULL — u boshqa yo'l bilan qamraladi: izohlar vazifa bilan birga CASCADE orqali o'chadi. vazifa_hodisalari da esa chet kalit umuman yo'q — statistika ataylab qoladi.
31-qismning keyingi darslarida yangi jadvallar qo'shiladi: AI sarfi, vazifa embedding'lari. Ularning chet kaliti CASCADE bo'lsa, funksiya ularni kodni o'zgartirmasdan qamraydi. Bu katalogga tayanishning asosiy yutug'i. Teskari holat ham bor: Audit log darsidagi jurnal foydalanuvchiga ataylab chet kalitsiz bog'lanadi — hisob o'chganda audit yozuvi raqam bilan qolishi kerak, ism va emailsiz.
7.2 Funksiya
CREATE FUNCTION hisob_anonimlashtir(fid bigint) RETURNS jsonb
LANGUAGE plpgsql SECURITY DEFINER
SET search_path = pg_catalog, public AS $$
DECLARE f record; n bigint; natija jsonb := '{}';
BEGIN
PERFORM 1 FROM foydalanuvchilar WHERE id = fid FOR UPDATE;
IF NOT FOUND THEN RETURN NULL; END IF;
FOR f IN
SELECT c.conrelid::regclass::text AS jadval,
a.attname AS ustun
FROM pg_constraint c
JOIN pg_attribute a
ON a.attrelid = c.conrelid AND a.attnum = c.conkey[1]
WHERE c.contype = 'f'
AND c.confrelid = 'public.foydalanuvchilar'::regclass
AND c.confdeltype = 'c'
AND c.conrelid NOT IN ('public.auth_hodisalar'::regclass,
'public.fayllar'::regclass)
ORDER BY 1
LOOP
EXECUTE format('DELETE FROM %s WHERE %I = $1',
f.jadval, f.ustun) USING fid;
GET DIAGNOSTICS n = ROW_COUNT;
IF n > 0 THEN
natija := natija || jsonb_build_object(f.jadval, n);
END IF;
END LOOP;
UPDATE auth_hodisalar
SET ip = NULL, user_agent = NULL, tafsilot = '{}'
WHERE foydalanuvchi_id = fid;
UPDATE foydalanuvchilar
SET email = format('ochirilgan-%s@ochirilgan.invalid', id),
toliq_ism = 'O''chirilgan foydalanuvchi',
parol_hash = NULL, telefon = NULL, sozlamalar = '{}',
email_tasdiqlangan_vaqt = NULL, rol = 'foydalanuvchi',
bloklangan_vaqt = now()
WHERE id = fid;
INSERT INTO auth_hodisalar (foydalanuvchi_id, tur)
VALUES (fid, 'hisob_ochirildi');
RETURN natija;
END $$;Qatorma-qator:
FOR UPDATE— foydalanuvchi qatori qulflanadi. Shu paytda parallel so'rov unga yangi vazifa qo'sha olmaydi.- Ikki istisno. Birinchisi —
auth_hodisalar. Qatorlar qoladi, lekin IP va brauzer tozalanadi: "hisob o'chirildi" degan hodisa xavfsizlik uchun kerak. Ikkinchisi —fayllar: S3 dagi obyektni SQL o'chira olmaydi; qator ishchiga "buni S3 dan o'chir" degan belgi bo'lib qoladi. format('… %s … %I', …)—%Iustun nomini xavfsiz qo'shtirnoqlaydi;%sgaregclass::textberiladi, u allaqachon to'g'ri yozilgan. Qiymat esaUSING fidbilan — parametr sifatida, SQL injection'siz (Funksiyalar va PL/pgSQL).- Natija — qaysi jadvaldan necha qator o'chgani. Bo'sh jadvallar ro'yxatga tushmaydi.
7.3 Haqiqiy hodisa: NOT NULL
Kanonning birinchi urinishida funksiya ism ustunini NULL qilmoqchi bo'ldi — mantiqan to'g'ri: ism shaxsiy ma'lumot. Natija:
\set VERBOSITY terse
UPDATE foydalanuvchilar SET toliq_ism = NULL WHERE id = 1;ERROR: null value in column "toliq_ism" of relation "foydalanuvchilar" violates not-null constraintXato kodi 23502 — NOT NULL buzildi: "foydalanuvchilar jadvalining toliq_ism ustuniga null qiymat — cheklov buzilmoqda". toliq_ism 28-qismdan beri majburiy. Ikki yo'l bor edi: cheklovni olib tashlash yoki joyiga neytral matn yozish. Kanon ikkinchisini tanladi — "O'chirilgan foydalanuvchi". Sabab: ism ko'rinadigan har joy (admin panel, eski izohlar) NULL ni kutmaydi va yangi xato chiqarardi. \set VERBOSITY terse faqat xato qatorini qoldirdi — aks holda Postgres butun qatorni, jumladan parol hash'ini, DETAIL ga chiqaradi. Bu ham saboq: xato xabarlari ham shaxsiy ma'lumotni logga olib chiqishi mumkin.
7.4 Ishga tushiramiz
Azizning hisobini anonimlashtiramiz:
SELECT hisob_anonimlashtir(1); hisob_anonimlashtir
--------------------------------
{"teglar": 8, "vazifalar": 11}
(1 row)O'n bitta vazifa va sakkizta teg o'chdi. Namunada Azizning 13 ta vazifasi bor edi — ikkitasini (1 va 2) «Yumshoq o'chirilganlarni tozalash» bo'limida allaqachon tozalagan edik. Sessiya, passkey va boshqa jadvallarda Azizning qatori yo'q edi — natijada ular ko'rinmaydi. Natija:
SELECT id, email, toliq_ism,
parol_hash IS NULL AS parolsiz,
bloklangan_vaqt IS NOT NULL AS bloklangan
FROM foydalanuvchilar WHERE id = 1;
SELECT tur, ip FROM auth_hodisalar WHERE foydalanuvchi_id = 1;
SELECT count(*) AS vazifalar FROM vazifalar WHERE egasi_id = 1; id | email | toliq_ism | parolsiz | bloklangan
----+---------------------------------+---------------------------+----------+------------
1 | ochirilgan-1@ochirilgan.invalid | O'chirilgan foydalanuvchi | t | t
(1 row)
tur | ip
-----------------+----
hisob_ochirildi |
(1 row)
vazifalar
-----------
0
(1 row)Qator qoldi, lekin unda Aziz haqida hech narsa yo'q. Yagona hodisa — "hisob o'chirildi", IP'siz. Mavjud bo'lmagan hisob uchun funksiya NULL qaytaradi — API buni xato emas, "allaqachon yo'q" deb tushunadi:
SELECT hisob_anonimlashtir(999) IS NULL AS topilmadi; topilmadi
-----------
t
(1 row)Tekshirib ko'ring: Ertaga
vazifalarga bog'langan yangieslatmalarjadvali qo'shildi (vazifa_id … ON DELETE CASCADE), foydalanuvchiga to'g'ridan bog'lanmagan. Anonimlashtirish uni qamraydimi?
Javob
Ha, bilvosita. Funksiya vazifalar dan Azizning qatorlarini o'chiradi, eslatmalar esa vazifaga CASCADE bilan bog'langan — u ham o'chadi. Xavf boshqa holatda: yangi jadval foydalanuvchiga ON DELETE SET NULL yoki chet kalitsiz bog'lansa, funksiya uni ko'rmaydi. Shuning uchun kanon testida hisob o'chgandan keyin shaxsiy jadvallarda nol qator qolgani tekshiriladi — keyingi darslarda yangi jadval qo'shilganda ham shu test yashil bo'lishi kerak.
8. API: DELETE /v1/auth/men
8.1 Controller
Foydalanuvchi hisobini o'zi o'chiradi. Yo'l — 28-qismdagi GET /v1/auth/men ning juftligi. Kanon kodi (telefon uchun qatorlarga bo'lindi, ma'nosi o'zgarmagan):
@Delete("men")
@HttpCode(204)
@JsonTana()
@SessiyaKerak()
async hisobniOchir(
@Body({ schema: HisobOchirishSxemasi }) body: { parol: string },
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
): Promise<void> {
const f = this.#sessiya(req);
const hisob = await this.d.ombor.idBilan(f.id);
if (hisob?.parolHash == null) {
throw new IlovaXatosi(409,
"Hisobda parol yo'q — avval parol o'rnating"
+ " (parolni tiklash orqali)",
{ kod: "parol-yoq", sarlavha: "Parol kerak" });
}
const t = await parolTekshir(hisob.parolHash, body.parol);
if (!t.togri) {
await this.#xizmat.hodisa({
tur: "hisob_ochirish_rad", foydalanuvchiId: f.id, ...mijoz(req),
});
throw new IlovaXatosi(403, "Parol noto'g'ri",
{ kod: "parol-notogri", sarlavha: "Parol noto'g'ri" });
}
await this.d.pool.query("SELECT hisob_anonimlashtir($1)", [f.id]);
this.d.sessiyalar.cookieOchir(res);
}Tana sxemasi — bitta maydon:
export const HisobOchirishSxemasi = z.strictObject({
parol: z.string().min(1).max(256),
});Butun yo'l bitta rasmda. Har tarmoq — alohida javob kodi:
flowchart TB
S["DELETE /v1/auth/men"] --> V{"parol maydoni bormi?"}
V -->|"yo'q"| E422["422"]
V -->|"ha"| P{"hisobda parol bormi?"}
P -->|"yo'q"| E409["409: avval parol o'rnating"]
P -->|"ha"| T{"parol to'g'rimi?"}
T -->|"yo'q"| E403["403 + hodisa yoziladi"]
T -->|"ha"| A["hisob_anonimlashtir()"]
A --> C["204, cookie o'chadi"]Diagrammaning o'ng tomonidagi uchta xato — bazaga tegmasdan qaytadi. Faqat oxirgi yo'l ma'lumotni o'zgartiradi.
Javob kodlari:
| Holat | Kod |
|---|---|
Tana yo'q yoki parol bo'sh |
422 (validatsiya, 25-qism kelishuvi) |
| Parol noto'g'ri | 403 + hisob_ochirish_rad hodisasi |
| Hisobda parol yo'q (faqat Google yoki Telegram bilan kirgan) | 409 |
| Muvaffaqiyat | 204, cookie o'chiriladi |
Sessiyalar alohida o'chirilmaydi: ular CASCADE ro'yxatida, funksiya ularni o'zi o'chiradi. Shuning uchun keyingi so'rov 401 oladi.
DB funksiyasini vazifalar_auth roli chaqiradi — migratsiyada unga faqat hisob_anonimlashtir uchun GRANT EXECUTE berilgan. Tozalash funksiyalari esa faqat vazifalar_ishchi ga.
8.2 Hujumchi nigohi
O'g'irlangan sessiya. Hujumchi foydalanuvchining cookie'sini oldi (Tokenni qayerda saqlash va CSRF). Parol tasdig'i bo'lmasa, u bitta so'rov bilan hisobni yo'q qilib, izlarni ham o'chiradi. Joriy parol so'rash — "qayta autentifikatsiya": sessiya yetarli emas, egasi ekanini yana bir bor isbotlash kerak.
Parol bilan DoS. max(256) tasodif emas. argon2 cheksiz uzun satrni ham xeshlaydi — 10 MB lik "parol" serverni soniyalab band qiladi (DoS va resurs tugashidan himoya). Uzunlik cheklovi xeshlashdan oldin ishlaydi.
Parol tanlash. 403 holatida hodisa yoziladi. 28-qismdagi tezlik cheklovi (API_CHEKLOVI) bu yo'lda ham ishlaydi — ketma-ket urinishlar to'xtatiladi.
409 holati. Faqat Google bilan kirgan foydalanuvchida parol yo'q. Kanon uni avval parol o'rnatishga yuboradi (parol tiklash orqali). Muqobil — Google bilan qayta kirishni so'rash. Kanon soddaroq yo'lni tanladi.
9. Rejali tozalash ishi
Funksiyalar bor, lekin ularni kim chaqiradi? 29-qismdagi BullMQ ishchisi (Cron va rejali vazifalar) — yangi rejali ish, har kuni 04:15 da, Toshkent vaqti bilan:
await this.tizim.upsertJobScheduler("saqlash-muddati",
{ pattern: "15 4 * * *", tz: "Asia/Tashkent" },
{ name: "saqlash-muddati" });Ish ishlovchisi uchala funksiyani vazifalar_ishchi roli bilan chaqiradi (telefon uchun bo'lindi):
case "saqlash-muddati": {
// saqlash siyosati: vazifalar_ishchi roli bilan
const q = d.ishchi;
const v = await q.query<{ n: string }>(
"SELECT ochirilganlarni_tozala() AS n");
const a = await q.query<{ n: Record<string, number> }>(
"SELECT auth_tozala() AS n");
const b = await q.query<{ b: string }>(
"SELECT hodisa_bolimlari_tozala() AS b");
return {
vazifalar: Number(v.rows[0]?.n ?? 0),
auth: a.rows[0]?.n ?? {},
hodisa_bolimlari: b.rows.map((r) => r.b),
};
}Nega 04:15? Kechasi yuk eng kam. 03:00 da zaxira olinadi, 03:30 da yetim fayllar tozalanadi, 04:00 da outbox. Ishlar bir-birining ustiga tushmasligi uchun vaqtlar surilgan. Number(…) — Postgres bigint ni pg satr qilib qaytaradi (node-postgres (pg) asoslari darsidagi tuzoq).
upsertJobScheduler — idempotent: ishchi har ishga tushganda chaqiriladi, lekin ish ikki marta yaratilmaydi.
10. Zaxira nusxalar va unutilish huquqi
Bitta savol ochiq qoldi: hisob bazadan o'chdi, lekin kechagi zaxirada u bor. Bu qonunni buzadimi?
Amaliyotdagi yondashuv: zaxiraning ham muddati bo'ladi (masalan, 7 kun). Shunda 7 kundan keyin o'chirilgan hisob zaxiralardan ham yo'qoladi. Zaxiradan tiklash kerak bo'lsa, tiklangandan keyin "o'chirilgan hisoblar ro'yxati" qayta qo'llanadi — anonimlashtirish funksiyasi shu ro'yxat bo'yicha yana chaqiriladi. Kanon bu jarayonni kodga kiritmadi. Buni docs/MAXFIYLIK.md ga yozib qo'yish kerak, toki tiklash mashqida unutilmasin.
11. Ko'p uchraydigan xatolar
| Noto'g'ri tasavvur | Haqiqat |
|---|---|
| "Saqlash siyosati hujjatda yozilgan — yetadi" | Kod har kuni bajarmasa, ma'lumot jim yig'iladi |
"Bitta katta DELETE — tezroq" |
Uzun qulf, WAL to'lqini, replika lagi; paketlab o'chiring |
"Hisobni o'chirish = DELETE qator" |
Statistika buziladi yoki chet kalit to'sadi; anonimlashtirish |
| "Jadvallar ro'yxatini funksiyaga yozib qo'yamiz" | Yangi jadval unutiladi; katalogdan oling va test bilan himoyalang |
| "SHA-256(email) — anonim qiymat" | Lug'at hujumi bilan qaytariladi; kalitli HMAC — psevdonim |
| "Psevdonimlangan ma'lumot — endi shaxsiy emas" | Kalit bor ekan — shaxsiy; GDPR ham shunday deydi |
"Arxivlash = DROP dan oldin nusxa" |
Arxivni tekshiring va uning ham muddatini belgilang |
12. Mashqlar
Mashqlar kurs/mashqlar/31/12-saqlash/ da. 2- va 3-mashq yechimlari Node 24.21.0 da tekshirilgan (biz ishga tushirdik).
1-mashq (oson): bo'shliqlarni to'ldiring
- Oxirgi
DELETEnecha qatorga tekkanini beradigan PL/pgSQL buyrug'i —GET n = ROW_COUNT - Bo'limni asosiy jadvaldan uzish —
ALTER TABLE … PARTITION - Chaqiruvchi emas, egasi nomidan ishlaydigan funksiya —
SECURITY - Hech qachon mavjud bo'lmaydigan standart domen —
. NOT NULLbuzilishining xato kodi —
Yechim
- DIAGNOSTICS — paket to'lmasa sikl tugaydi.
- DETACH — keyin
COPYvaDROP. - DEFINER —
search_pathni qotirish shart. - invalid —
ochirilgan-<id>@ochirilgan.invalid. - 23502 — kanonning birinchi urinishidagi hodisa.
2-mashq (o'rta): nima tozalanadi?
tozalanadiganlar(yozuvlar, bugun, siyosat) funksiyasini yozing. yozuvlar — { tur, sana } massivi (sana — "YYYY-MM-DD"), siyosat — tur → kunlar ({ sessiya: 7, hodisa: 180 }). Funksiya muddati o'tgan yozuvlarni qaytarsin; siyosatda yo'q tur — hech qachon o'chirilmaydi (ehtiyotkor sukut). Kunlarni Date.UTC bilan hisoblang. Ishora: filter (filter), in operatori.
Yechim
const KUN = 24 * 60 * 60 * 1000;
const kun = (s) => {
const [y, m, d] = s.split("-").map(Number);
return Date.UTC(y, m - 1, d) / KUN;
};
function tozalanadiganlar(yozuvlar, bugun, siyosat) {
return yozuvlar.filter((y) =>
y.tur in siyosat && kun(bugun) - kun(y.sana) > siyosat[y.tur]);
}
const yozuvlar = [
{ tur: "sessiya", sana: "2026-10-20" },
{ tur: "sessiya", sana: "2026-10-30" },
{ tur: "hodisa", sana: "2026-04-01" },
{ tur: "hodisa", sana: "2026-06-01" },
{ tur: "audit", sana: "2020-01-01" },
];
const siyosat = { sessiya: 7, hodisa: 180 };
console.log(tozalanadiganlar(yozuvlar, "2026-11-03", siyosat));Konsolda:
[
{ tur: 'sessiya', sana: '2026-10-20' },
{ tur: 'hodisa', sana: '2026-04-01' }
]2026-10-20 dagi sessiya 14 kunlik (> 7), 10-30 dagisi — 4 kunlik, qoladi. 04-01 dagi hodisa 216 kunlik, 06-01 dagisi — 155. 2020-yilgi audit yozuvi siyosatda yo'q — tegilmadi. Bu ataylab: siyosatga yozilmagan narsani o'chirish — tiklab bo'lmaydigan xato, o'chirmaslik esa tuzatsa bo'ladigan kamchilik. Date.UTC yoz vaqti va mahalliy soat zonasi tuzog'idan qutqaradi (Backend'da sana va vaqt).
3-mashq (qiyin): k-anonimlik
Statistik eksportda ism yo'q, lekin yosh va tuman bor. Ular birgalikda odamni aniqlab qo'yishi mumkin. Tumanda 34 yoshli faqat bitta foydalanuvchi bo'lsa — u topildi. Bu xavfni k-anonimlik o'lchaydi. Talab: har (yosh guruhi, tuman) juftligida kamida k ta odam bo'lsin. kAnonim(qatorlar, k, qadam) funksiyasini yozing: yoshni qadam yillik oraliqqa aylantirsin (30–39), guruhlarni sanasin va k dan kichik guruhlarni qaytarsin. Ishora: Map (Map).
Yechim
function oraliq(yosh, qadam) {
const bosh = Math.floor(yosh / qadam) * qadam;
return `${bosh}–${bosh + qadam - 1}`;
}
function kAnonim(qatorlar, k, qadam) {
const guruhlar = new Map();
for (const q of qatorlar) {
const kalit = `${oraliq(q.yosh, qadam)} ${q.tuman}`;
guruhlar.set(kalit, (guruhlar.get(kalit) ?? 0) + 1);
}
return [...guruhlar].filter(([, soni]) => soni < k);
}
const qatorlar = [
{ yosh: 23, tuman: "Chilonzor" }, { yosh: 27, tuman: "Chilonzor" },
{ yosh: 34, tuman: "Yunusobod" }, { yosh: 31, tuman: "Chilonzor" },
{ yosh: 36, tuman: "Chilonzor" }, { yosh: 22, tuman: "Chilonzor" },
];
console.log(kAnonim(qatorlar, 2, 10));
console.log(kAnonim(qatorlar, 2, 20));Konsolda:
[ [ '30–39 Yunusobod', 1 ] ]
[ [ '20–39 Yunusobod', 1 ] ]10 yillik qadamda Yunusoboddagi 34 yoshli foydalanuvchi yolg'iz — u aniqlanadi. Qadamni 20 yilga kattalashtirish ham yordam bermadi: tumanda boshqa odam yo'q. Yechim — tumanni ham umumlashtirish ("Toshkent") yoki bu qatorni eksportdan chiqarish. k-anonimlik ko'rsatadi: ism o'chirilgani ma'lumotni anonim qilmaydi. Umumlashtirish esa aniqlikni yeydi — bu doim muvozanat.
13. Loyiha qadami: saqlash muddati va hisobni o'chirish
Oldingi darsda docs/MAXFIYLIK.md ni yozgan edik. Bu darsda unga kod qo'shiladi va loyihaning shu bosqichi commit bo'ladi.
1. SQL va migratsiya. sql/302-saqlash-muddati.sql — uchta funksiya (ochirilganlarni_tozala, auth_tozala, hisob_anonimlashtir) va huquqlar. Oxirida:
REVOKE EXECUTE ON FUNCTION
ochirilganlarni_tozala(interval, integer),
auth_tozala(interval),
hisob_anonimlashtir(bigint) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION
ochirilganlarni_tozala(interval, integer),
auth_tozala(interval) TO vazifalar_ishchi;
GRANT EXECUTE ON FUNCTION hisob_anonimlashtir(bigint)
TO vazifalar_auth;REVOKE … FROM PUBLIC muhim: Postgres sukut bo'yicha yangi funksiyani hammaga chaqirishga ruxsat beradi. SECURITY DEFINER funksiya uchun bu xavfli — har qanday rol hisobni anonimlashtira olardi. Prisma migratsiyasi 20261102110000_saqlash_muddati — sql/302 ning aynan nusxasi; sxema farqi tekshiruvi buni nazorat qiladi.
2. Auth. src/auth/sxema.ts ga HisobOchirishSxemasi, auth.controller.ts ga hisobniOchir, ombor.ts dagi hodisa turlariga hisob_ochirish_rad va hisob_ochirildi.
3. Ishchi. navbat.ts ga saqlash-muddati rejali ishi, ishlovchilar.ts ga uning ishlovchisi.
4. Hujjatlar. API.md ga yangi qism — "Prodakshn va AI (31-qism)" — va DELETE /v1/auth/men qatori. package.json versiyasi — 4.0.0-alfa.1: 31-qism loyihaning to'rtinchi katta versiyasi.
5. Test. test/saqlash.test.ts — uchta test: muddat chegarasi (40 kunlik o'chdi, 2 kunlik qoldi, paket = 1), ishchi ishi vazifalar_ishchi huquqlari bilan, DELETE oqimi. Uchinchisidan parcha:
await request(app).delete("/v1/auth/men").set("Cookie", aziz)
.send({}).expect(422);
await request(app).delete("/v1/auth/men").set("Cookie", aziz)
.send({ parol: "noto'g'ri-parol-123" }).expect(403);
await request(app).delete("/v1/auth/men").set("Cookie", aziz)
.send({ parol: PAROL }).expect(204);
await request(app).get("/v1/vazifalar").set("Cookie", aziz)
.expect(401);Keyin test bazaga kiradi va tekshiradi: email ochirilgan-<id>@…, vazifalar, teglar, sessiyalar da nol qator, IP tozalangan, statistika hodisalari qolgan, Dilnozaning vazifasiga tegilmagan, eski parol bilan kirish — 401.
Kanon tekshiruvi (Docker'dagi PostgreSQL 18.6, Node 24.21.0):
--- npm run tip
exit 0 (7208 ms)
--- npm run lint
exit 0 (15138 ms)
--- npm run build
exit 0 (8188 ms)
--- npm run test
Test Files 57 passed | 1 skipped (58)
Tests 303 passed | 1 skipped (304)
--- prisma migrate deploy (yangi baza): 22 migrations found
--- schema.prisma ↔ migratsiyalar (migrate diff): []
--- migratsiyalar va sql/ sxemasi farqi: 0 qator303 test o'tdi, bittasi — Testcontainers — alohida bayroq bilan yuradi. Migratsiyalar 22 ta, Prisma sxemasi bilan farq yo'q. Jonli serverdagi mavjud yo'llarning javob kodlari oldingi bosqich bilan aynan.
Docker'siz takrorlash: SQL funksiyalarni o'z Postgres'ingizda yaratib, shu darsdagi SQL bloklarini ketma-ket bajaring — natija bir xil chiqishi kerak (hisob_anonimlashtir dagi son namunangizga bog'liq).
Commit:
git add -A
M="feat: saqlash muddati va hisobni o'chirish —"
M+=" tozalash funksiyalari, anonimlashtirish,"
M+=" DELETE /v1/auth/men, MAXFIYLIK.md"
git commit -m "$M"14. Real ishda
- Qayerda uchraydi: "Hisobni o'chirish" tugmasi — zamonaviy ilovada foydalanuvchi kutadigan odatiy imkoniyat. Bank va telekomda — qonun bilan belgilangan minimal saqlash muddati ham bor: u erda o'chirish emas, saqlash talab qilinadi.
- Jamoada: yangi jadval qo'shilganda kod tekshiruvida savol — "bu jadvalning saqlash muddati qancha va hisob o'chirilganda nima bo'ladi?"
- Intervyu savollari: "Katta jadvaldan eski ma'lumotni qanday o'chirasiz?", "Anonimlashtirish va psevdonimlashtirish farqi?", "Unutilish huquqini zaxira nusxalarda qanday bajarasiz?", "SECURITY DEFINER funksiyasining xavfi nima?"
- Vositalar:
pg_partman(bo'limlarni avtomatik boshqarish),pg_cron(baza ichida rejali ish), Parquet va S3 (arxiv). Kanon ularni sinamadi: o'z funksiyalari va 29-qismdagi ishchi yetarli bo'ldi.
Xulosa
- Saqlash siyosati — har ma'lumot uchun muddat va uni har kuni bajaradigan kod.
- Katta o'chirishni paketlab bajaring; vaqt qatorlarida esa butun bo'limni
DETACHvaDROPqiling — o'lchovda 17 baravar tez. - Arxivlash:
DETACH→ nusxa → tekshiruv →DROP; arxivning ham muddati bo'ladi. - Psevdonim kalit bilan qaytariladi va shaxsiy ma'lumot bo'lib qoladi; anonimlik — odamni topib bo'lmaydigan holat.
- Hisobni o'chirish — anonimlashtirish: qator qoladi, unga bog'liq hamma narsa katalog bo'yicha ketadi.
DELETE /v1/auth/men— joriy parol bilan, uzunlik cheklovi bilan;SECURITY DEFINERfunksiyalar —REVOKE FROM PUBLICva aniqGRANTbilan.
Keyingi dars: LLM API asoslari — vazifalar-api ga AI xususiyatini qo'shishni boshlaymiz: xabarlar, tokenlar, narx va API kalitni xavfsiz saqlash.
Manbalar
- PostgreSQL 18 hujjati — Table Partitioning (DETACH PARTITION): https://www.postgresql.org/docs/18/ddl-partitioning.html
- PostgreSQL 18 hujjati — COPY: https://www.postgresql.org/docs/18/sql-copy.html
- PostgreSQL 18 hujjati — CREATE FUNCTION, SECURITY DEFINER xavfsizligi: https://www.postgresql.org/docs/18/sql-createfunction.html
- PostgreSQL 18 hujjati — System Catalogs, pg_constraint: https://www.postgresql.org/docs/18/catalog-pg-constraint.html
- RFC 2606 — Reserved Top Level DNS Names (
.invalid): https://www.rfc-editor.org/rfc/rfc2606 - GDPR, 4-modda (5) — psevdonimlashtirish ta'rifi, 26-kirish qismi — anonim ma'lumot: https://eur-lex.europa.eu/eli/reg/2016/679/oj (EUR-Lex avtomatik so'rovlarni to'sadi; matn 2026-11-03 da qabul qilingan nusxa bo'yicha tekshirildi: https://www.legislation.gov.uk/eur/2016/679/article/4/adopted)
- Kanon o'lchovi (IlmHamroh, 2026-11):
vazifalar-apishu bosqichi (PostgreSQL 18.6, Node 24.21.0), bo'limlash o'lchovi. SQL bloklari, HMAC modeli va mashq yechimlari — biz ishga tushirdik (2026-11-03, PostgreSQL 17.6, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!