[백엔드] 서비스 성능 최적화 및 운영 필수 지식

핵심 요약

  • 성능 지표와 확장: 응답 시간과 처리량(TPS)을 모니터링하여 병목 지점을 파악하고, 상황에 맞는 수직/수평 확장을 수행해야 함.
  • 리소스 최적화: DB 커넥션 풀을 효율적으로 관리하고, 데이터 압축 및 메모리 누수 방지를 통해 시스템 안정성을 확보해야 함.
  • 트래픽 대응 전략: 다층적 캐시 전략(Local/Remote)과 대기열 시스템을 도입하여 급격한 트래픽 변동에 유연하게 대처해야 함.

목차

1. 서비스 성능 지표

1. 응답 시간 (Response Time)

  • 요청을 호출해서 응답을 받을 때까지 소요된 시간을 의미함.
  • FFTB (Time To First Byte): 응답 데이터 중 첫 번째 바이트가 도착할 때까지 걸린 시간.
  • FFLB (Time To Last Byte): 응답 데이터 중 마지막 바이트가 도착할 때까지 걸린 시간.
  • 서버 처리 시간의 주요 구성 요소
    • 로직 수행 (aggregate, for, if 등)
    • DB 연동 작업 (select, insert, update 등)
    • 외부 API 연동 (타 서비스, 데이터센터 등)
    • 응답 데이터 생성 및 전송
  • DB 연동과 외부 API 연동 작업은 통상적으로 요청 처리 시간에서 가장 많은 비중을 차지하는 요소
sequenceDiagram
  participant Client
  participant API as API Server
  participant DB as DBMS

  Client->>API: API Request
  activate API

  API->>DB: Query1
  activate DB
  DB-->>API: Result1
  deactivate DB

  API->>DB: Query2
  activate DB
  DB-->>API: Result2
  deactivate DB

  Note over Client,API: 응답 시간(Response Time)

  API-->>Client: JSON Response
  deactivate API

  Note left of Client: TTFB
  Note left of Client: TTLB

2. 처리량 (Throughput)

  • 단위 시간당 시스템이 처리하는 작업량을 뜻함.
  • 주로 TPS(초당 트랜잭션), RPS(초당 요청 수)를 지표로 사용함.
  • 최대 TPS 시스템이 초당 처리할 수 있는 최대 요청 수.
  • 최대 TPS를 초과하는 요청 유입 시 처리가 지연되어 유저 입장에서 ‘대기’를 겪게 됨.
  • TPS를 늘리는 방법
    • 요청 1개를 처리하는 시간 자체를 단축함.
    • 서버가 동시에 처리할 수 있는 요청의 수를 늘림.
sequenceDiagram
  participant User as 사용자
  participant Server as 서버

  Note over Server: 요청 처리 중

  User->>Server: 요청 1
  User->>Server: 요청 2
  User->>Server: 요청 3

  Note over User: 왜 반응이 없음?

  User->>Server: 재시도 요청
  User->>Server: 재시도 요청
  User->>Server: 재시도 요청

  Note over Server: 요청 폭증

2. 병목과 확장

1. 병목 지점 (Bottleneck)

  • 트래픽 증가 및 데이터 누적 시 응답 시간이 급격히 느려지거나, 재시작 후에도 증상이 반복되는 현상.
  • 이때 성능 저하를 유발하는 정확한 지점을 찾아야 함.
  • 대다수의 경우 외부 API 연동 지점이나 DB 호출 지점에서 성능 문제가 발생함.

2. 확장 (Scaling)

  • 수직 확장 (Scale-up): CPU, 메모리, 디스크 등 장비의 자원 성능을 높여 TPS를 확보하는 방법.
  • 수평 확장 (Scale-out): 서버의 대수를 늘려 TPS를 높이는 방법.
  • 주의사항: 실제 병목 지점 파악이 선행되어야 함. DB가 병목인데 API 서버만 늘리는 것은 효과가 없음.
  • 수평 확장 시 트래픽을 분배하기 위한 로드 밸런서(Load Balancer)가 필요함.

3. DB 커넥션 관리

1. 커넥션 풀 (Connection Pool)

  • DB 처리 과정: 연결(Connect) → 쿼리 실행 → 종료(Disconnect)의 3단계로 진행됨.
  • 환경별 연결 소요 시간 (RTT 기준)
    환경 소요 시간 특징
    Localhost 2~5ms 빠름
    Same Region (EC2 ↔ RDS) 10~30ms 트래픽 증가 시 부담이 됨
    SSL/TLS 적용 50~200ms 핸드쉐이크 비용으로 인해 느림
    Public Internet 100ms ~ 수 초 사용 불가 수준임
  • 매 요청마다 연결을 새로 맺으면 전체 시간의 80~90%를 연결/종료에 소모할 수 있음.
  • 따라서 커넥션 풀에 미리 연결을 생성해 두고 재사용하여 시간을 단축해야 함.

2. 주요 커넥션 설정

  • 커넥션 풀 크기 (Pool Size)
    • 미리 생성해 둘 커넥션의 개수. (예: 10개 풀, 쿼리 0.1초 소요 시 초당 100 처리 가능)
    • 트래픽 폭증 서비스라면 최소 크기(Min)를 최대 크기(Max)와 동일하게 맞추는 것이 유리함.
    • 단, DB 서버의 CPU 사용률을 고려하여 적정 크기를 설정해야 함.
  • 대기 시간 (Connection Timeout)
    • 여유 커넥션이 없을 때 기다릴 수 있는 최대 시간.
    • HikariCP 기본값은 30초이나, 너무 길면 사용자 재시도(새로고침)로 요청이 폭증할 수 있음.
    • 서비스 특성에 따라 0.5~3초 이내로 설정하는 것을 권장함.
  • 유효 시간 (Lifetime & Idle)
    • 최대 유휴 시간 (Max Idle) 사용되지 않는 커넥션을 유지하는 최대 시간.
    • 최대 유지 시간 (Max Lifetime) 커넥션 생성 후 생존 가능한 절대 시간.
    • 시간 초과 시 커넥션을 제거하여 끊긴 연결 사용을 방지해야 함. 무한대로 설정하지 않도록 주의할 것.

4. 캐시 전략

1. 적중률과 삭제 규칙

  • 캐시 적중률 (Hit Rate): 캐시에 데이터가 존재하여 조회에 성공한 비율.
  • 일반적으로 오래된 데이터보다 최신 데이터의 조회 빈도가 높음.
  • 삭제 규칙 (Eviction Policy)
    • LRU: 가장 오래전에 사용된 데이터를 제거함.
    • LFU: 사용 빈도가 가장 낮은 데이터를 제거함.
    • FIFO: 가장 먼저 들어온 데이터를 먼저 제거함.

2. 로컬 캐시 vs 리모트 캐시

구분 Java Go Node.js
라이브러리 Caffeine go-cache node-cache
  • 로컬 캐시 (In-Memory): 서버 프로세스 메모리를 사용함. 속도가 빠르지만 서버 재시작 시 데이터가 소실됨. 데이터 양이 적을 때 적합함.
  • 리모트 캐시 (Remote): Redis 등 별도 저장소를 사용함. 대용량 데이터, 잦은 배포 환경, 데이터 공유가 필요할 때 유리함.

3. 고급 활용 전략

  • 사전 적재 (Cache Warming): 트래픽 폭증이 예상되는 시점(예: 푸시 발송 직전)에 미리 데이터를 캐싱하여 적중률을 높이는 방법.
  • 캐시 무효화 (Invalidation): 데이터 변경 시 캐시를 지워야 함. 가격이나 정책 등 민감한 데이터는 즉시 무효화해야 하며, 덜 민감한 데이터는 만료 시간을 둠.

5. 메모리와 응답 최적화

1. 가비지 컬렉터(GC)와 메모리

  • 메모리를 많이 점유하는 로직(대량 객체 생성 등)은 트래픽 폭증 시 OOM(Out Of Memory) 오류를 유발할 수 있음.
  • GC 실행 중 애플리케이션이 멈추는 Stop The World 현상으로 응답 시간이 길어질 수 있음.
  • 조회 범위를 제한하거나, 파일 다운로드 시 스트림(Stream)을 사용하여 메모리에 데이터를 한 번에 올리는 것을 방지해야 함.

2. 응답 데이터 압축

  • 데이터 크기가 클수록 전송 시간이 길어지고 클라우드 비용이 증가함.
  • HTML, JSON 등 텍스트 데이터는 압축 시 전송량을 70% 이상 줄일 수 있음.
  • 단, 이미 압축된 이미지(jpg, zip 등)는 재압축 효과가 없으므로 제외해야 함.
  • 헤더 활용
    • Accept-Encoding 클라이언트가 지원하는 압축 방식을 알림.
    • Content-Encoding 서버가 응답 데이터에 적용한 압축 방식을 명시함.

6. 정적 자원과 대기 처리

1. 브라우저 캐시

  • 웹 서버 트래픽 중 HTML, CSS, JS, 이미지 등 정적 파일의 비중이 경우에 따라 80%에 달함.
  • Cache-Control, Expires 헤더를 사용하여 클라이언트(브라우저)에 데이터를 일정 시간 저장하도록 유도하면 서버 부하를 줄일 수 있음.

2. 대기 처리 (Queueing)

  • 수강 신청 등 순간 트래픽 폭증 시, 무조건적인 확장은 비용 효율이 낮음.
  • 시스템이 처리 가능한 만큼만 요청을 받고 나머지는 대기열로 안내하는 것이 현실적.
  • 대기 순번을 시각적으로 보여주면 사용자의 무한 새로고침(Retry Storm)을 방지할 수 있음.

이 글은 어떠셨나요? 자유롭게 의견을 남겨주세요! 💬