Rinda 사용량 제한 시스템 · 전수분석 & 최적화 제안

사이드바 위젯 → 코드·플랜·실 DB(alpha·beta) 전수분석 · 2026 최적패턴 기준 해결안

2026-07-02 env: alpha + beta 제한 컬럼 13종 SSOT: billing_plans PG 18
TL;DR

1. 플랜별 제한 (alpha = beta 동일)

플랜검색 월/일/시바이어(월)발송 월/일체험일금액(원)
14일 무료체험
← 현재 화면
10 / 3 / 25005,000 / 200140
Pro 월정기100 / 10 / 31,0005,000 / 2000198,000
Team 월정기300 / 20 / 53,00050,000 / 2,0000495,000
Enterprise 맞춤무제한무제한무제한0견적
Internal Infinite (내부)무제한무제한무제한00

NULL=무제한. tier 저장 2단 분리 — 숫자 한도는 billing_plans(subscriptions.plan_id 연결), 기능 접근권은 iam_tier_boundaries(tier→policy 매핑).

2. 제한값별 참조 지도 (전 컬럼)

컬럼trial차단 지점집행 방식상태
buyer_search_{h/d/m}_limit2/3/10rate-limit.service:191/201/211파생집계 (jobs COUNT)활성
leads_monthly_limit500usage.service:159저장카운터우회 3경로
products_limit3plan-limits.service:263실시간 행수활성
emails_daily_limit200send-email:476 (Redis+DB)파생집계+Redis활성
emails_monthly_limit5,000send-email:483저장카운터활성
email_accounts_limit1email-account.service:446실시간 행수활성
sending_domains_limitnullsending-domains.routes:109실시간 행수활성
workspaces_limit1workspace-auth.service:265실시간 행수활성
workspace_members_limitnullplan-limits.service:350 (grandfather)실시간 행수활성
csv_download_enabledfalseplan-limits:395 + FE불리언 게이트활성
ai_monthly_budget_usd_cents$10ai-budget.service:174예산 누적합활성
trial_days14recurring-billing:130 → period_end시점 계산활성(최다)
has_dedicated_manager없음전시용(dead)

중앙 SSOT = getPlanLimits() (5분 인메모리 캐시 + Redis pub/sub 무효화). 검색 차단은 assertBuyerSearchAllowed() 단일 함수.

3. 기능별 표시값 ↔ 실제 차단값 검증

기능표시실제 차단 게이트판정
검색 횟수10/월월·일·시 3단 비교 후 throw일치
바이어 저장500/월recordUsage(lead_create) — 3경로 미호출불일치
캠페인 발송5,000·200reserveSendQuota daily=defer / monthly=실패일치
무료체험 만료D-14매시간 크론 trialing→expired → 전기능 deny일치

4. 실 DB 구조 (alpha / beta)

테이블역할alphabeta
billing_plans플랜별 제한 SSOT55
subscriptionsWS→plan·체험만료100~12
workspace_usage카운터 (leads_used·emails_sent)76437
usage_logsappend 이벤트로그2,6711,085,696
leads실제 리드234,8851,157,869
buyer_search_jobs검색 파생집계 소스3560
0.1%
beta 리드 카운트 로그 / 실제 (1,152 / 115만)
27%
beta workspace_usage 커버리지 (318/1,158)
0.12ms
leads 실시간 월COUNT (Index Only Scan 실측)

인덱스 leads_ws_created_id_idx (workspace_id, created_at DESC, id)가 alpha·beta 양쪽에 이미 존재 → 실시간 파생 카운트가 저렴.

5. 구조적 문제 (2026 안티패턴)

P1 · 카운터가 실제를 극단적으로 과소집계 (치명)

beta 실제 leads 1,157,869건 vs usage_logs.lead_create 1,152건(0.1%). workspace_usage.leads_used는 사실상 무의미 → 바이어저장 500 제한이 규모상 이미 무력화. 수동·대량·방문자 적재 우회의 실측 증거.

P2 · 이중 기록 (dual bookkeeping)

같은 사용량을 usage_logs(append 로그)와 workspace_usage(카운터) 두 곳에 기록 → drift. 2026 원칙은 "이벤트 로그 1개 = SSOT, 나머지는 파생". 현재 둘 다 불완전.

P3 · workspace_usage 커버리지 갭

alpha 35%·beta 27%만 usage row 보유(lazy 생성). "row 없음=0"으로 표시돼 실제 사용이 있어도 0 노출 가능.

P4 · metadata drift (양 환경)

metadata.emailsLimit(6000/1500) vs 컬럼(5000) 불일치·코드 미참조 dead JSONB. + beta billing_plans PG stats stale.

6. 최적 해결안 (제1원리: 삭제→단순화→SSOT)

핵심: 더 정확한 소스가 이미 DB에 존재(leads 테이블·usage_logs)하는데 부정확한 카운터를 병행 유지 중. 카운터를 삭제하고 정확 소스로 파생.

집행 방식 2종으로 표준화

제한 유형방식대상
리소스 개수리소스 테이블 실시간 COUNT(*)leads · products · domains · accounts · workspaces · members
행위 빈도usage_logs 파생 집계email_send · search

저장 카운터(workspace_usage) 방식은 폐기 — 신규 제한 추가 시 금지 원칙.

실행 우선순위

1
leads → 실시간 COUNT 전환 (매출 누수·P1)leads_used 폐기, leads 테이블 실시간 COUNT (0.12ms, 인덱스 존재). 신규 경로 자동 반영 = 우회버그 원천 소멸.
2
emails → usage_logs 파생 + partial 인덱스usage_logs(workspace_id, created_at) WHERE usage_type='email_send' 추가 후 파생 집계. beta 1.08M 로그가 이미 SSOT.
3
workspace_usage 테이블 제거①②로 두 카운터 소비처 소멸 → 테이블·커버리지 갭(P2·P3) 동시 해소.
4
청소metadata.emailsLimit dead JSONB 삭제(양 환경) · has_dedicated_manager 마케팅 플래그 명시 · beta billing_plans ANALYZE.

alpha·beta 값·구조·drift가 완전히 동일 → 동일 마이그레이션으로 양쪽 처리 (alpha-beta-sync 규칙 적용).