← posts/b.log()

blog92@web:~$ cat posts/backend-antipatterns-16-why-we-repeat-ourselves.md

BACKEND10 min read

왜 같은 실수가 반복되는가 — 안티패턴의 생산 메커니즘

알려진 해법이 있는데도 같은 구조가 반복되는 이유. Conway의 법칙, 도구 우선 선택, 지연 도착하는 비용, 중앙화와 분산의 진자. 시리즈 완결.

15편을 다 읽어도 풀리지 않는 문제

앞의 14편은 각각 하나의 구조를 해부하고 탈출 경로를 제시했다. 그런데 그 탈출 경로들은 대부분 이미 널리 알려진 것들이다. Transactional Outbox(11편)는 2010년대 초부터 정리돼 있었고, Circuit Breaker(12편)는 2007년 책에 나온다. Conway의 논문은 1968년 것이다.

그럼에도 같은 구조가 계속 만들어진다. 새 팀, 새 언어, 새 클라우드에서 2년쯤 지나면 같은 자리에 도착한다.

이게 이 시리즈의 마지막 질문이다. 해법이 이미 알려져 있는데 왜 안티패턴이 계속 생산되는가. 답은 코드가 아니라 그 코드를 만든 구조 — 조직, 인센티브, 시간 — 에 있다.

조직이 아키텍처를 복제한다

Melvin Conway가 1968년 Datamation에 실은 "How Do Committees Invent?"의 결론은 한 문장이다.

시스템을 설계하는 조직은, 그 조직의 커뮤니케이션 구조를 그대로 복제한 설계를 만들어낸다.

이건 경향성이 아니라 거의 제약에 가깝다. 두 모듈 사이의 인터페이스는 그 모듈을 만든 두 사람이 대화한 만큼만 정교해진다. 팀 A와 팀 B가 분기에 한 번 이야기하면 A-B 인터페이스는 분기에 한 번 개선된다.

이 법칙이 앞선 편들을 다시 설명한다.

Distributed Monolith(9편)의 조직적 기원. 팀을 6개로 나누고 서비스를 6개로 쪼갰지만 로드맵이 하나라면, 6개 팀은 같은 기능을 위해 매 스프린트 조율해야 한다. 커뮤니케이션 구조는 여전히 단일 집단이고, 아키텍처는 그것을 복제한다. 배포 파이프라인만 6개로 늘어난다.

Nanoservices. 서비스가 너무 작아서 네트워크·배포·관측 오버헤드가 그 서비스의 이득을 넘는 상태다. 대개는 기술적 판단이 아니라 조직적 판단의 결과다. 서비스 경계는 비즈니스 능력을 따라야 하는데(10편) 조직도를 따라간다.

ESB(6편)의 재현. 중앙 플랫폼 팀이 게이트웨이·공용 라이브러리·표준 파이프라인을 소유하면, 그 팀이 모든 변경의 관문이 된다. 조직이 중앙집권적이면 아키텍처도 그렇게 된다. 도구가 ESB에서 서비스 메시로 바뀌어도 구조는 같다.

여기서 Inverse Conway Maneuver가 나온다. 원하는 아키텍처가 있으면 코드가 아니라 팀 경계를 먼저 바꾼다. 대가는 정직하게 말해야 한다. 조직 개편은 리팩터링보다 훨씬 비싸고 되돌리기 어렵고 사람에게 영향을 준다. 아키텍처가 조직의 다른 목표보다 중요할 때만 정당한 수단이다.

도구를 먼저 고르고 문제를 맞추는 구조

Golden Hammer는 Brown et al.(1998)의 원래 카탈로그에 있는 항목이다. 익숙한 도구 하나에 모든 문제를 맞춰 넣는 구조를 가리킨다.

오늘날 이것이 나타나는 형태는 개인의 도구 편애보다 조직 차원의 유행 추종에 가깝다. 판단 근거가 "문제의 성질"이 아니라 "어떤 회사가 쓴다"가 되는 경우다.

컨퍼런스 발표와 기술 블로그가 위험한 정보원이 되는 이유는 두 가지 편향 때문이다. 첫째, 생존자 편향 — 실패한 마이그레이션은 발표되지 않는다. 둘째, 맥락 절단 — 발표에서 가장 먼저 잘려 나가는 것이 규모와 제약 조건이다. 일 10억 요청과 팀 200명이라는 전제는 슬라이드에 남지 않고 해법만 남는다.

이게 1편에서 세운 명제의 사회적 버전이다. 맥락이 사라진 뒤에도 남아 있는 해법. 개인의 코드에서는 시간이 맥락을 지우고, 조직의 기술 선택에서는 전파 과정이 맥락을 지운다.

부채라는 비유가 잘못 쓰이는 지점

Ward Cunningham이 1992년 기술 부채라는 비유를 만들었을 때 의도한 뜻은 흔히 쓰이는 뜻과 다르다. 그의 설명에서 부채는 "대충 짜고 나중에 고친다"가 아니라 "지금의 이해 수준으로 최선을 다해 출시하고, 도메인 이해가 깊어지면 그 이해를 코드에 반영한다"였다. 갚아야 할 것은 나쁜 코드가 아니라 낡은 이해다.

오해된 비유는 두 방향으로 실패한다. 부채가 모든 지름길의 면죄부가 되거나, 모든 부채를 갚아야 한다는 결벽이 되거나. 이자를 계산해 보면 어느 쪽도 아닌 답이 나온다.

기술 부채의 이자는 변경 빈도에 비례한다. 건드리지 않는 코드의 부채는 이자가 0이다.

2년간 아무도 열지 않은 1,500줄짜리 God Object(2편)는, 보기에는 최악이지만 갚을 이유가 없다. 반대로 매주 세 번 수정되는 300줄 모듈의 부채는 매주 세 번 이자를 낸다. Adam Tornhill이 Your Code as a Crime Scene(2015)에서 제안한 hotspot 분석이 정확히 이 계산이다. 복잡도만 보지 말고 복잡도 × 변경 빈도로 정렬한다. 데이터는 git 로그에 이미 있다.

9편에서 함께 바뀌는 서비스 쌍을 찾을 때 쓴 커밋 로그가 여기서도 그대로 데이터원이 된다.

비용이 지연되어 도착한다

안티패턴이 살아남는 가장 근본적인 이유는 비용의 시차다. 1편의 판별 조건 중 "시간축에서 순손실"이라는 항목이 이걸 가리킨다. 대부분의 안티패턴은 첫 3개월 동안 순이득이다.

결정이득이 도착하는 시점비용이 도착하는 시점
경계를 긋지 않음 (2편)즉시 (이번 스프린트)12~24개월 후
Dual Write (11편)즉시 (5줄이면 끝)첫 장애 때
계층 통과 생략 (4편)즉시스키마 변경 때
관측 도입 미루기 (15편)즉시장애 한가운데
서비스 성급히 분리 (9편)발표 자료에 즉시6개월 후 배포 조율에서

각 시점에서 개별 결정은 합리적이다. 2편의 1,800줄 파일이 각각 옳았던 커밋 수십 개의 합이었던 것과 같은 구조다. 여기에 두 가지가 더해지면 기전이 완성된다. 결정한 사람이 결과를 보지 못하는 구조 — 평균 재직 기간이 부채 만기보다 짧으면 비용은 항상 다른 사람에게 간다. 그리고 출시는 측정되고 유지보수는 측정되지 않는 보상 구조. 이 둘은 누구의 악의도 아니고, 그래서 개인의 각성으로는 고쳐지지 않는다.

중앙화와 분산의 진자

시리즈를 시대 순으로 놓고 보면 한 가지 운동이 보인다.

시대방향해결한 문제그 해법이 만든 문제
모놀리식중앙단순한 배포·트랜잭션·호출경계 부재 (2~5편)
SOA / ESB중앙 (통합 계층)이기종 시스템 통합병목·단일 장애점 (6편)
MSA분산독립 배포·팀 자율성경계 오설정, 분산의 대가 (9~12편)
서버리스분산 (극단)운영 부담 제거상태·연결·관측의 붕괴 (13~15편)
플랫폼 팀 / 서비스 메시중앙분산의 반복 비용(진행 중)

각 진동은 실제 문제를 풀었고, 그 해법의 대가가 다음 진동의 동력이 됐다. 이걸 "업계가 방향을 못 잡는다"고 읽으면 틀린 독해다.

모든 아키텍처 결정은 무엇을 중앙에 둘 것인가에 대한 답이고, 중앙화와 분산 중 어느 쪽도 영구적으로 옳지 않다. 옳은 답은 조직의 크기, 변경 빈도, 팀 간 신뢰 비용에 따라 움직인다.

이 시리즈에서 다음 유행을 예측할 수는 없지만, 다음 유행을 판정할 기준은 만들 수 있다. 새 아키텍처가 제시될 때 물을 것은 "이게 옳은가"가 아니라 "이것이 무엇을 중앙으로 옮기고 무엇을 분산시키는가, 그리고 우리 조직에서 그 교환이 남는 장사인가"다.

그래서 무엇을 할 것인가

안티패턴 목록을 외우는 것은 답이 아니다. 1편에서 지적한 대로 카탈로그의 기계적 적용 자체가 Golden Hammer다. 대신 앞의 분석에서 직접 따라 나오는 실천 네 가지가 있다.

맥락을 해법과 함께 저장한다. 안티패턴의 발생 기전이 "맥락의 소실"이라면, 대응은 맥락을 기록으로 남기는 것이다. ADR(Architecture Decision Record, Michael Nygard, 2011)이 그 최소 형식이다.

md
<!-- docs/adr/0014-outbox-for-order-events.md -->
# 14. 주문 이벤트 발행에 Transactional Outbox를 쓴다
 
- 상태: 승인 (2026-03-11)
- 맥락: 주문 서비스가 DB 커밋과 브로커 발행을 따로 한다.
  일 12만 건, 배포는 주 5회 롤링. 유실이 결제 정합성에 직결된다.
- 결정: 같은 트랜잭션에 outbox 레코드를 쓰고 전용 릴레이가 발행한다.
- 기각: 2PC — 브로커가 XA를 지원하지 않는다.
  CDC — 운영할 팀이 없다. 팀이 생기면 재검토 대상.
- 대가: 발행이 폴링 주기(1초)만큼 지연된다. 중복 발행을 전제하므로
  모든 소비자가 멱등해야 한다.

중요한 건 결정이 아니라 기각한 대안과 그 이유다. 위 예에서 3년 뒤 되돌릴 근거가 되는 줄은 "CDC — 운영할 팀이 없다"이지 결정문이 아니다.

되돌리기 비용으로 결정을 분류한다. 되돌릴 수 있는 결정은 빨리 내리고 빨리 틀린다. 되돌리기 어려운 결정 — 데이터 모델, 서비스 경계, 공개 API 계약(8편) — 에만 긴 논의를 쓴다. 대부분의 조직은 이 둘에 같은 시간을 쓴다.

측정 가능한 것으로 논쟁한다. "결합도가 높다"는 반박도 검증도 불가능하다. "이 두 리포지토리가 지난 6개월 PR의 71%에서 함께 바뀌었다"(9편)는 검증 가능하다. 아키텍처 논쟁이 취향 싸움으로 끝나는 건 대개 측정을 건너뛰었기 때문이다.

부채를 변경 빈도로 정렬한다. 전면 리팩터링 계획서보다 hotspot 상위 5개가 실행 가능하다. 데이터는 이미 저장소에 있다.

bash
# 최근 1년, 변경 횟수 × 현재 줄 수로 정렬한다.
git log --since=1.year --name-only --pretty=format: -- '*.ts' \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -40 \
  | while read changes file; do
      [ -f "$file" ] || continue
      lines=$(wc -l < "$file")
      echo "$((changes * lines))  변경 ${changes}회  ${lines}줄  $file"
    done | sort -rn | head -10

곱한 값이 큰 파일이 이자를 가장 많이 내고 있는 파일이다. 줄 수는 복잡도의 거친 대용치지만 순위를 매기는 데는 충분하다. 2편의 God Object가 이 목록에 없다면 그건 갚을 부채가 아니라 그냥 오래된 코드다.

이 안티패턴들이 오히려 정답인 경우

종장에서도 이 절은 유지한다. 이 자리가 시리즈 내내 말해온 것은 하나다.

시장 검증 전의 코드에 구조를 투자하는 것은 대부분 낭비다. 제품이 살아남을지 모르는 단계에서 서비스 경계를 정교하게 긋는 일은, 12~24개월 뒤에나 도착할 이득을 위해 지금 확실한 비용을 내는 것이다. 그 제품의 절반은 12개월을 넘기지 못한다.

의식적으로 진 부채와 모르고 진 부채의 차이는 코드에 나타나지 않는다. 차이는 기록과 만기에 있다. "이 시점에 이 이유로 이 지름길을 택했고, 이 조건이 오면 갚는다"가 적혀 있으면 그건 부채다. 아무 기록이 없으면 그건 안티패턴이다.

요약

항목내용
근본 질문해법이 알려져 있는데 왜 같은 구조가 반복되는가
조직 기전Conway's Law — 커뮤니케이션 구조가 아키텍처로 복제된다 (9·10편의 조직적 기원)
유행 기전생존자 편향 + 맥락 절단. 전파 과정이 규모와 제약을 지운다
시간 기전비용의 시차. 대부분의 안티패턴은 첫 3개월 순이득
인센티브 기전출시는 측정되고 유지보수는 측정되지 않는다. 결정자와 비용 부담자의 불일치
부채의 이자변경 빈도에 비례. 안 건드리는 코드의 부채는 이자 0
진자 운동중앙화와 분산 어느 쪽도 영구적으로 옳지 않다. 답은 조직 규모와 변경 빈도에 따라 움직인다
실천맥락을 ADR로 저장 · 되돌리기 비용으로 분류 · 측정으로 논쟁 · 변경 빈도로 정렬

시리즈를 닫으며

16편을 관통한 명제는 1편에서 세운 한 문장이었다.

안티패턴은 틀린 코드가 아니라, 맥락이 사라진 뒤에도 남아 있는 해법이다.

God Object는 경계가 공짜였던 맥락의 잔여물이었고, ESB는 통합이 가장 큰 문제였던 맥락의 잔여물이었다. Shared Database는 단일 DB의 정규화 원칙이 서비스 경계를 넘어 살아남은 것이고, 서버리스의 커넥션 폭발은 프로세스당 풀을 공유하던 습관이 인스턴스당 풀인 세계로 넘어온 결과였다.

그래서 이 시리즈가 남기려는 것은 안티패턴 열네 개의 목록이 아니라 질문 하나다.

지금 우리가 쓰고 있는 이 해법은, 어떤 맥락에서 만들어졌고 그 맥락은 아직 유효한가.

다음 시리즈는 같은 역사 축을 반대편에서 걷는다. 여기서 본 문제들에 대해 커뮤니티가 축적한 대답 — 백엔드 디자인 패턴이다. 안티패턴을 먼저 본 이유가 거기 있다. 패턴을 해법의 목록으로 외우면 그것이 다음 안티패턴이 되고, 패턴이 풀려고 했던 문제를 먼저 알면 그 패턴을 언제 쓰지 말아야 하는지도 함께 알게 된다.


다음 시리즈 — 백엔드 디자인 패턴

같은 네 시대를 따라가되 이번에는 해법 쪽을 본다. 각 패턴이 어떤 안티패턴에 대한 응답으로 태어났는지, 그리고 그 패턴이 어떤 맥락에서 다시 안티패턴이 되는지를 짝지어 읽는다.

COMMENTS (…)

댓글을 불러오는 중이에요.

NEW COMMENT0 / 1000
⌘↵ 전송

blog92@web:~$ cd ..