데이터베이스 쓰기 성능 최적화하기 (NoSQL 중심으로)

Keno Kim·2025년 2월 22일

시작하기

  • 최근 사용하는 NoSQL 데이터베이스에 쓰기 트래픽이 몰려 서비스가 느려지는 이슈가 발생했다.
  • 이 경우 쓰기 트래픽이 전체 서비스의 성능 병목이 된다.
  • 이러한 이슈에 대해 일반적으로 어떻게 해결할 지 정리해 보자.

스케일 업 / 스케일 아웃

  • 가장 먼저, 데이터베이스의 스케일 업 / 스케일 아웃을 할 수 있다.
  • 특히, 서버 증설이 쉬운 클라우드 환경에서는 쉽게 시도할 수 있는 방법이다.
  • 그러나 scale-up, scale-out 은 금전적인 비용이 발생하며, 별도로 주의해야 할 점도 많다.
  • 예를 들어, AWS RDS (RDB) 의 경우 scale-up 을 하면 인스턴스 재시작이 필요하다. (중단이 발생한다.)
  • Elasticsearch 과 같은 NoSQL 의 경우 scale-out 이 무중단으로 가능하나, 리밸런싱으로 인해 (샤드를 다른 노드로 이동시킨다.) 내부적인 트래픽이 발생한다.
    • 이 동안에 클러스터 전체 성능이 감소할 수 있다.
  • 물리적인 scale-out 이 어려운 RDB 의 경우 샤딩을 통해 분산할 수 있다.
    • 이 경우에도 추가로 scale-out 을 할 경우 데이터 재분배 등 조정이 필요하다.

쓰기 / 읽기 분리

  • https://www.tencentcloud.com/ko/document/product/1042/33316
  • 예를 들어, RDB 의 경우 replica 를 생성하여, 읽기 트래픽은 replica 가, 쓰기 트래픽은 master 가 처리하도록 구성할 수 있다.
  • 이 경우, 쓰기 트래픽이 과도하게 발생해도 읽기 트래픽은 최적의 성능을 유지한다. (병목이 발생하지 않는다.)
  • NoSQL 의 경우에도 읽기 / 쓰기 트래픽을 분리할 수 있다.
  • 서비스 특성 상 읽기 / 쓰기의 비중이 어떻게 되는지 확인해야 한다.
    • 예를 들어, 로그 저장 DB 와 같이 읽기 비중이 매우 낮은 경우 분리하는 장점이 없다.

(NoSQL) 샤딩, 파티션 키 최적화

  • NoSQL 에서 파티션이란 데이터를 여러 샤드 (파티션)로 분산하여 저장하고, 읽기 / 쓰기를 최적화하는 개념이다. (NoSQL 에서는 샤딩 / 파티셔닝 개념이 같다.)
  • 파티션 키는 보통 설정 가능하며, 파티션 키에 따라 데이터가 어느 샤드로 저장될 지 결정된다.
  • 파티션 키를 잘 설정해서 쓰기 트래픽이 여러 샤드에 분산되도록 할 수 있다.
  • 예를 들어, 검색 성능 향상을 위해 파티션 키를 삽입 시간으로 정했다고 하자.
    • 이 경우 hot partition 문제가 발생하는데, 모든 쓰기 트래픽이 하나의 샤드(파티션)로 집중되기 때문이다.
    • 이 경우 해당 샤드가 성능 병목이 된다.
  • 샤드가 하나인 경우에는 의미가 없을 수 있다. (특히 클라우드 서비스의 경우 물리적인 샤드가 몇개인지 확인이 필요하다.)

인덱스 최적화

  • 인덱스는 읽기 성능을 향상시키지만, 쓰기 성능을 저하시킨다.
  • 불필요한 인덱스가 많이 있는 경우 이를 제거하여 쓰기 성능을 최적화할 수 있다.
  • RDB 에서 인덱스가 있는 것 처럼, NoSQL 에서도 인덱스가 있는 경우가 많다.
  • RDB 는 필드에 수동으로 인덱스를 생성해 줘야 생기는데, NoSQL 은 어떤 방식으로 인덱스가 생성되는지 경우에 따라 다를 수 있다.
  • 예를 들어, 어떤 NoSQL 은 기본적으로 모든 필드에 인덱스를 생성한다.
    • 이 경우 데이터 적재 성능이 저하될 수 있다.

쓰기 배치 처리 / Bulk API

  • 배치 처리란, 여러 개의 요청을 한 번에 묶어서 처리하는 방식이다.
    • 이로 인해 네트워크 비용 감소, 트랜잭션 성능 향상 등의 이점이 있다.
  • 먼저, 배치 파이프라인에서 배치 처리가 가능하다.
    • 이 경우, 실시간성 요청이 아니므로 쉽게 구현할 수 있다.
  • 실시간 쓰기 요청을 배치 처리하는 경우 여러 방법이 있다.
    • 예를 들어, API 를 통해 쓰기 요청이 랜덤하게 들어오는 경우이다.
    • Kafka 와 같은 메시지 큐를 두고, consumer 가 배치 처리할 수 있다.
      • 예를 들어, 5초마다 100개씩 poll 하는 방식도 가능할 것이다.
    • 메모리 버퍼를 두고 배치 처리할 수 있다.
      • 예를 들어, Redis 를 두고 쓰기 요청이 들어오면 redis 에 저장한 후 동기화하는 방식이다.
      • 이 경우, 메모리 버퍼에 장애가 발생하면 메시지가 유실될 수 있다.
  • 쓰기 요청을 배치 처리할 경우 비동기 처리하는 것과 마찬가지로, 실시간으로 데이터 변경의 반영이 필요한 서비스에는 적합하지 않다.

비동기 처리

  • 위의 내용과 겹치는데, Kafka 등 메시지 큐를 두어 비동기 처리할 수 있다.
profile
개발자의 생각 로그

0개의 댓글