규모의 착각, 5부: 팀을 초월하는 시스템
출처: Dev.to
몇 년 전, 나는 주 정부 법 집행 기관을 위해 전자 수색 영장 시스템을 구축했다. 서류 절차, 법원 물류, 몇 시간씩 기다리는 일 – 우리는 그것을 두 분 안에 끝냈다. 설계하고, 만들고, 배포하고, 인계했다.
그 시스템은 아직도 가동 중이다. 8년이 지난 지금도. 우리가 만들 때는 존재하지 않았던 영장 유형까지 처리하도록 확장되었다. 원래 팀은 몇 년 전 떠났고(대부분 은퇴했다). 요구사항 문서는 이제 아무도 열어보지 않는 폴더에 있을 것이다.
그럼에도 여전히 동작한다.
나는 이 점을 많이 생각했다. 왜 그 시스템은 다른 많은 시스템처럼 사라지지 않고 살아남았을까? 나는 기술적으로 더 흥미로웠던 것들을 만들었지만, 빠르게 움직이고 부수는 문화 속에서 2년 안에 다시 작성되기도 했다. 무엇이 달랐을까?
이 글은 규모가 커지면서 시스템을 은밀히 무너뜨리는 가정들에 대한 연재의 마지막 포스트다. 하지만 이번에는 다른 주제, 즉 시스템을 오래 지속하게 만드는 요소에 대해 이야기한다.
내가 제대로 하지 못한 부분부터 얘기하고 싶다. 왜냐면 그 실수가 더 교훈적이라고 생각하기 때문이다.
나는 버그처럼 보이는 패턴을 가진 코드베이스를 인수받았다. 한 서비스가 두 개의 테이블에 데이터를 쓰고 있었는데, 두 번째 쓰기는 이미 다른 곳에 존재하는 데이터를 복사하는 듯했다. 명백히 죽은 코드, 즉 자신이 남긴 흔적을 정리하지 않은 사람의 기술 부채였다.
나는 그것을 제거했다. 테스트는 통과했고, 뭔가 유용한 일을 했다는 느낌에 생산성을 느꼈다.
그로부터 일주일 뒤, 두 번째 쓰기가 방어하고 있던 바로 그 시나리오에서 데이터 일관성 사고가 발생했다. 문서도 없고, 코드에 주석도 없으며, ADR도 어디에도 없었다. 그 코드를 작성한 엔지니어는 이미 회사를 떠난 뒤였다.
두 번째 쓰기는 실제로 부하를 지고 있었지만 완전히 보이지 않았다. 나는 그것이 “중복”이라고 판단해 뽑아냈지만, 왜 존재했는지 고민하지 못한 것이었다.
코드는 시스템이 무엇을 하는지는 알려주지만, 왜 그렇게 하는지는 말해주지 않는다. 규모가 커질수록 이 두 사이의 간극이 심각한 장애(SEV)의 온상이 된다.
대부분의 엔지니어링 팀은 자신들을 위해 시스템을 설계한다. 구조, 네이밍, 추상화에 대한 결정은 현재 존재하는 팀에게 최적화되어 있다. 왜냐하면 그 팀이 시스템을 만들고 현재 사용하고 있기 때문이다.
문제는 그 팀이 영원히 존재하지는 않는다는 점이다. 사람들은 떠나고, 우선순위는 바뀐다. 시스템을 이어받는 팀은 당신의 맥락을 알 수 없으며, 질문을 할 수도 없고, 남겨진 흔적만으로 의도를 추론해야 한다.
대부분은 그 흔적이 거의 없다.
오랫동안 살아남는 시스템은 설계 단계에서 다른 질문을 던진다: “우리에게 의미가 있나요?”가 아니라 “여기에 있지 않은 사람이 이해할 수 있을까요?”
이 작은 변화가 구체적인 행동을 바꾼다. 이름 짓는 방식, 외부화와 내재화의 비율, 런북이 시스템을 만든 팀을 위한 것인지, 2시 새벽에 사고가 발생했을 때 아무도 부를 사람이 없는 상황의 엔지니어를 위한 것인지 등이다.
수색 영장 시스템은 거의 과도하게까지 설정 가능하도록 설계되었다. 새로운 문서 유형? 코딩이 아니라 설정. 새로운 승인 체인? 설정. 새로운 워크플로우 상태? 설정. 솔직히 말해 조금 과했을지도 모른다.
주장의 핵심은 기술적 정교함이 아니라 단순한 믿음이었다: 요구사항은 계속 변하고, 우리는 언제든 코드를 바꿀 수 없을 것이다.
8년이 지나도 재작성되지 않았다. 그 믿음이 살아남았다.
똑똑한 코드에 대한 이야기: 짧고 만족스럽게 문제를 해결한다. 나는 똑똑한 코드를 쓰는 것이 즐겁고, 똑똑해 보인다. 하지만 몇 달 뒤 맥락을 잊어버리면 이해하기 어렵고, 전혀 그 현장에 없던 사람에게는 거의 불가능에 가깝다.
설정 가능한 코드는 쓰기 지루하다. 장황하고, 때로는 반복적이다. 하지만 팀이 떠난 뒤에도 수년간 가동되는 시스템에서 흔히 볼 수 있다.
똑똑한 시스템을 이어받은 팀은 의도를 역공학해야 한다. 설정 가능한 시스템을 이어받은 팀은 대부분 그대로 읽으면 된다. 2시 새벽에 나는 어느 쪽을 더 선호할까?
엔지니어는 문서를 작성하려고 한다. 기술 지옥으로 가는 길은 “좋은 문서 작성 의도”로 포장되어 있다. 하지만 실제로는 문서가 작성되지 않거나, 한 번만 작성돼 설계된 상태를 반영하고 배포된 실제 시스템과는 달라진다. 6개월 뒤엔 틀려 있고, 누가 현재 정확한 버전을 써야 할지 몰라 업데이트되지 않는다.
실제로 살아남아 유용한 문서는 포괄적인 스펙이 아니다. 바로 결정 로그다.
원 팀이 어떤 문제에 부딪혔는가? 어떤 대안을 고려하고 거절했는가? 왜 그 선택을 했는가? 어떤 트레이드오프를 의도적으로 받아들였는가?
이 정보는 사람들의 머릿속에 있다. 그 사람들이 떠나면 정보도 함께 사라진다. 이어받는 팀은 시스템이 왜 그렇게 동작하는지 모른다. 단지 동작한다는 것만 안다. 그리고 의도된 부분을 “수정”하면서 이미 해결된 버그를 다시 만들곤 한다—대개 최악의 시점에.
아키텍처 결정 기록(ADR)은 반드시 형식적일 필요는 없다. 짧은 문서: 상황, 고려한 옵션, 내린 결정, 받아들인 트레이드오프. 결정이 내려진 시점에, 상황이 신선할 때 작성하고, 다음 팀이 실제로 찾을 수 있는 곳에 보관한다.
한 ADR 덕분에 3년 동안 두 개의 별도 팀이 내 시스템에서 같은 실수를 반복하지 않았다. 그 한 페이지는 코드베이스의 어떤 다른 단일 아티팩트보다 더 큰 가치를 지녔다. 그리고 두 번째 쓰기에 대한 ADR를 작성했다면, 일주일 간의 사고는 5분짜리 읽기로 끝났을 것이다.
당신이 만든 시스템에 문제가 생기면, 어디를 봐야 할지 안다. 어떤 메트릭이 중요한지, 어떤 로그 라인이 신호를 담고 있는지, 어떤 알림이 실제 문제를 의미하는지 알게 된다.
반면, 물려받은 시스템에 문제가 생기면, 처음부터 다시 시작한다. 모든 메트릭이 중요해 보이고, 모든 로그 라인이 정답일 수도 있다. 아직 직감이 없기 때문이다.
팀을 초월해 살아남는 시스템은 스스로를 설명한다. 의미를 담은 이름을 가진 메트릭, 무슨 의미인지 알려주는 구조화된 로그, 요청이 지나가는 모든 컴포넌트를 추적할 수 있는 트레이스 ID 등이다.
이것들은 기능이 아니다. 새로 온 사람이 운영할 수 있는 시스템과, 만든 사람만이 운영할 수 있는 시스템을 구분 짓는 차이점이다. 만든 사람이 더 이상 없을 때는 전자가 필요하다. 그리고 그 사람은 결국 사라진다.
이 연재의 각 포스트는 같은 문제의 다른 버전을 다루었다: 시스템이 작을 때는 무해했지만, 커지면서 비용이 크게 증가한 가정들. 카디널리티에 대한 믿음이 담긴 데이터 모델, 역할이 단순할 것이라 가정한 권한 모델, 격리된 상황에서 검증됐지만 실제 부하에서는 틀린 레이턴시 예산 등.
생존하는 시스템은 보통 가장 건축적으로 인상적인 것이 아니다. 데모에 나타나지 않는 부분에 시간을 투자한 시스템이다. 하드코딩보다 설정, 암묵적 지식보다 결정 로그, 낙관주의보다 가시성, 똑똑함보다 단순함을 선택한 곳이 바로 살아남는다.
수색 영장 시스템이 아직도 가동되는 이유는, 만든 팀이 일부러 지루한 결정을 내렸기 때문이다. 그 안에 똑똑한 코드는 없다. 요구사항을 충족하면서도 가능한 한 단순하게 만들었다. 복잡성은 정말 필요할 때만 추가했다.
하이퍼스케일이 결국 가르쳐 주는 점은, 목표는 인상적인 시스템을 만드는 것이 아니라, 당신이 없을 때도 계속 동작하는 시스템을 만드는 것이다.
이것으로 “규모의 착각” 연재 5편, 5가지 가정, 하나의 의도를 마무리한다. 함께 해줘서 고맙다!
행복한 개발 되세요!