유성

조회수 증가 API: 왜 RDBMS에 값을 넣는 것이 불가능에 가까울까? 본문

Architecture

조회수 증가 API: 왜 RDBMS에 값을 넣는 것이 불가능에 가까울까?

백엔드 유성 2026. 1. 6. 17:50

조회수 증가는 단순히 특정 컬럼의 숫자를 하나 올리는 아주 가벼운 작업처럼 보인다.

하지만 수천, 수만 명의 사용자가 동시에 "클릭"을 누르는 순간, 이 간단한 +1은 RDBMS의 문제를 발생시킨다.

 

왜 RDBMS만으로는 이 기능을 구현하는 것이 불가능에 가까운지, 그 내부를 뜯어보자.

 

1. Update 동작은 어떻게 수행되는가?

우선 쉽게 접할수 있는 MySQL의 InnoDB를 기준으로 update가 어떻게 수행되는지 알아야 한다.

UPDATE posts SET views = views + 1 WHERE id = 1;

 

위와 같은 쿼리를 실행할 때, MySQL(InnoDB) 내부에서는 개발자가 명시하지 않아도 배타적 락이 발동된다.

  • 배타적 락의 선언: "내가 이 데이터를 수정할 거니까, 내가 끝날 때까지 아무도 이 데이터를 수정하지 마" 라고 선언하는 것과 같다.

다행히 RDBMS는 Undo Log를 사용하기에 락을 걸기 전 데이터를 읽을수는 있다.

 

2. 그럼 문제가 언제 발생하는가?

조회수 1개씩 한번에 100번 수행한다고 조회수가 99가 되거나 101이 되지는 않는다.

 

그러나 문제는 "동일한 Row에 대한 수많은 동시 업데이트"에서 발생한다.

락을 사용한 경우 각 요청이 한번에 수행되는 것이 아닌 '락 획득을 위한' 줄 세우기가 진행된다.

 

줄 세우기 문제

예를 들어 1000개의 요청이 동시에 실행되면, 1명만 락을 획득하고 나머지 999명은 DB 엔진 내부의 대기 큐에서 순서를 기다린다.

이 대기 큐들은 단순히 순서만을 기다리는 것 뿐 아니라, 각각 DB Connection을 물고있다.

 

Connection 고갈

Connection을 물고 있음으로 인해 가용할 수 있는 Connection이 고갈되면 추가적으로 들어오는 Select, Insert, Update 쿼리 모두 대기 상태로 빠지게 되고,

Timeout 시간을 넘어서는 대기 상태의 작업들은 모두 실패로 돌아간다.

 

디스크 I/O 병목 및 UndoLog 비대화

이 뿐만 아니라 DB에서는 파일쓰기 작업이 같이 진행되는데, 1,000번의 업데이트는 1,000번의 디스크 쓰기 기록을 의미한다.

(MVCC Undo-Log를 참고하자)

디스크 쓰기 작업에서도 쓰기 속도가 따라가지 못하거나, 파일 쓰기 작업이 너무 많아 디스크 사용량이 비대해지는 문제가 발생한다.

 

3. 비관적 락 대신 낙관적 락을 사용하면?

그럼 비관적 락을 사용하지 않으면 어떨까? 이에 대한 대용으로 낙관적 락을 볼수있다.

낙관적 락이란 이름과 다르게 락을 사용하지않는 CAS 명령어 구조로 동작한다.

무한한 재시도와 CPU 과사용

값을 수정한 후 버전을 확인하는 방식인데, 동시에 1,000개의 요청이 몰리면 1개만 수행되고 999개는 다시 시도하게 된다.

운이 나쁜 경우 '1000 + 999 + 998 + ... + 1' 번 작업이 수행되면서 CPU가 스파이크를 칠수 있다.

 

여기서 멈추지 않고 계속 요청이 들어오면 애플리케이션의 CPU와 DB Connection 자원은 고갈되어 서비스 장애로 이어진다.

 

4. 해결책은?

결국 RDBMS의 락 메커니즘 안에서는 이 문제를 해결할 수 없다.

해결책은 DB와 거리를 두고, DB 외부에서 취합한 후 DB에 넘겨주는 방식으로 이를 해결할 수 있다.

 

애플리케이션 메모리에서 버퍼링

애플리케이션 레벨에서 조회수를 즉시 DB에 쏘지 않고, 메모리에 모으는 방식이다.

  • 방식: 메모리 내에서 Lock을 사용하거나, 더 성능이 필요한 경우 CAS 연산자를 통해 취합한다.
  • 단점: 서버가 '강제 종료' 되면 DB에 올라가지 않은 데이터는 잃어버린다.
  • 주의점: 메모리에 쌓는 작업과, 취합한 데이터를 flush 하는 과정에서의 '데이터'를 적절히 분리하여 데이터 오염을 방지해야한다.

애플리케이션의 Graceful Shutdown의 경우 데이터를 백업할 수 있지만, Hard Shutdown의 경우 데이터를 보존할 수 없다.

만약 데이터의 정합성이 완벽할 필요까지는 없다면 충분히 검토 해볼만한 꽤나 단순한 해법이다.

 

애플리케이션 메모리 밖에서 버퍼링

데이터를 빠르게 모을 수 있는 서비스를 사용하는 방식이다.

  • Redis 활용: 락이 없는 인메모리 DB인 Redis의 INCR 명령어를 사용하고, 들어온 각 명령어들이 자동으로 직렬화 되기에 문제가 발생하지 않는다.
  • Kafka Streams 활용: 조회수 이벤트를 발행하고, 이를 윈도우 단위로 묶어 처리한다. 고가용성, 유지보수 편의성을 모두 만족한다.

이 방식들은 속도와 데이터의 보존으로 고가용성을 만족한다.

하지만 운영 시점에 네트워크 순단, 서비스 일시적 다운, 서버에 대한 방화벽 만료기간 확인, 데이터 스키마 관리 등 운영 관리 비용이 증가한다.

 

글을 마치며

RDBMS는 일관성을 지키기에 매우 뛰어난 시스템이다.

하지만 조회수처럼 가벼우면서도 폭발적인 데이터까지 일관성의 잣대로 묶어두기엔 그 비용이 너무 크다.

 

꼭 RDBMS, Redis, Kafka를 사용하기 앞서서 지금 생성되는 데이터가 어떤 특성을 가지고 있고, 어떤 흐름과 조립이 필요한 데이터인지 생각해보는 글이 되었으면 한다.