목록으로
개발/아키텍처

읽기 성능 vs 쓰기 성능 최적화 전략

성능 최적화를 키워드 나열이 아닌 계층(Layer)별 구조로 접근하는 방법을 정리합니다. 읽기와 쓰기의 비용 구조 차이, 캐싱·복제·인덱스·샤딩 전략, 그리고 B-Tree vs LSM-Tree까지 트레이드오프 관점에서 다룹니다.

guideintermediate
18 min read
읽기 성능과 쓰기 성능 최적화읽기 성능과 쓰기 성능 최적화
그림 1. 읽기와 쓰기는 비용 구조가 정반대다

들어가며

면접에서 "성능을 어떻게 높일 수 있나요?"라는 질문을 받았을 때, 캐시·인덱스·샤딩 같은 키워드를 두서없이 나열하는 것은 좋은 답이 아닙니다. 핵심은 계층(Layer)별로 분류해 구조적으로 접근하고, 각 선택이 동반하는 트레이드오프(Trade-off)를 함께 설명하는 것입니다.

특히 읽기(Read)와 쓰기(Write)는 비용 구조가 정반대입니다. 읽기는 조회 경로를 단축하는 방향으로, 쓰기는 부하를 분산하고 비동기로 미루는 방향으로 최적화합니다. 한쪽을 빠르게 만들면 다른 쪽이 느려지는 경우가 많아, 둘을 분리해서 사고하는 것이 출발점입니다.

구분읽기(Read) 최적화쓰기(Write) 최적화
핵심 비용조회 경로 탐색, 네트워크 왕복디스크 I/O, 정렬·인덱스 갱신, 락 경합
기본 전략캐싱 + 복제 + 인덱스로 경로 단축버퍼링 + 분산 + 비동기로 부하 분산
인덱스 관점많을수록 유리 (탐색 단축)적을수록 유리 (갱신 비용 감소)
대표 패턴Read Replica, Materialized ViewCQRS, 최종 일관성, LSM-Tree

대부분의 서비스는 읽기 비중이 압도적으로 높습니다. 따라서 읽기 튜닝이 전체에 더 큰 효과를 주는 경우가 많지만, 무엇을 먼저 손댈지는 반드시 측정 이후에 결정해야 합니다.

읽기 성능 최적화

읽기 최적화의 본질은 요청이 더 깊은 계층까지 내려가지 않도록 막는 것입니다. 사용자에게 가까운 계층에서 응답할수록 비용이 줄어듭니다. 그래서 전략을 네트워크·웹 → 애플리케이션 → 데이터베이스 순서로 살펴봅니다.

그런데 "가까운 계층일수록 싸다"는 말이 추상적으로 들린다면, 아래 그림이 그 이유를 한눈에 보여 줍니다. 각 계층의 비용은 결국 메모리 계층 간 레이턴시의 차수(Order of Magnitude) 격차에서 나옵니다. 로컬 캐시(RAM, 100ns)와 디스크 조회(HDD, 5ms)는 같은 '조회'라도 단위 자체가 다릅니다.

레이턴시 차수와 아키텍처 계층의 매핑레이턴시 차수와 아키텍처 계층의 매핑
그림 2. 계층마다 비용이 다른 이유 — 위로 갈수록 빠르고 가깝고 저렴하다

계층별 비용 차이의 근거가 되는 레이턴시 숫자(ns·μs·ms)의 감각은 바이브 코딩 시대, 개발자와 아키텍트를 가르는 숫자 감각에서 따로 정리했습니다.

네트워크 및 웹 레이어

전략동작효과
브라우저 캐시사용자 브라우저 자체에 캐시 저장서버로의 재요청 자체를 차단
CDN사용자와 가까운 엣지 서버에 캐싱정적 리소스뿐 아니라 동적 API 결과까지 캐싱
HTTP/2 & HTTP/3멀티플렉싱, 헤더 압축, QUIC 기반 전송네트워크 전송 효율 극대화

브라우저 캐시는 Cache-Control, ETag 같은 HTTP 헤더로 제어합니다. 가장 저렴한 최적화이지만, 갱신된 리소스가 반영되지 않는 캐시 무효화 문제를 함께 설계해야 합니다.

CDN은 흔히 이미지·미디어 같은 정적 리소스 캐싱으로 알려져 있으나, AWS CloudFront처럼 전용선(Backbone)을 타고 이동하는 경우 동적 API 최적화에도 유용합니다.

애플리케이션 레이어 (읽기)

전략위치특징
로컬 캐시애플리케이션 서버 메모리(RAM)가장 빠르지만 서버 간 공유 불가, 데이터 불일치 위험
분산 캐시Redis 등 전용 인메모리 DB여러 서버가 공유, 네트워크 비용은 있으나 일관성 확보

로컬 캐시는 네트워크 홉이 없어 가장 빠르지만, 서버가 여러 대면 각 서버의 캐시가 어긋날 수 있습니다. 분산 캐시(Redis)는 캐시 레이어를 외부로 빼서 모든 서버가 같은 데이터를 보게 하고, 백엔드(DB) 요청을 줄이는 핵심 수단이 됩니다.

데이터베이스 레이어 (읽기)

전략동작비고
리플리케이션Master-Slave로 복제, 읽기를 Replica로 분산다수의 읽기 요청 분산 처리
인덱싱조회 경로 단축RDBMS 읽기 성능의 핵심. 병목 대부분이 여기서 발생
파티셔닝대용량 테이블을 날짜·지역 등으로 분할탐색 범위 축소 (Partition Pruning)
서머리 테이블미리 계산된 결과를 배치로 생성실시간 집계 대신 즉시 서빙

인덱싱은 RDBMS에서 가장 중요한 읽기 최적화 요소입니다. 실무 병목의 상당수는 인덱스가 아예 없거나 잘못 설계된 데서 나옵니다. 서머리 테이블(Materialized View)은 원천 데이터에서 매번 집계하지 않고, 백그라운드 스케줄러가 미리 계산해 둔 결과를 그대로 내보내는 방식으로, 반정규화 전략과 같은 맥락입니다.

쓰기 성능 최적화

쓰기는 읽기와 비용 구조가 다릅니다. 디스크에 실제로 기록해야 하고, 인덱스를 정렬·갱신해야 하며, 동시 쓰기 시 락(Lock) 경합이 발생합니다. 따라서 핵심은 부하 분산과 비동기 처리입니다.

애플리케이션 레이어 (쓰기)

가장 대표적인 수단은 메시지 큐(MQ / Kafka)입니다. 쓰기 요청을 DB에 직접 꽂지 않고 큐에 먼저 적재(버퍼링)합니다. 사용자에게는 즉시 빠른 응답을 돌려주고, 실제 DB 쓰기는 컨슈머가 비동기(Asynchronous)로 처리합니다.

text
[클라이언트] --쓰기 요청--> [API 서버] --즉시 응답(202)--> [클라이언트]
                                  |
                                  v
                             [메시지 큐]  <- 버퍼
                                  |
                            (비동기 소비)
                                  v
                             [DB 저장]

단, 큐에 적재된 뒤 DB 반영 전까지는 데이터가 보이지 않는 지연(Lag)이 생깁니다. 따라서 읽기 측에서 이 지연을 감안한 설계가 필요합니다.

데이터베이스 레이어 (쓰기)

전략동작트레이드오프
샤딩데이터를 여러 DB 노드에 분산 저장쓰기 부하 원천 분산, 단 조인·트랜잭션 복잡도 증가
파티셔닝하나의 DB 내에서 테이블 분할쓰기 경합(Lock) 감소
최종 일관성강한 일관성을 포기, 쓰기 성능·확장성 확보당장은 불일치, 최종적으로 정합성 수렴 (주로 NoSQL)
배치 라이트여러 건을 묶어 한 번에 처리네트워크 왕복·디스크 I/O 횟수 감소
인덱스 최적화불필요한 인덱스 제거쓰기마다 발생하는 인덱스 갱신 비용 최소화

샤딩 vs 파티셔닝의 차이가 헷갈리기 쉽습니다. 샤딩은 여러 물리 노드로 데이터를 쪼개는 것이고, 파티셔닝은 하나의 DB 안에서 테이블을 나누는 것입니다. 둘 다 쓰기 부하와 경합을 줄이지만 분산 범위가 다릅니다.

여기서 주목할 항목은 인덱스 최적화입니다. 같은 인덱스가 읽기에서는 도움이 되지만 쓰기에서는 부담이 됩니다. 조회할 때는 인덱스 덕분에 데이터를 빠르게 찾지만, 데이터를 추가·수정·삭제할 때는 본문뿐 아니라 인덱스도 함께 정렬·갱신해야 하기 때문입니다. 예를 들어 인덱스가 5개 걸린 테이블에 한 건을 넣으면 본문 1번에 더해 인덱스 5번까지, 총 6번의 쓰기가 발생합니다. 그래서 인덱스를 늘릴수록 조회는 빨라지지만 쓰기는 느려집니다. 이것이 읽기와 쓰기 트레이드오프의 가장 전형적인 모습입니다.

트레이드오프는 어디서 오는가: B-Tree vs LSM-Tree

읽기 최적화와 쓰기 최적화가 왜 반대로 가는지는, 데이터를 디스크에 어떻게 저장하느냐(스토리지 엔진)에서 갈립니다. 이 부분은 면접에서 한 단계 더 깊이 들어갈 때 좋은 무기가 됩니다.

구분B-TreeLSM-Tree
대표 DBPostgreSQL, MySQL(InnoDB)Cassandra, RocksDB, HBase
읽기빠름 (정렬된 트리, 적은 탐색)상대적으로 느림 (여러 SSTable 병합 탐색)
쓰기제자리 갱신 → 랜덤 I/O 발생메모리에 모아 순차 기록 → 쓰기에 유리
지향읽기 중심 워크로드쓰기 중심 워크로드
  • B-Tree는 데이터를 정렬된 형태로 제자리 갱신(in-place update)하므로 읽기는 빠르나, 쓰기 시 랜덤 디스크 I/O가 발생합니다.
  • LSM-Tree는 쓰기를 메모리(MemTable)에 모았다가 순차적으로 디스크에 기록(Sequential Write)하므로 쓰기 처리량이 높습니다. 대신 읽을 때 여러 파일을 병합 탐색해야 하고, 백그라운드 병합(Compaction)으로 인한 쓰기 증폭(Write Amplification) 비용이 따릅니다.

"읽기가 빨라지면 쓰기가 느려진다"는 원칙은 추상적 구호가 아니라, 이처럼 저장 구조 수준에서 물리적으로 결정됩니다.

성능 튜닝의 3가지 원칙

기술을 나열하기 전에, 아래 세 원칙으로 사고의 틀을 잡는 것이 핵심입니다.

1. 병목(Bottleneck) 우선 측정

파레토 법칙 — 시스템의 20% 병목이 전체 성능의 80%를 좌우합니다. 전체를 무작정 건드리기 전에, 슬로우 쿼리 로그나 APM(Application Performance Monitoring) 도구로 병목 구간을 먼저 측정하고 제거해야 합니다. 측정 없는 최적화는 추측일 뿐입니다.

2. 트레이드오프 고려

읽기가 빨라지면 쓰기가 느려집니다. 예를 들어 DB 인덱스를 추가하면 조회는 빨라지지만, 데이터를 쓸 때마다 인덱스를 갱신해야 하므로 쓰기 성능은 저하됩니다. 기술 도입에 따른 비용과 성능 사이의 균형을 맞춰야 합니다.

3. 읽기 vs 쓰기 비율 분석

운영 중인 서비스가 읽기 중심인지 쓰기 중심인지 먼저 파악합니다. 읽기 99% 서비스라면 캐싱·복제에 투자하고, 쓰기가 폭주하는 서비스라면 큐·샤딩에 투자하는 식으로 전략적 비중을 둡니다.

심화: 면접 꼬리 질문 대비

성능 분리 전략을 답한 뒤 자주 이어지는 주제들입니다.

Replication Lag (복제 지연)

Master-Slave 간 동기화가 지연되면, Replica에서 읽었을 때 옛 데이터를 볼 수 있습니다. 해결책으로는 중요한 읽기를 Master에서 수행(read-your-writes), 동기 복제 사용, 복제 지연 모니터링 등이 있습니다.

CQRS 패턴

CQRS는 Command(쓰기)와 Query(읽기)의 책임·모델·시스템을 완전히 분리하는 패턴입니다. 읽기 모델과 쓰기 모델을 각각 독립적으로 최적화할 수 있으나, 두 모델 간 동기화(주로 이벤트 기반)와 최종 일관성을 감수해야 합니다.

캐시 쓰기 전략 (Cache Write Strategy)

전략동작성능일관성
Write-Through캐시와 DB에 동시 기록쓰기 느림강함 (항상 일치)
Write-Back캐시에만 쓰고 나중에 모아 DB 반영쓰기 빠름약함 (장애 시 유실 위험)
Cache Aside캐시 미스 시 DB 조회 후 캐시 적재읽기 중심에 유리무효화 설계 필요

Write-Back은 쓰기를 캐시에 모았다가 일괄 반영하므로 빠르지만, 캐시 장애 시 미반영 데이터가 유실될 수 있습니다. Write-Through는 매번 DB까지 쓰므로 느리지만 캐시와 DB가 항상 일치합니다.

마치며

성능 최적화는 단일 기법의 문제가 아니라 계층별 분류 × 트레이드오프 인식의 문제입니다. 정리하면 다음과 같습니다.

  • 성능 전략은 네트워크 → 앱 → DB 계층으로 나눠 구조적으로 접근합니다.
  • 읽기는 캐싱·복제·인덱스로 경로를 단축하고, 쓰기는 큐·샤딩·배치·인덱스 절제로 부하를 분산합니다.
  • 인덱스를 늘리면 조회는 빨라지지만 쓰기는 느려집니다. 이 상충 관계는 저장 구조 수준에서 B-Tree(읽기) vs LSM-Tree(쓰기)로 드러납니다.
  • 무엇을 먼저 할지는 항상 병목 측정 → 읽기/쓰기 비율 분석 순으로 결정합니다.

출처: 코딩하는기술사 (YouTube) — 읽기 성능 vs 쓰기 성능 최적화 전략. 본문의 스토리지 엔진(B-Tree/LSM-Tree), 캐시 쓰기 전략 등 일부 내용은 학습을 위해 보강했습니다.

CDN
Content Delivery Network의 약자입니다. 사용자와 물리적으로 가까운 엣지(Edge) 서버에 콘텐츠를 캐싱해 응답 속도를 높입니다. 정적 리소스뿐 아니라, AWS CloudFront처럼 전용선을 활용하는 경우 동적 API 응답 최적화에도 쓰입니다.
리플리케이션
Master-Slave 구조로 데이터를 여러 DB 노드에 복제합니다. 쓰기는 Master, 읽기는 여러 Slave(Replica)로 분산해 다수의 조회 요청을 처리합니다. 복제가 지연되면(Replication Lag) Replica에서 옛 데이터를 읽을 수 있습니다.
샤딩
데이터를 물리적으로 다른 여러 데이터베이스 노드에 분산 저장합니다. 쓰기 부하를 원천적으로 나눌 수 있으나, 노드를 가로지르는 조인과 트랜잭션 처리가 복잡해집니다. 하나의 DB 안에서 테이블을 나누는 파티셔닝과 구분됩니다.
최종 일관성
강한 일관성(Strong Consistency)을 포기하는 대신 쓰기 성능과 확장성을 얻는 트레이드오프 전략입니다. 데이터 변경 직후에는 노드 간 값이 다를 수 있지만, 시간이 지나면 최종적으로 모든 노드가 동일한 값으로 수렴합니다. 주로 NoSQL에서 채택합니다.
LSM-Tree
Log-Structured Merge Tree의 약자입니다. 쓰기를 메모리(MemTable)에 모았다가 디스크에 순차적으로 기록(Sequential Write)해 쓰기 처리량이 높습니다. 대신 읽을 때 여러 SSTable을 병합 탐색해야 하고, 백그라운드 병합(Compaction)으로 쓰기 증폭이 발생합니다. Cassandra, RocksDB 등이 사용합니다.
CQRS
Command Query Responsibility Segregation의 약자입니다. 쓰기(Command)와 읽기(Query)의 책임·모델·시스템을 완전히 분리합니다. 읽기 모델과 쓰기 모델을 각각 독립적으로 최적화할 수 있으나, 두 모델 간 동기화(주로 이벤트 기반)와 최종 일관성을 감수해야 합니다.

관련 글