알림 4건 중 긴급 조치 필요는 beta 단 1건(커넥션 누수). beta elysia 메모리 99% CRITICAL은 오진단(실제 99%는 postgres-1 page cache, elysia는 12%·OOM 0). alpha 커넥션은 DB_POOL_MAX=20 + blue-green 2중 가동에 의한 구조적 정원초과(누수 아님). worker 폭주는 없음 — DB는 pool당 20 hard-cap. 다만 beta Redis(1.52M keys·peak 5G·noeviction)가 모니터에 안 잡힌 진짜 잠재 리스크.
판정: 커넥션 누수 아님 — 풀 사이징 + blue-green 2중 가동에 의한 구조적 정원초과. 활성 쿼리 0건, idle-in-transaction 0건. DB는 한가한데 풀이 커넥션만 점유.
| 점유 주체 (application_name) | role / host | conns | 정체 |
|---|---|---|---|
| rinda-api | postgres / .15 | 20 | elysia 컨테이너 A (pool max 20 만재) |
| rinda-worker | postgres / .16 | 11–20 | BullMQ 워커 |
| rinda-api | postgres / 115.91.133.187 | 12–13 | 외부 dev 머신 직결 |
| rinda-api-analytics | analytics_reader / .15 | 5 | 분석 풀 (상한 정상) |
| (background) | — | 8 | autovacuum/checkpointer 등 정상 |
DB_POOL_MAX=20 env 오버라이드 — 코드 기본값은 10 (config.ts:177)-1161(08:33) + -1162(08:39) 둘 다 running → API만 풀 40판정: 진짜 커넥션 누수. 죽은 worker 컨테이너(IP 172.18.0.8)의 DB 풀 20개가 graceful shutdown 없이 좀비로 잔존. 재배포마다 +20 누적되는 구조.
| client_addr | app | backend_start | conns | 정체 |
|---|---|---|---|---|
| 172.18.0.11 | rinda-worker | 08:36:38 | 20 | 현 worker (정상) |
| 172.18.0.8 | rinda-worker | 08:18–08:34 | 20 | 고아 — 존재하지 않는 IP, ClientRead 영구대기 |
| 172.18.0.9 | rinda-api | — | ~23 | 현 elysia (정상) |
pool.end() 미호출 → 구 풀 20개 좀비화idle_session_timeout·tcp_keepalives 미설정 → half-open 자동회수 안 됨SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE client_addr='172.18.0.8'; (20개 회수→즉시 해소)worker.close() + pool.end() 연결 (재발 차단·근본해결)idle_session_timeout(10min) + tcp_keepalives_idle 설정 — 좀비 자동회수 방어선판정: 긴급 아님 — 모니터의 컨테이너 오매핑 + cache 포함 메트릭. 99%로 잡힌 건 elysia가 아니라 postgres-1이고, 그마저도 누수가 아니라 reclaimable page cache.
| 컨테이너 | Mem | % | 분석 |
|---|---|---|---|
| elysia-server (알림 대상) | 752 MiB / 6 GiB | 12% | 건강. anon 909MB, heap 에러 0 |
| postgres-1 (실제 99%) | 7.21 GiB / 8 GiB | 90% | anon 1GB + page cache 7.16GB + shared_buffers 2GB |
| redis-1 | 1.52 GiB / 10 GiB | 15% | 정상 |
max 10.4M = harmless reclaim(OOM 아님). 호스트 30GiB 중 21GiB available.memory.current raw = 회수가능 page cache 포함. 실 working-set 아님memory.current − inactive_file 로 계산 (postgres 99%→30~40%, 반복 false CRITICAL 차단)판정: worker 폭주에 의한 커넥션/메모리 증가 가설 기각. DB pool은 프로세스당 20 hard-cap이라 concurrency가 커넥션을 곱하지 않음. 단, 모니터에 안 잡힌 beta Redis가 진짜 잠재 리스크.
| 지표 | alpha | beta | 비고 |
|---|---|---|---|
| Redis used / peak | 332M | 1.27G / 5.08G | maxmemory 8G · noeviction |
| Redis keys (DBSIZE) | 236K | 1.52M | retention + stale send-acc 잔여 |
| connected / blocked clients | 8 / 0 | 564 / 179 | bzpopmin = BullMQ 정상 blocking poll |
| worker DB conns | 40 | 20 | = 컨테이너수 × pool 20 (cap 작동) |
| 최대 active job (큐) | 3 | 25 | lead-on-demand-enrich = concurrency 정확히 일치 |
send-acc-* 키 잔여lead-on-demand-enrich waiting 2,426 (draining 중, failed 1 — 양호)removeOnComplete/removeOnFail retention 축소 + 죽은 send-acc-* 키 GC (live는 1~3개뿐)maxmemory-policy 안전장치 또는 >6G 알림 추가 — noeviction OOM 캐스케이드 방지lead-on-demand-enrich 배수 추세 재확인 — 평탄/증가 시 enrich 상류 점검worker.ts가 부팅하는 start*Worker() = 총 96종 + worker-buyersearch 전용 컨테이너 + per-account 동적 spawn(send-acc-*). 전부 단일 20-slot DB pool 공유 → concurrency 합과 무관하게 커넥션은 컨테이너당 20 cap.
| 지표 | 값 | 의미 |
|---|---|---|
| 총 워커 종류 | 96 | + buyersearch 컨테이너 + 동적 send-acc-* |
| concurrency: 1 (단일) | ~45 | 절반이 직렬 — 커넥션 압박 거의 없음 |
| concurrency: 2~5 | ~30 | 경량 병렬 |
| 고-concurrency 소수 | 4 | sequence-email 40 · lead-on-demand-enrich 25 · web-extraction 20 · personalized-email-streaming 20 |
| 도메인 | 수 | 주요 워커 |
|---|---|---|
| 시퀀스·발송 | 22 | sequence-email(40)·sequence-email-loader·sequence-activation·sequence-lifecycle·sequence-proposal-{ai-completion,commit,preview-email}·enrollment-resume·bulk-email·personalized-email-streaming(20)·outbox-dispatcher·followup-email·follow-up-{auto-send,daily-digest,draft-generate}·nudge-email·auto-nudge·reply-nudge·re-engagement-nudge·ooo-resend·recipient-send-time-learning·per-account-worker-manager |
| 워밍업 | 6 | warmup-{send,scheduler,engage,reputation,recovery,bounce-retry} |
| AI 에이전트 | 10 | agent-{approval-expiry,approval-handler,goal-orchestrator,memory-archival,memory-extract,mission,mission-postmortem,wisdom-curator}·sales-advisor-auto·sales-autopilot |
| 리드·바이어 발굴/인리치 | 16 | buyer-search-{agent-v1,pipeline,pro}·lead-discovery-{analyze,search}·lead-on-demand-enrich(25)·lead-csv-export·lead-pipeline-gmail-backfill·hot-lead-evaluation·visitor-enrich·web-extraction(20)·group-analysis-v2·group-assessment·scoring-preset-generate·legal-address-suggest·business-card-ocr |
| 답장·CRM 분류 | 6 | reply-classification-outcomes·reply-tags-auto-classify·crm-stage-classify·crm-email-backfill·thread-summary-reconcile·unreplied-reply-digest |
| LinkedIn SDR | 5 | linkedin-sdr-{outreach,profile-view,scheduler}·unipile-inbox-poll·unipile-inactive-cleanup |
| 이메일 인프라·평판 | 6 | ses-monitor·ses-suppression-sync·sendgrid-suppression-sync·verify-sending-domain·dns-health-check·api-key-monitor |
| 빌링·라이프사이클 | 9 | billing-payment·paddle-webhook-debug·trial-{expiration,intervention}·proxy-workspace-expiry·account-deletion·customer-group-deletion·dsar-fulfillment·onboarding-auto-generate |
| 알림·브로드캐스트·다이제스트 | 7 | admin-notification-broadcast·board-{email-broadcast,notification}·release-note-{email,notification}·weekly-digest·customer-share-report-refresh |
| 콘텐츠·퍼블리시 | 4 | blog-pipeline·cross-publish·recording-transcription·standup-translation |
| 벤치마크·시스템 | 5 | benchmark-pipeline·benchmark-quick-run·server-health-monitor·db-cleanup·test |