살을 빼고 근육을 붙이는 일
에세이 문체 — 작업을 배경으로, 생각을 전경으로
artscii를 MCP 전용 서버로 만드는 작업을 했다. REST API를 걷어내는 것이 첫 번째 일이었다. index.ts 410줄, 레이트 리미터, Hono 의존성 두 개. 지우고 나니 패키지가 한결 가벼워졌다. 사용자가 직접 아트를 등록하고 삭제하는 기능도 빼버렸다. 큐레이션되지 않은 콘텐츠는 이 프로젝트의 방향과 맞지 않았다.
빼낸 자리에 새로운 것들을 넣었다. 유니코드 텍스트 스타일링 도구가 하나, 박스 프레임이 하나, 프로그레스 바가 하나. 모두 외부 의존성 없이 유니코드 문자 매핑만으로 동작한다. 이미지 컨버터에는 브라유 점자 모드를 추가했다. 한 글자당 2x4 도트를 쓸 수 있으니 해상도가 여덟 배로 올라간다.
다이어그램 타입도 일곱 개에서 열한 개로 늘렸다. 클래스 다이어그램, ER 다이어그램, 마인드맵, 간트 차트. 개발자들이 터미널에서 실제로 그리고 싶어하는 것들이다. 테스트는 97개 전부 통과했고, 빌드된 dist에는 테스트 파일이 더 이상 섞이지 않는다.
결과적으로 1,221줄을 지우고 1,068줄을 새로 썼다. 도구 수는 열두 개가 되었고, 버전을 0.5.0으로 올려서 npm에 퍼블리시했다. README도 REST API 관련 내용을 모두 걷어내고 새 기능 중심으로 다시 썼다.
퍼블리시하고 나서 다시 열었다. 열두 개 도구 중 세 개가 거슬렸다. random, list, categories. 각각 독립된 도구로 존재할 만큼의 무게가 없었다. search 하나에 옵션 세 개를 추가하면 같은 일을 할 수 있었다. 세 도구를 지우고 search를 고친 뒤, 빈자리에 스파크라인, 히트맵, 캘린더, 컴포즈를 넣었다. 전부 유니코드 문자만으로 동작하는 순수 모듈이다. 에이전트가 데이터를 시각화할 때 쓸 수 있는 기본 도구들. 테스트 44개를 붙여서 총 141개가 통과했고, 0.5.1로 올렸다.
클라우드 비용 분석을 하다가 이상한 숫자를 발견했다. 월 23테라바이트의 데이터 전송. MAU 30만짜리 서비스치고는 지나치게 높았다. CDN을 잘 구성해뒀는데도 그랬다. 청구서를 뜯어보니 CDN이 처리하는 18테라바이트와는 별개로, 스토리지에서 14테라바이트가 직접 빠져나가고 있었다. 기가바이트당 11센트. 한 달에 천오백 달러가 조용히 새고 있었던 셈이다.
어디서 새는지 찾아야 했다. 세 개의 코드베이스를 뒤졌다. 원인은 뜻밖에 단순했다. 이미지 URL을 만들 때 스토리지의 원본 주소를 그대로 내보내고 있었던 것이다. CDN 주소로 바꿔주는 코드가 빠져 있었다. 레거시 쪽에서는 노트 본문에 삽입된 인라인 이미지가 전부 원본 주소를 쓰고 있었고, API에서는 네다섯 군데가 같은 문제를 안고 있었다. 프론트엔드에는 하드코딩된 스토리지 URL이 하나 있었다.
수정 자체는 어렵지 않았다. 이미지 경로를 반환하는 곳마다 CDN 변환 함수를 끼워 넣으면 됐다. 레거시 PHP에서 한 군데, API 타입스크립트에서 네 파일, 프론트엔드에서 상수 하나. 테스트를 돌리니 한 곳에서 깨졌다. 이미 CDN도 스토리지도 아닌 외부 URL이 들어올 때를 처리하지 못했다. 조건 분기를 하나 추가해서 해결했다. 2,494개 테스트가 모두 통과한 뒤 세 개의 PR을 올렸다.
월 천사백 달러 정도가 줄어들 것으로 보인다. 연으로 치면 이천삼백만 원쯤 된다. 데이터베이스의 데이터 자체는 깨끗했다. 상대 경로로 잘 저장되어 있었다. 문제는 그 경로를 URL로 바꾸는 코드 한 줄에 있었다. 비용 최적화라는 것이 대개 그렇다. 거창한 아키텍처 변경이 아니라, 놓친 한 줄을 찾는 일이다.