WAL: 분산 시스템에서 쓰기 경로를 중앙화하는 방법
- 개발
- 2026. 1. 3. 23:05
서비스 규모가 커지면서 쓰기(write) 작업은 점점 많아졌고, 자연스럽게 각 서비스는 자신에게 맞는 방식으로 데이터를 저장하기 시작했습니다. 어떤 서비스는 데이터베이스에 직접 쓰고, 어떤 서비스는 메시지 큐를 거쳐 비동기로 처리했습니다.
문제는 장애가 발생했을 때 드러났습니다. 쓰기 경로가 서비스마다 달라지면서 "이 쓰기가 실제로 처리되었는지",
"어디까지 반영된 상태인지"를 판단할 수 없게 된 것입니다. 즉, 시스템 전체의 쓰기 상태를 하나의 기준으로 확인할 수 없게 되었습니다.
넷플릭스는 이 문제를 개별 서비스의 로직을 고치는 방식이 아니라, 쓰기 자체의 흐름을 통제하는 방식으로 해결했습니다.
그 선택이 바로 WAL(Write-Ahead Logging)이었습니다. 이 글에서는 넷플릭스가 WAL을 통해 분산 환경에서 흩어진 쓰기 경로를 어떻게 하나의 통로로 중앙화했는지, 그리고 그로 인해 어떤 문제를 해결할 수 있었는지를 살펴봅니다.
WAL이란 무엇일까?
넷플릭스가 WAL을 통해 분산 환경의 문제를 어떻게 해결했는지 살펴보기 전에, 먼저 WAL(Write-Ahead Logging)이 무엇인지부터 짚고 넘어가겠습니다.
WAL은 데이터를 실제 저장소(DB 등)에 반영하기 전에, 해당 변경 내용을 먼저 내구성 있는 로그에 기록하는 방식입니다.
그렇다면, 내구성이 있는 로그는 무엇일까요? 단순히 println()을 붙이는 방법을 말하는 걸까요?
만약, println()으로 로그를 작성하게 되면 서버와 함께 로그도 함께 죽게 됩니다. 즉, 판단 근거가 사라지는 거죠. 그렇게 되면 복구가 불가능해지며 사실상 WAL의 의미가 상실된다고 생각합니다. 그래서 WAL은 장애가 난 뒤에도 무슨 일이 일어났는지를 다시 확인을 할 수 있어야 합니다.
일반적으로 WAL에서 말하는 내구성은 다음과 같은 조건들을 만족해야 합니다.
1. 디스크에 기록
2. 쓰기 완료가 실제 저장을 의미
3. 프로세스 재시작 후에도 다시 읽을 수 있음
그렇다면 이러한 조건을 만족하는 내구성 있는 로그에는 어떤 것들이 있을까요?
대표적으로 데이터베이스의 WAL 파일, 파일 시스템에 기록되는 append-only 로그, 그리고 메시지 큐의 persistent log 등을 예로 들 수 있습니다. 이들의 공통점은 단 하나입니다. 서버가 종료되더라도 로그 자체는 사라지지 않는다는 점입니다.
여기서 한 가지 의문이 들 수 있습니다. println()이나 logback과 같은 로깅 프레임워크를 사용해 로그를 파일로 남기는 방식도 결국 디스크에 기록되는 것인데, 이 역시 WAL로 사용할 수 있지 않을까요?
println()또는 로깅 프레임워크가 WAL이 될 수 없는 이유
결론부터 말씀드리자면, 일반적인 애플리케이션 로그는 WAL이 요구하는 내구성과 신뢰성을 충족하지 못합니다.
이유는 크게 네 가지가 있습니다.
1. 기록 완료 시점을 신뢰할 수 없다.
WAL에서 가장 중요한 점은 해당 로그가 실제로 저장되었는지 여부를 신뢰할 수 있어야 한다는 것입니다.
그러나 일반적인 애플리케이션 로깅은 성능을 우선하기 때문에, 로그를 메모리에 먼저 적재한 뒤
별도의 스레드가 나중에 디스크에 기록하는 비동기 방식으로 동작합니다.
즉, 로그 출력 코드가 실행되었다는 사실만으로는 해당 로그가 실제로 디스크에 기록되었음을 보장할 수 없습니다.
반면 WAL에서는 로그가 내구성 있는 저장소에 기록되는 시점이 곧 트랜잭션의 커밋 기준이 됩니다.
이러한 이유로, 애플리케이션 로그는 WAL이 요구하는 신뢰성과 기준점을 제공할 수 없습니다.
2. 장애 복구를 전재로 설계되지 않았다.
로깅 프레임워크의 주된 목적은 사람이 시스템의 상태를 관찰하고 문제를 추적하기 위한 것입니다.
그렇기 때문에 로그 포맷이 완전히 일정하지 않아도 되고, 일부 로그가 유실되더라도 시스템 동작에 치명적인 영향을 주지는 않습니다.
또한 로그의 순서가 완벽하게 보장되지 않더라도 큰 문제가 되지 않습니다.
반면 WAL은 전혀 다른 목적을 가집니다. WAL의 로그는 사람이 아니라 시스템이 직접 읽어야 하며,
쓰기 작업의 순서가 명확하게 보장되어야 하고, 이를 기반으로 Redo 또는 Undo 여부를 판단할 수 있어야 합니다.
즉, WAL은 장애 이후의 복구 시나리오를 전제로 설계된 반면, 애플리케이션 로그는 애초에 이러한 복구 과정을 고려하지 않고 만들어졌습니다. 이러한 설계 목적의 차이로 인해, 애플리케이션 로그는 WAL이 요구하는 역할을 수행할 수 없습니다.
3. 트랜잭션의 성공 여부를 판단할 수 없다.
WAL 로그에는 반드시 다음과 같은 정보들이 포함됩니다. 어떤 쓰기 작업인지,
어느 트랜잭션에 속한 작업인지, 그리고 해당 트랜잭션이 커밋되었는지 혹은 중단되었는지에 대한 명확한 상태 정보입니다.
이 정보들이 있어야 장애 이후에 Redo 또는 Undo 여부를 판단할 수 있습니다.
반면 일반적인 애플리케이션 로그에는 이러한 트랜잭션 경계가 존재하지 않습니다.
로그가 남아 있다는 사실만으로는 해당 작업이 성공을 의미하는지, 중간에 실패했음을 의미하는지를 판단할 수 없습니다.
즉, 로그가 기록되어 있더라도 이 쓰기 작업을 다시 실행해야 하는지, 아니면 되돌려야 하는지를 결정할 수 없는 것입니다.
이처럼 애플리케이션 로그는 트랜잭션의 성공 여부를 판단하기 위한 공식적인 기준으로 사용할 수 없습니다.
4. 시스템이 신뢰하는 단일 기준점이 될 수 없다.
WAL의 핵심 역할은 분명합니다. "이 쓰기는 존재했다"는 사실을 시스템 차원에서 공식적으로 보증하는 기준점이 되는 것입니다.
즉, WAL에 기록되었다는 사실만으로 해당 쓰기 작업의 존재 여부를 모든 구성 요소가 동일하게 인식할 수 있어야 합니다.
반면 애플리케이션 로그는 애플리케이션 내부에서 참고하기 위한 부가적인 정보에 불과합니다.
이 로그는 시스템 전체가 공통으로 신뢰하는 기준점이 아니며, 다른 서비스나 저장소가 이를 기준으로 동작하도록 설계되지도 않았습니다.
만약 애플리케이션 로그를 기준으로 시스템을 복구하거나 쓰기 성공 여부를 판단하게 된다면,
각 구성 요소가 서로 다른 기준을 갖게 되고 그 순간 정합성과 신뢰성은 즉시 무너지게 됩니다.
이러한 이유로 애플리케이션 로그는 WAL이 요구하는 단일하고 신뢰 가능한 기준점이 될 수 없습니다.
이러한 이유들로 인해, 애플리케이션 로그는 WAL의 역할을 수행할 수 없습니다.
지금까지의 설명을 따라오다 보면 자연스럽게 한 가지가 떠오릅니다. 바로 데이터베이스(DB)입니다.
데이터베이스는 트랜잭션을 공식적으로 지원하며, 그 내부에는 이미 WAL 개념이 깊숙이 자리 잡고 있습니다.
대표적인 예가 Redo Log와 Undo Log입니다. 이 로그들은 트랜잭션의 변경 이력을 기록함으로써,
장애 발생 시 작업을 다시 적용하거나 되돌릴 수 있는 근거를 제공합니다.
즉, 데이터베이스 내부의 WAL은 트랜잭션이 원자성과 영속성을 보장하는 핵심 메커니즘입니다.
이 점만 놓고 보면, DB는 이미 WAL을 활용해 안정적인 쓰기를 수행하고 있다고 볼 수 있습니다.
넷플릭스는 왜 WAL을 선택해야만 했을까?
넷플릭스는 데이터베이스가 내부적으로 WAL을 사용하고 있음에도 불구하고,
이를 시스템 전체의 공통 WAL로 선택하지 않았습니다. 그 이유는 단순합니다.
모든 쓰기 작업이 데이터베이스를 통해 이루어지지 않기 때문입니다.
넷플릭스의 쓰기 작업은 데이터베이스뿐만 아니라 캐시, 검색 인덱스, 메시지 시스템 등 여러 저장소로 동시에 확장됩니다. 이러한 환경에서 데이터베이스의 WAL은 자신의 저장소 상태를 복구하는 데에는 충분하지만, 시스템 전반의 쓰기 흐름을 하나의 기준으로
통제하기에는 한계가 있습니다.
즉, 데이터베이스의 WAL은 'DB 자신'을 위한 복구 메커니즘일 뿐, 분산 환경에서 발생하는 모든 쓰기 작업을
아우르는 기준점이 될 수는 없었습니다.
그렇다면, 넷플릭스는 어떻게 WAL을 구축했을까?
넷플릭스의 WAL 시스템은 개발자에게 매우 단순한 인터페이스만을 제공합니다.
내부에서는 다양한 저장소로의 fan-out, 상태 추적, 재시도, 복구와 같은 복잡한 작업들이 수행되지만,
이 모든 복잡성은 WAL 시스템 내부로 감춰집니다.

개발자가 상호작용하는 API는 단 하나입니다. 바로 Write to Log입니다.
즉, 개발자는 "이 쓰기를 로그에 기록한다"는 행위만 수행하면 되고, 그 이후의 처리 과정은 WAL 시스템이 책임집니다.
이러한 방식으로 넷플릭스는 쓰기 로직을 단순화하는 동시에, 분산 환경에서 발생하는 복잡한 정합성과 복구 문제를
중앙에서 통제할 수 있도록 설계했습니다. 응답까지 그리면 다음처럼 그릴 수 있을 거 같습니다.

WAL의 각 네임스페이스(namespace)는 동일한 인터페이스를 공유하면서도, 내부 동작 방식은 독립적으로 구성할 수 있습니다.
예를 들어 어떤 네임스페이스는 Kafka를 기반으로 로그를 처리하도록 설정할 수 있고,
다른 네임스페이스는 Amazon SQS와 같은 메시지 큐를 사용하도록 구성할 수도 있습니다.
이처럼 WAL은 저장 매체나 메시징 시스템을 고정하지 않음으로써, 각 팀이 자신의 요구사항에 맞게
재시도 횟수, 백오프 전략, 처리 지연(latency)과 같은 세부 동작을 미세 조정할 수 있도록 설계되었습니다.
즉, 넷플릭스의 WAL은 쓰기의 기준점은 중앙화하면서도, 운영 전략은 각 도메인에 위임하는 구조를 취하고 있습니다.
마무리
넷플릭스는 다양한 쓰기 패턴과 장애 시나리오를 지원할 수 있도록 WAL 시스템을 페르소나(Persona)라는 개념으로 설계했습니다.
페르소나는 각 네임스페이스가 어떤 저장소를 사용하고, 어떤 재시도 전략과 백오프 정책을 가지며,
어떤 수준의 지연과 신뢰성을 요구하는지를 정의하는 쓰기 정책의 집합입니다.
이를 통해 넷플릭스는 모든 쓰기를 하나의 트랜잭션으로 묶으려 하지 않고, 대신 로그를 기준점으로 삼아 각 쓰기가 일관된 규칙 아래 동작하도록 통제합니다. 즉, 넷플릭스의 WAL은 데이터베이스 트랜잭션을 분산 환경에 그대로 확장한 것이 아니라,
분산 환경에 맞게 정합성의 책임과 기준을 다시 정의한 설계라고 볼 수 있습니다.
어떻게 보면 넷플릭스는 분산 환경에서 "완벽한 트랜잭션"을 구현하려 하지 않고, 통제 가능한 정합성의 기준을 시스템 차원에서 확립했다고 볼 수 있죠.
출처
https://blog.bytebytego.com/p/how-netflix-built-a-distributed-write
'개발' 카테고리의 다른 글
| Lint는 무엇인가? (1) | 2026.01.08 |
|---|---|
| MDC란 무엇인가 그리고 어떻게 설정할 수 있을까? (1) | 2026.01.06 |
| 멀티 모듈 개발기(1) (feat.grale) (0) | 2026.01.02 |
| 스쿼시, 머지, 리베이스 (0) | 2025.12.31 |
| 정합성은 코드가 아니라 구조에서 결정된다 (feat. Uber) (1) | 2025.12.30 |