백엔드라는 직업의 본질 — 요청과 응답 사이, 그 좁은 회랑에서
3 views
# 백엔드라는 직업의 본질 — 요청과 응답 사이, 그 좁은 회랑에서
신입 엔지니어에게 백엔드를 설명할 때, 나는 늘 같은 문장에서 출발한다. "우리는 요청과 응답 사이의 좁은 회랑(回廊)에서 일한다." 그 회랑의 폭은 보통 수십 밀리초다. 그 짧은 시간 안에서 인증이 이루어지고, 권한이 검증되며, 트랜잭션이 열리고, 캐시가 갱신되고, 큐에 이벤트가 적재된다. 마침내 직렬화된 한 덩어리의 바이트가 클라이언트로 떠난다. 회랑은 짧지만, 그 안에 쌓인 결정은 길다.
백엔드 엔지니어링의 본질은 도구의 숙련이 아니다. **시간·상태·신뢰**라는 세 개의 질서를 매 요청마다 다시 세우는 일이다. 이 글은 그 세 축을 따라 실무에서 마주치는 핵심 개념들을 사유의 지도 위에 다시 배치하려는 시도다.
## 시간의 질서 — 동기, 비동기, 그리고 결국 큐

동기 호출은 인간의 직관에 가깝다. 묻고, 기다리고, 답을 받는다. 코드는 위에서 아래로 흐르고, 스택은 자라났다가 줄어든다. 문제는 이 직관이 단일 프로세스의 환상이라는 점이다. 호출자와 피호출자가 같은 운명을 공유한다는 가정, 즉 둘 중 하나가 늦으면 둘 다 늦는다는 결합은 시스템이 둘 이상으로 쪼개지는 순간 무너진다.
비동기는 그 결합을 끊는다. 정확히 말하면, 결합을 끊는 대신 **시간 자체를 자원화**한다. 즉시 응답해야 할 일과 나중에 해도 되는 일을 분리하고, 후자를 큐에 적재해 다른 워커에게 위임한다. 이는 단순한 성능 최적화가 아니라 책임의 재분배다. 결제 승인 API가 영수증 이메일 발송까지 동기로 처리하던 시절을 떠올려보라. 메일 게이트웨이가 흔들리면 결제도 흔들렸다. 두 일은 같은 트랜잭션에 묶일 이유가 없었다. 단지 그렇게 짜였을 뿐이다.
큐가 도입되면 새로운 어휘가 따라온다. 멱등성(idempotency), 재시도(retry), 백오프(backoff), 데드레터(dead letter), 지수적 폭주(exponential blowup). 메시지는 한 번 이상 전달될 수 있으며, 두 번 처리되어도 결과가 같아야 한다. 이 제약은 비즈니스 로직 깊숙이 침투한다. 주문 생성 핸들러는 `order_id`가 아니라 `request_id`를 본다. 같은 의도가 두 번 도착하면 두 번째는 조용히 첫 번째의 결과를 반환한다. 멱등성은 기능이 아니라 사고방식이다.
```python
def handle_order(request_id, payload):
existing = orders.find_by_request(request_id)
if existing:
return existing
with tx():
order = orders.create(payload)
outbox.append("OrderCreated", order.id)
return order
```
위 패턴은 **트랜잭셔널 아웃박스(transactional outbox)**라 불린다. 도메인 변경과 이벤트 발행을 같은 트랜잭션에 묶고, 발행 자체는 별도 워커가 외부 브로커로 옮긴다. 분산 환경에서 "DB는 커밋되었는데 메시지는 못 보냈다"라는 흔한 비극을 막는 장치다. 짧은 코드, 긴 함의.
## 상태의 질서 — 데이터, 트랜잭션, 그리고 일관성의 환상
백엔드의 다른 절반은 상태를 다루는 일이다. 그리고 상태를 다루는 일의 절반은, 상태가 실은 여러 곳에 흩어져 있다는 사실을 인정하는 일이다.
관계형 데이터베이스는 ACID라는 오래된 약속을 제공한다. 그 약속의 핵심은 **격리 수준(isolation level)**이다. Read Committed, Repeatable Read, Serializable. 각 수준은 동시성과 정합성 사이의 다른 타협점이다. 대다수 RDBMS의 기본값은 Read Committed지만, PostgreSQL의 Repeatable Read는 스냅숏 격리로 동작해 같은 트랜잭션 안에서 본 세계가 끝까지 변하지 않는다. 차이는 미세하지만 결제·재고·잔액 같은 도메인에서는 운명을 가른다. 격리 수준을 모른 채 트랜잭션을 쓰는 것은, 안전벨트의 작동 원리를 모른 채 시속 200km로 달리는 것과 같다.
분산 환경으로 넘어가면 약속은 더 얇아진다. CAP 정리가 가르치는 바는 단순하다. 네트워크가 분할될 때, 가용성과 일관성 중 하나를 양보해야 한다. 실무에서 이 정리의 진짜 가치는 선택을 강요하는 데 있지 않다. **모든 경계마다 어떤 일관성을 택할지 의식적으로 결정하라**는 요구에 있다. 사용자 프로필은 결과적 일관성(eventual consistency)으로 충분하다. 계좌 잔액은 그렇지 않다. 같은 시스템 안에서도 도메인마다 답이 다르다.

캐시는 이 질서를 가장 자주 무너뜨리는 장치다. 캐시는 빠르지만, 캐시는 거짓말한다. 정확히는, 과거의 진실을 현재의 진실인 양 제시한다. TTL, 쓰기 무효화(write-through invalidation), 쓰기 지연(write-behind), 스탬피드 방지(single-flight). 캐시 전략의 모든 어휘는 결국 "언제 어떤 거짓말을 허용할 것인가"에 대한 합의다. 캐시 히트율 95%라는 숫자는 자랑이 아니라 질문이다. 나머지 5%의 미스가 폭주할 때, 원본 저장소는 견디는가.
> "데이터베이스는 사실의 저장소이고, 캐시는 의견의 저장소다." — 출처가 분명치 않지만 현장에서 자주 인용되는 격언이다. 의견을 사실로 착각할 때 장애가 시작된다.
## 신뢰의 질서 — 관측, 장애, 그리고 운영의 윤리
세 번째 축은 가장 늦게 배우게 되는 축이다. 신뢰는 코드가 아니라 운영에서 만들어진다.
관측 가능성(observability)은 흔히 로그·메트릭·트레이스라는 세 기둥으로 요약된다. 그러나 도구의 나열만으로는 본질에 닿지 못한다. 관측 가능성의 진짜 정의는 다음과 같다. **시스템 외부에서 수집한 신호만으로 내부 상태를 추론할 수 있는가.** 추론이 가능하면 관측되는 것이고, 새 코드를 배포해야 원인을 알 수 있다면 관측되지 않는 것이다. 구분은 도구의 유무가 아니라 설계의 깊이에 있다.
장애는 가능성이 아니라 일정이다. 언제 일어날지 모를 뿐, 반드시 일어난다. 그러므로 백엔드 엔지니어의 일은 장애를 막는 것이 아니라 **장애의 반경(blast radius)을 좁히는 것**으로 재정의되어야 한다. 서킷 브레이커, 타임아웃, 격벽(bulkhead), 그레이스풀 디그레이데이션, 페일오버. 이 패턴들은 모두 한 가지를 말한다. 일부가 죽어도 전체는 살아야 한다.
타임아웃은 그중 가장 저평가된 장치다. 호출하는 모든 외부 의존성에는 타임아웃이 있어야 한다. 없으면 기본값을 따른다는 뜻이고, 기본값은 보통 무한대에 가깝다. 한 다운스트림의 지연이 업스트림의 스레드 풀을 잠식하고, 그 풀이 고갈되면 무관한 엔드포인트까지 응답을 멈춘다. 장애는 이런 방식으로 전파된다. 천천히, 그리고 갑자기.
배포는 신뢰의 또 다른 시험대다. 블루-그린, 카나리, 점진적 롤아웃. 어떤 방식을 택하든 핵심 질문은 같다. **잘못된 변경을 얼마나 빨리, 얼마나 작은 손상으로 되돌릴 수 있는가.** 롤백 시간이 60분인 조직과 60초인 조직은 같은 일을 하지 않는다. 후자는 실험할 수 있고, 전자는 기도한다.
## 경계의 질서 — API, 도메인, 그리고 결합의 비용
마지막으로, 위 세 축을 가로지르는 한 가지 질문이 남는다. **무엇을 무엇으로부터 분리할 것인가.**
API는 단순한 호출 인터페이스가 아니라 **변경의 계약**이다. 한 번 공개된 필드는 사라지지 않는다. 사라진다면, 그 비용은 모든 클라이언트가 나누어 부담한다. REST의 자원 모델, GraphQL의 쿼리 모델, gRPC의 RPC 모델은 각자 다른 결합 방식을 제안한다. 어느 것이 옳다기보다, 어느 결합을 감내할 수 있는가의 문제다.
도메인 주도 설계가 가르치는 바운디드 컨텍스트(bounded context)는 이 질문의 다른 형태다. 같은 단어가 다른 컨텍스트에서 다른 의미를 가질 수 있음을 인정하라는 요구다. 주문(Order)은 결제 컨텍스트에서는 청구 단위지만, 물류 컨텍스트에서는 배송 단위다. 이 둘을 한 테이블에 욱여넣는 순간, 두 도메인의 변경은 영원히 서로의 발목을 잡는다.
마이크로서비스는 이 분리를 물리적 경계까지 밀어붙이는 선택이다. 그러나 분리의 대가는 분산이며, 분산의 대가는 앞서 말한 시간·상태·신뢰의 모든 질서를 새로 세우는 일이다. 모놀리스를 잘 모듈화한 조직은 잘못 쪼갠 마이크로서비스를 운영하는 조직보다 거의 언제나 더 빠르다. 경계를 긋는 일은 자유롭게, 경계를 물리화하는 일은 신중하게.
## 닫는 말 — 회랑의 폭을 넓히지 말 것
좋은 백엔드 엔지니어는 회랑을 더 넓히려 하지 않는다. 좁은 회랑 안에서 더 정확한 결정을 내릴 뿐이다. 응답 시간 P99를 300ms에서 80ms로 줄이는 일은 새로운 기능을 추가하는 일보다 화려하지 않다. 그러나 그 감소가 가능했던 이유, 즉 어디에 캐시를 두었고 어떤 호출을 비동기로 옮겼으며 어떤 인덱스를 다시 그었는지를 한 문장으로 설명할 수 있을 때, 비로소 시스템을 이해했다고 말할 수 있다.
기술의 목록은 매년 바뀐다. Kafka가 NATS로, Redis가 KeyDB로, REST가 gRPC로 교체되어도 회랑의 구조는 변하지 않는다. 요청은 들어오고, 상태는 변하고, 응답은 나간다. 그 사이에서 우리는 시간을 자원으로 다루고, 상태의 거짓말을 관리하며, 장애의 반경을 좁힌다. 백엔드라는 직업의 정의는 거기까지다. 그 너머는 매번 새로 쓰이는 본문이다.
> 子曰, 知之者不如好之者, 好之者不如樂之者.
> 아는 자는 좋아하는 자만 못하고, 좋아하는 자는 즐기는 자만 못하다.
좁은 회랑을 즐기는 자만이, 그 안에서 오래 머문다.