PG Insight
Ochiq multi-tenant monitoring xizmati: har kim ro'yxatdan o'tib, o'z PostgreSQL bazasini qo'shadi va faqat o'zi qo'shgan bazalarni ko'radi
Bu loyiha nimani isbotlaydi
- Tenant izolyatsiyasi: global guard har bir :targetId route'ida egani tekshiradi, begona target'ga 403 emas, 404 qaytaradi; himoyasiz mutating route qo'shilsa test yiqiladi
- SSRF himoyasi: DNS javobidagi barcha IP tekshiriladi, ulanish tekshirilgan IP'ga qotiriladi (TLS SNI saqlanadi); production'da platform-db, 169.254.169.254, 127.0.0.1 rad etildi
- Ixtiyoriy SQL'ni cheklash: EXPLAIN 6 qatlamda himoyalangan, READ ONLY va 5 s statement_timeout bilan; pg_sleep 0.05 s da, generate_series(1, 10000000000) 5.4 s da 400 bilan to'xtatildi
- Kuzatiladigan bazaga yukni o'lchadim: 10 daqiqada 1 yadroning 0.06% i, konteyner CPU'si +0.3%
- Operatsiya: SHA-tagli deploy, health tekshiruvidan keyingina yozib qo'yish, ikki yo'nalishli rollback mashqi
- Rol
- Yakka: dizayn, backend, frontend, xavfsizlik, testlar, deploy va operatsiya (81 commit)
- Muddat
- 2026.08 – 2026.10
- Texnologiyalar
- NestJS
- TypeScript
- Prisma
- PostgreSQL
- TimescaleDB
- Socket.IO
- React
- Vite
- Tailwind CSS
- Recharts
- Jest
- Vitest
- Docker
- Caddy
Asosiy raqamlar
| Ko'rsatkich | Qiymat | Turi | Dalil |
|---|---|---|---|
| 10 daqiqada target'ga yuborilgan so'rovlar | 2 430 | O'lchangan | Dalilni ko'rish |
| Target'ga yuk (1 yadroga nisbatan) | 0,06 % | O'lchangan | Dalilni ko'rish |
| So'rovning o'rtacha ijro vaqti | 0,15 ms | O'lchangan | Dalilni ko'rish |
| Yig'ilgan ma'lumot kechikishi | 1,4 s | O'lchangan | Dalilni ko'rish |
| EXPLAIN ichidagi pg_sleep'ni to'xtatish vaqti | 0,05 s | O'lchangan | Dalilni ko'rish |
| Avtomatik testlar (188 backend + 49 frontend) | 237 | O'lchangan | Dalilni ko'rish |
Hal qilinadigan muammo
Ishlab turgan PostgreSQL'dagi sekin so'rovlar, lock kutishlari, bloat va replikatsiya kechikishi turli vosita va SQL orqali alohida tekshiriladi. Ularni bitta xizmatga yig'dim; bu begona bazalarga ulanadigan ochiq xizmat bo'lgani uchun tenant izolyatsiyasi, SSRF himoyasi va ixtiyoriy SQL'ni cheklash boshidanoq kerak edi.
Arxitektura
Collector'lar har bir bazadan 3 soniya–5 daqiqa oralig'ida metrika yig'ib, TimescaleDB'ga (10 ta hypertable) yozadi; jonli sahifalar esa bazaga to'g'ridan-to'g'ri so'rov yuboradi. Foydalanuvchi, target, alert va audit Prisma 7 boshqaradigan platforma DB'da (PostgreSQL 16). NestJS 11 global guard'lari (Throttler → JWT → TargetAccess) har bir :targetId route'ida egalikni tekshiradi, target so'rovlari esa target boshiga pool'da (maks. 5) xom SQL bilan bajariladi. Rollar ikkita: admin (birinchi hisob) va user (o'z target'lari, 3 tagacha, faqat ommaviy manzillar); UI koreys, ingliz va o'zbek tillarida.
Ma'lumotlar modeli
Asosiy cheklovlar va indekslar
- users.role DEFAULT 'user', bcrypt 12
- refresh_tokens.tokenHash UNIQUE: SHA-256 해시만 저장, 사용할 때마다 교체
- targets.passwordEncrypted: AES-256-GCM, createdByUserId = 테넌트 소유자, sslMode DEFAULT 'require'
- hypertable 10개, 1일 chunk; connection_metrics, query_metrics는 7일 뒤 압축, retention 7–30일
- audit_logs: 로그인, 가입, 대상 변경, 모든 EXPLAIN 기록
Asosiy loyihaviy qarorlar
Demo emas, ochiq multi-tenant xizmat
- Vaziyat
- Har kim o'z bazasini kuzata olsin.
- Qaror
- Ro'yxatdan o'tish ochiq, har bir foydalanuvchi faqat o'zi qo'shgan target'larni ko'radi.
- Evaziga
- Tenant izolyatsiyasi, SSRF, kvota va rate limit majburiy bo'ldi.
Egalik tekshiruvi controller'da emas, global guard'da
- Vaziyat
- Yangi route qo'shilganda tekshiruvni unutib bo'lmasin.
- Qaror
- Throttler → JWT → TargetAccess tartibidagi global guard'lar har bir :targetId route'ini tekshiradi.
- Evaziga
- Boshqa ID'li route'lar uchun @TargetAccess dekoratori kerak.
Begona target'ga 403 emas, 404
- Vaziyat
- Target mavjudligini ham oshkor qilmaslik kerak.
- Qaror
- Ruxsat bo'lmasa, mavjud bo'lmagan target bilan bir xil 404 qaytadi.
- Evaziga
- Admin uchun ham mavjudlik alohida tekshiriladi.
Host'ni resolve qilib, IP'ga qotirish
- Vaziyat
- Tekshiruv va ulanish paytidagi DNS javobi farq qilsa (DNS rebinding), ichki tarmoqqa kirish mumkin.
- Qaror
- DNS javobidagi barcha IP net.BlockList bilan tekshiriladi va ulanish tekshirilgan IP'ga to'g'ridan-to'g'ri qilinadi.
- Evaziga
- TLS uchun SNI qo'lda beriladi; o'z-o'zini hosting uchun TARGET_ALLOW_PRIVATE_HOSTS.
EXPLAIN qoladi, lekin READ ONLY + 5 s + funksiya denylist
- Vaziyat
- Egasi o'z bazasida reja ko'ra olishi kerak, lekin EXPLAIN ANALYZE so'rovni haqiqatan bajaradi.
- Qaror
- 6 qatlamli tekshiruvdan keyin READ ONLY tranzaksiyada statement_timeout va lock_timeout bilan bajariladi.
- Evaziga
- Vaqt va funksiya cheklovi sabab ba'zi to'g'ri so'rovlar ham rad etilishi mumkin.
Yangi target uchun standart sslmode=require
- Vaziyat
- pg driver'da libpq'dagi prefer (TLS, bo'lmasa oddiy ulanish) yo'q, shuning uchun standart prefer amalda shifrlanmagan ulanish edi.
- Qaror
- Yangi target'lar va DB default'i require'ga o'zgartirildi (47287b4).
- Evaziga
- TLS'siz bazada foydalanuvchi disable'ni o'zi tanlaydi.
Migratsiyalar orqaga qaytarilmaydi
- Vaziyat
- Rollback faqat image'ni almashtirsa, tez va xavfsiz.
- Qaror
- Har bir migratsiya qo'shimcha shaklda (avval qo'shish, keyin tozalash).
- Evaziga
- Har migratsiya oldingi kod bilan mos bo'lishi kerak.
Oldin va keyin
- Nima o'zgartirildi
- Orchestrator onModuleInit'da pool'lar ulanib bo'lishidan oldin ishga tushardi; endi onApplicationBootstrap'da ishga tushadi va restart tartibini tekshiradigan lifecycle testi qo'shildi (6246ce2). Shu bosqichda lock so'rovidagi RENAME xatosi, metrika vaqti, pauza holati, TLS default'i va /health 500 → 503 ham tuzatildi.
Nosozlik tarixi
- Belgi
- API restartdan keyin hech bir target kuzatilmadi; overhead o'lchovida PG Insight'dan 0 ta so'rov chiqdi.
- Sabab
- Log Starting collection for 0 active target(s) ni ko'rsatdi: orchestrator onModuleInit'da pool'lar ulanishidan oldin ishga tushgan, 14:23–17:25 UTC oralig'idagi ma'lumot yo'qoldi.
- Yechim
- Ishga tushish onApplicationBootstrap'ga ko'chirildi (6246ce2); restartdan keyin ikkala target ham darhol yig'iladi.
- Takrorlanmasligi uchun
- Xatoni qayta chiqaradigan lifecycle testi qo'shildi, u pauzadagi target'lar o'tkazib yuborilishini ham tekshiradi; yangi testlar eski kodda yiqilishi tasdiqlangan.
Test va sifat
- Testlar soni
- 237
- Qamrov
- —
- CI
- GitHub Actions (워크플로 준비, billing 문제로 현재 중단)
- Test vositalari
- Jest, Supertest, Vitest, 실제 PostgreSQL 16
Deploy va ekspluatatsiya
- Deploy muhiti
- iwinv VPS, Docker Compose: api (NestJS, root bo'lmagan), web (Caddy + React statik fayllari, /api/* va /socket.io/* proxy), platform-db (PostgreSQL 16), metrics-db (TimescaleDB). DB portlari tashqariga ochilmagan. deploy.sh: conflict belgisi tekshiruvi → git merge --ff-only → api va web SHA tegi bilan build → health 45 × 2 s kutiladi → faqat muvaffaqiyatda APP_TAG va .deploy-history yangilanadi. API konteyneri ishga tushganda prisma migrate deploy bajaradi.
- Backup
- Server umumiy cron backup'i (har kuni 03:00, 7 kun) platform-db va metrics-db'ni qamraydi. Foydalanuvchi bazalarining pg_dump backup'ini faqat admin yarata oladi.
- Rollback
- rollback.sh build'siz mavjud image bilan api va web'ni qayta yaratadi, avval ikkala image borligini tekshiradi. Production'da ikki yo'nalishda (71dceaf ↔ d587ce6) sinaldi, ikkalasida ham health 200.
- Monitoring
- Real foydalanish: LedgerCore va FlashDrop production bazalari target sifatida qo'shilib kuzatiladi (o'sha serverdagi ichki Docker tarmog'i). /api/v1/health (DB ishlamasa 503), audit loglar (login, sign-up, target o'zgarishi, har bir EXPLAIN), sign-up IP boshiga soatiga 5 ta, login daqiqasiga 10 ta. GitHub private vulnerability reporting (SECURITY.md)
Skrinshotlar
Cheklovlar va xulosalar
Hozirgi cheklovlar
- Hisob boshqaruvi yo'q: email tasdiqlash, parolni tiklash va hisobni o'chirish hali qilinmagan
- Diagnostika, xavfsizlik natijalari, tavsiyalar va xato xabarlari backend'dan faqat inglizcha keladi (UI uch tilda)
- connection_by_app hypertable'iga retention qo'yilmagan va cheksiz o'sadi; metrika DB'ning migratsiya mexanizmi yo'q (init-platform.sql faqat volume birinchi yaratilganda ishlaydi)
- Real-time socket JWT va egalik bilan himoyalangan, lekin UI hali subscribeToTarget'ni chaqirmaydi va REST polling bilan yangilanadi
- Monitoring so'rovlari optimallashtirilmagan: pg_settings har 10 s qayta o'qiladi, target boshiga pool 5 ta; kesh va 2–3 ulanishli pool keyingi qadam
- query_metrics hajmi (target boshiga soatiga ~4 MB) 22 daqiqalik oynadan taxmin; 24 soatlik o'lchov hali qilinmagan
- Mavjud ikki target (LedgerCore, FlashDrop) ichki Docker tarmog'ida prefer bilan, ya'ni shifrlanmagan ulanishda (trafik serverdan chiqmaydi)
- CI workflow tayyor, lekin GitHub Actions billing sababli to'xtagan, testlar lokalda yuritiladi. Kalit almashtirish skripti (rotate-encryption-key.ts) Prisma 7 adapter'isiz yozilgan, production'da sinalmagan; Playwright E2E yo'q
Asosiy kod
pg-insight-back/src/auth/target-access.guard.tsGlobal egalik tekshiruvi; begona target — 404
pg-insight-back/src/auth/admin-routes.spec.tsHimoyasiz mutating route'ni metadata orqali topadigan test
pg-insight-back/src/targets/target-host.policy.tsSSRF: BlockList, DNS'dagi barcha IP, IP'ga qotirish + SNI
pg-insight-back/src/live/live-query.service.tsEXPLAIN: 6 qatlam, READ ONLY, statement_timeout / lock_timeout
pg-insight-back/src/targets/target-pool.manager.tsTarget pool'lari, shifrdan ochilgan parol bilan reconnect, paused holatni saqlash
pg-insight-back/src/collector/collector.orchestrator.tsonApplicationBootstrap, xatoda reconnect, pauzadagilarni o'tkazib yuborish
pg-insight-back/src/real-time/real-time.gateway.tsHandshake JWT, room obunasida egalik
pg-insight-back/docker/init-platform.sqlHypertable'lar, siqish va retention siyosatlari
scripts/measure-overhead.shTarget'ga yukni o'lchash skripti
scripts/deploy.sh, scripts/rollback.shConflict guard, SHA tegi, health'ga bog'langan yozuv
Hozirgi holat va keyingi qadamlar
Yig'ish kuzatilayotgan bazaga 10 daqiqada 2 430 so'rov va jami 353.4 ms ijro vaqti berdi, ya'ni 1 yadroning ~0.06% i. Haqiqiy production bazalarda (LedgerCore, FlashDrop) 5 ta, kod ko'rigi va testlarda yana 3 ta xato topildi; har biri avval test bilan qayta chiqarilib, keyin tuzatildi. Boshqa holatlar: real-time broadcast xatosi collector'larni to'xtatgani (b9825e4), lock so'rovidagi RENAME sintaksis xatosi (b9825e4), connection metrikasiga sessiya boshlangan vaqt yozilgani (d587ce6, 891 noto'g'ri qator o'chirildi), pauzadagi target restartdan keyin o'zi yoqilgani (41569c7), parol o'zgarganda SSL yo'qolgani va prefer amalda shifrlamagani (41569c7, 47287b4), birinchi rollback mashqida eski image yo'qligi sababli skript xavfsiz to'xtagani.