Pink Spider/

Created Tue, 14 Oct 2025 11:25:18 +0900 Modified Mon, 08 Dec 2025 08:41:47 +0900
1198 Words 5 min

DB의 테이블의 row가 너무 많아질떄 (예 하루에 10만건씩 누적), 대처 방법

  • “하루 10만 건씩 계속 누적”이면 1년에 3,650만 건입니다. 초반엔 버티지만, 몇 달만 지나도 쿼리·백업·VACUUM/OPTIMIZE 비용이 급증하죠. 아래 순서대로 적용해보세요.

1) 먼저 결정할 것 (운영 정책)

  • 보존 기간: 예) “6개월은 온라인(핫), 2년은 아카이브(콜드), 그 외 삭제”
  • 주요 조회 패턴: 최근 N일만 조회? 특정 사용자/키 기준 조회? 집계 위주?
  • SLA/성능: p95 응답시간, 허용 가능한 쓰기 지연

2) 즉시 효과 (스키마/인덱스/쓰기)

  • 인덱스 슬림화: 실제 조건/정렬에 쓰는 컬럼만 인덱스. 불필요한 복합 인덱스 제거.

  • Covering/Partial 인덱스:

    • PostgreSQL: 최근 데이터만 대상으로 부분 인덱스

      CREATE INDEX CONCURRENTLY idx_events_recent
      ON events(user_id, created_at DESC)
      WHERE created_at >= now() - interval '30 days';
      
    • 대용량 타임순 데이터는 BRIN 인덱스 고려(append-only에 매우 저렴).

      CREATE INDEX CONCURRENTLY idx_events_brin ON events USING BRIN(created_at);
      
  • 배치 쓰기: 단건 insert 대신 배치(예: 500~5,000건 단위), 트랜잭션 묶기.

  • PK 설계: InnoDB/Clustered PK는 증가형(빅시리얼/스노우플레이크). 랜덤 UUID v4를 클러스터 키로 쓰지 않기.

3) 파티셔닝 (핵심)

대부분의 “시간 기반 로그/이벤트”는 기간별 파티션으로 해결됩니다. 파티션 단위는 월(일간 트래픽이 높으면 “주/일”).

  • 장점: DROP PARTITION으로 오래된 데이터 제거가 O(메타데이터)급. 유지보수·백업·인덱스가 파티션별로 슬림.

  • PostgreSQL 예시 (월 파티션):

    CREATE TABLE events (
      id bigserial PRIMARY KEY,
      user_id bigint NOT NULL,
      created_at timestamptz NOT NULL,
      payload jsonb
    ) PARTITION BY RANGE (created_at);
    
    CREATE TABLE events_2025_10 PARTITION OF events
    FOR VALUES FROM ('2025-10-01') TO ('2025-11-01');
    
    -- 앞으로 매월 자동 생성(배치 잡) + 오래된 파티션 DROP
    DROP TABLE IF EXISTS events_2024_10;  -- 보존기간 지난 달은 즉시 삭제
    
  • MySQL/InnoDB: RANGE 파티션(TO_DAYS(created_at)) 또는 HASH(user_id). 오래된 파티션 ALTER TABLE ... DROP PARTITION.

4) 보존/아카이브/요약(롤업)

  • TTL/만료 정책: 파티션 드롭으로 실행(DELETE보다 훨씬 빠름).

  • 아카이브 테이블/스토리지:

    • OLTP에는 최근 3~6개월만 두고, 이전은 아카이브 스키마로 이동(또는 S3/Coldw storage + Athena/BigQuery/ClickHouse).

    • 예:

      INSERT INTO events_archive SELECT * 
      FROM events 
      WHERE created_at < now() - interval '180 days';
      -- 그 후 해당 파티션 DROP
      
  • 롤업 테이블/머터리얼라이즈드 뷰: 조회가 “일/주 단위 집계”라면 사전 집계.

    CREATE MATERIALIZED VIEW daily_counts AS
    SELECT date_trunc('day', created_at) AS day, count(*) AS cnt
    FROM events
    GROUP BY 1;
    
    -- 주기적 갱신
    REFRESH MATERIALIZED VIEW CONCURRENTLY daily_counts;
    

5) 운영/튜닝

  • Autovacuum/Analyze(PostgreSQL): 대용량 테이블은 파라미터 하향 조정(빠른 회전), 파티션별 병렬 VACUUM. 예) autovacuum_vacuum_scale_factor 0.1→0.02, autovacuum_analyze_scale_factor 0.05 등.
  • 백업 전략: 파티션 단위 백업. 오래된 파티션은 스냅샷만 보관.
  • 모니터링: 파티션 개수, 인덱스 크기, 힙블로트, 느린 쿼리 패턴 추적.

6) 필요 시 아키텍처 분리

  • 쓰기 많은 로그/이벤트: TimescaleDB(PSQL 확장), ClickHouse, BigQuery 같은 시계열/컬럼 지향으로 분리. OLTP(트랜잭션)는 RDB, 분석/검색은 OLAP/서치로 CQRS/ETL.
  • CQRS 파이프라인: OLTP → (Debezium/Kafka CDC) → 스트리밍 집계/보관.

7) 현실적인 적용 조합(권장 기본셋)

  1. “기간 기준 파티셔닝(월)” + “최근 구간 Partial/BRIN 인덱스”
  2. “6개월 TTL(파티션 드롭)” + “일간 롤업 뷰”
  3. “아카이브는 S3/저비용 스토리지(압축/파케이)”
  4. “배치 insert + PK 증가형” → 대부분의 10만건/일 워크로드는 이 4가지로 깔끔히 안정화됩니다.