문을 걷어내기 전에, 그 문이 몰래 지키던 것을 세어 봤다
가입 게이트를 없애야 퍼널이 열린다는 건 분명했습니다. 그런데 그 문은 유료 AI 호출의 유일한 상한이기도 했습니다. 걷어내기 전에 상한을 먼저 옮겨 지었습니다.
somesignal은 사주와 MBTI를 함께 읽어 두 사람 사이를 한 장으로 설명하는 서비스입니다. 원래는 그 한 장을 보려면 먼저 가입해야 했습니다.
이 게이트를 없애야 한다는 건 오래전부터 분명했습니다. 아직 아무것도 보여주지 않은 사람에게 생년월일과 계정을 먼저 요구하고 있었으니까요. 그런데 게이트를 걷어내려고 코드를 들여다보다 알게 된 것이 있습니다. 그 문은 사용자의 편의만 막고 있던 게 아니었습니다.
게이트는 문서에 없는 일을 하고 있었다
이 제품의 결과는 유료 AI 호출로 만들어집니다. 그리고 그 호출량에 걸려 있던 유일한 실질 상한이 "계정당 쿼터" 였습니다.
계정당 쿼터는 계정이 있어야 성립합니다. 즉 로그인 게이트가 있어야 성립합니다. 게이트만 걷어내면 누구든 아무 제한 없이 유료 호출을 태울 수 있는 상태가 됩니다. 이 사실은 어느 문서에도 적혀 있지 않았습니다. 게이트는 "가입을 받는 장치"로만 기록돼 있었고, 비용을 붙잡고 있다는 건 코드를 따라가야만 보이는 부수 효과였습니다.
여기서 선택지는 둘이었습니다. 게이트를 걷어내고 비용 문제는 나중에 보거나, 상한을 먼저 다시 세우고 그다음에 걷어내거나. 후자를 골랐습니다.
상한을 없앤 게 아니라 다른 자리에 옮겨 지었다
게이트가 혼자 지고 있던 역할을 셋으로 나눠 다시 지었습니다.
- 초대 링크 하나는 결과를 하나만 가진다. DB 유니크 제약으로 걸었습니다. 애플리케이션 레벨의 검사가 아니라 제약이라, 서버 인스턴스가 몇 개로 늘어나든 모두 같은 숫자를 봅니다.
- 링크를 만들려면 여전히 로그인해야 한다. 게이트를 없앤 건 결과를 보는 자리이지 결과를 만드는 자리가 아닙니다.
- 비회원의 분석은 링크를 만든 사람의 쿼터에 계량한다. 비용이 익명 트래픽으로 새지 않고 반드시 어떤 계정 이름 아래로 돌아옵니다.
세 개를 합치면 총비용이 "인증된 계정이 만든 링크 수"로 묶입니다. 걷어낸 것은 결과 앞의 벽이고, 남긴 것은 비용의 천장입니다. 둘은 원래 한 덩어리였지만 같은 것이 아니었습니다.
옮기고 나니 원래보다 단단해졌다
의도하지 않았던 결과가 하나 있습니다. 새로 지은 상한이 원래 것보다 낫습니다.
계정당 쿼터는 계정을 여러 개 만들면 뚫립니다. 반면 지금 구조는 링크 생성이라는 훨씬 좁은 목에 걸려 있고, 그 목은 DB 제약이 지킵니다. 편의를 위해 방어를 깎은 게 아니라, 방어를 제대로 지을 이유가 생겨서 다시 지은 셈이 되었습니다.
이건 우연이라고 생각하지 않습니다. 오래된 장치는 대개 처음 지어질 때의 이유로 서 있고, 그 뒤에 붙은 역할들은 설계된 게 아니라 얹힌 것입니다. 얹힌 것을 떼어 내 각자의 자리에 다시 지으면 대체로 더 나아집니다 — 이번에는 그것을 의식적으로 설계하기 때문입니다.
걷어내기 전에 한 번 세어 보는 값이 사고 한 번보다 싸다
제품을 막고 있는 장치를 걷어낼 때, 사람들은 그것의 표면적 목적만 봅니다. 로그인 벽은 가입을 받는 장치, 승인 단계는 실수를 막는 장치, 사용량 제한은 남용을 막는 장치. 문서에 그렇게 적혀 있고, 그것이 걷어낼 이유이기도 합니다.
그런데 오래 서 있던 장치는 그 사이에 다른 것도 부수적으로 붙잡게 됩니다. 비용, 정합성, 순서, 권한 같은 것들입니다. 이런 역할은 설계 문서에 없습니다. 애초에 설계된 적이 없기 때문입니다.
그래서 무언가를 걷어내기 전에 "이게 지금 무엇을 붙잡고 있나"를 한 번 세어 보는 일이 필요합니다. 코드를 따라가는 데 반나절이 들 수 있습니다. 그래도 사고 한 번보다는 훨씬 쌉니다. 특히 그 사고가 남의 돈이 아니라 종량제로 나가는 자기 돈일 때 그렇습니다.