원문 캡처 · seangoedecke.com
원문 캡처 · seangoedecke.com

소프트웨어를 설계할 때는 충족할 요구사항뿐 아니라 포기해도 되는 요구사항도 중요하다. 무엇이 느려도 되는지, 데이터가 오래된 상태여도 되는지, 유연하지 않아도 되는지 판단하면 더 단순하고 우아한 설계를 할 수 있다.

유닉스 소켓에 요청을 직접 대기열로 넣는 Unicorn은 느린 클라이언트 처리 문제를 해결하지 않는 대신 구현을 단순화한다. 서버 인스턴스끼리 변경 가능한 상태를 공유하지 않으므로 각 서버를 별도 프로세스로 실행해 격리할 수도 있다. 느린 클라이언트가 초당 한 패킷씩 요청을 보내도 서버 프로세스가 계속 묶여야 하는가? Unicorn의 답은 그렇다는 것이며, 앞단에 nginx나 HAProxy를 두라고 권한다.

S3는 여러 객체를 한 트랜잭션으로 갱신하는 기능을 제공하지 않는다. 대신 저장소 파티션을 가로지르지 않고 데이터를 읽고 쓸 수 있어 더 빠르고, 조정해야 할 요소와 단일 진실 공급원이 줄어 확장하기 쉽다. StatsD는 기본적으로 UDP를 사용해 전송한 데이터가 모두 저장된다고 보장하지 않는다. 이 요구사항을 포기한 덕분에 네트워크 부담이 줄어든다. 데이터를 계속 전송하는 통계 집계 서비스에는 유리한 절충이다.

데이터가 항상 최신이어야 한다는 요구를 내려놓고 데이터를 엣지 캐시에 분산하면 속도와 확장성을 얻을 수 있다. SQLite는 동시 쓰기를 지원하지 않는 대신 단일 파일로 실행할 수 있어 운영이 간단하다. Cursor의 분산 Git 백엔드는 높은 꼬리 지연을 받아들여 더 쉽게 확장할 수 있다. 잘 설계된 시스템은 약점을 강점으로 바꾼다.

요구사항을 낮추기 어려운 이유

실제 소프트웨어를 설계하거나 분산 시스템을 공부해 본 사람이라면 시스템 설계가 절충의 과정임을 안다. 속도, 동시성, 최신 데이터, 단순한 인터페이스, 복잡한 사용자 행동 지원 등 가능한 모든 요구사항을 충족하려 하면 작동하는 시스템조차 설계하기 어렵다.

어떤 요구사항을 완화할지 판단하기 어려운 까닭은 기술과 사업에 대한 이해가 모두 필요하기 때문이다. 설계 변경이 데이터의 오래된 상태를 어느 정도 허용하는지 기술적으로 정확히 파악해야 하고, 그 정도를 사용자가 받아들일 수 있는지도 사업 관점에서 판단해야 한다.

대개 기술을 아는 엔지니어가 영향을 설명하고, 제품 관리자가 사업 측면에서 판단한다. 하지만 두 역할은 서로 다른 동기를 갖고 상대의 일을 확인하기 어려워 신뢰를 쌓기 힘들다. 신뢰가 없으면 제품 관리자는 요구사항을 양보하지 않으려 할 수 있고, 그 결과 설계가 해결 불가능한 상태에 놓인다. 반대로 제품 관리자가 데이터가 어느 정도 오래되어도 괜찮다고 설득하려는데 엔지니어가 완벽한 시스템을 고집하는 경우도 있다.

이 점은 1인 개발자나 스타트업이 대기업보다 앞서 나갈 때가 있는 이유를 설명해 준다. 한 사람이 기술적 결정과 사업적 결정을 함께 내리면 대기업에서는 하기 어려운 절충을 선택해 더 간결하고 강력한 시스템을 만들 수 있다.

코딩 에이전트와 요구사항

코딩 에이전트와 함께 일할 때는 충족하지 않아도 될 요구사항을 아는 일이 더욱 중요하다. 최첨단 모델은 유능하지만, 강화학습(RLVR)을 거치며 채점자의 요구를 충족하는 데 집중하도록 훈련됐다. 숙련된 사람보다 사업 맥락도 부족한 경우가 많다. 그래서 실제로는 필요하지 않은 요구사항을 놓친 정확성이나 일관성 문제로 오인할 수 있다.

예를 들어 모델은 서로 어느 정도 관련된 두 데이터가 항상 동기화되도록 뮤텍스 같은 동시성 제어를 추가하곤 한다. 이미 확인한 데이터가 요청 처리 도중 바뀌지 않았는지 다시 검사하는 코드도 넣을 수 있다. 하지만 그런 검사가 우려하는 TOCTOU 오류를 막지 못할 수도 있다. 관리자가 권한을 잃는 순간 관리 요청을 처리하면 어떻게 되는지, 저장소 파티션 A의 캐시가 B보다 아주 조금 먼저 갱신되면 사용자가 서로 다른 데이터를 보게 되는지 같은 예외 상황도 고려한다. 이런 식의 변경을 반복하거나 “실수하지 말라”고 지시하면 네 줄짜리 수정이 천 줄짜리 복잡한 코드로 불어날 수 있다.

그래서 코드 리뷰가 끝났다는 주장에 의문이 든다. 큰 AI 생성 코드 변경을 검토할 때마다 대략 절반은 불필요한 요구사항이 추가돼 차이가 복잡해진 것을 발견한다. 더 살펴보면 모델이 그 요구사항을 스스로 넣은 경우가 많다. 코드 리뷰에 관한 논의도 이런 맥락에서 볼 수 있다.

캐시를 다룰 때는 데이터가 때때로 오래된 상태이거나 캐시 인스턴스 사이에서 잠시 일치하지 않아도 괜찮다고 프롬프트에 적곤 한다. Go 코드를 검토할 때는 불필요해 보이는 뮤텍스나 간접 처리 계층이 추가됐는지 먼저 살핀다. 이런 요구사항을 확인 없이 코드베이스에 계속 받아들이면 시스템은 시간이 지날수록 굳어진다. 검사를 거듭하고 잠금을 걸며 일관성을 강제하는 코드가 쌓이면 시스템 확장이 어려워진다.

우아한 시스템을 유지하려면 데이터가 오래되거나 꼬리 지연이 가끔 발생하는 것을 받아들이고, 꼭 필요한 경우가 아니면 조정을 피해야 한다. 핵심은 충족하지 않아도 되는 요구사항이 무엇인지 가려내는 일이다.