누가 시간을 소유하는가
요약
시스템 설계 시 스케줄러 자체의 결함보다 비즈니스 로직의 '소유권(ownership)' 설정 오류가 더 치명적인 버그를 유발할 수 있음을 설명합니다. 에피소드나 기록 단위가 아닌, 실제 사용자(환자, 후보자)를 기준으로 시간 흐름을 제어해야 함을 강조합니다.
핵심 포인트
- 버그의 원인은 스케줄러가 아닌 잘못된 데이터 소유권 설계에 있을 수 있음
- 비즈니스 로직이 '무엇이 시간을 소유하는가'를 결정하는 방식이 중요함
- 단순한 기록(시도)이 아닌 실제 상태 변화를 기준으로 로직을 설계해야 함
- 설계 단계에서 결정이 '배관 작업'처럼 보일 정도로 당연해 보인다면 재검토 필요
한 환자가 왜 다른 사람들은 하루에 한 번씩 받는데 자신은 하루에 두 번씩 알림 문자를 받는지 물었습니다. 타당한 질문이었습니다. 그녀는 나의 Medicare RTM 엔진에 두 개의 열린 에피소드(open episodes)를 가지고 있었고, 나는 알림 로직을 열린 에피소드당 한 번 실행되도록 작성해 두었습니다. 두 개의 에피소드, 두 개의 시계, 두 배의 성가심. 스케줄러(scheduler)는 정확한 시간에 작동했습니다. 항상 그렇듯이 말이죠. 몇 달 후, 다른 산업 분야에서 나는 내가 똑같은 버그를 다시 작성하기 시작하는 것을 발견했습니다. 그때 비로소 나는 그 버그가 무엇인지 마침내 깨달았습니다.
스케줄러는 무죄다
주기(cadence)가 잘못되면 모두가 가장 먼저 살펴보는 곳은 스케줄러입니다. 저 역시 그곳을 살펴보았습니다. cron 표현식을 읽고, 시간대(timezone)를 확인하고, 드리프트(drift)를 추적했습니다. 모든 것이 괜찮았습니다. cron은 거의 항상 괜찮기 때문입니다. 스케줄러는 내가 운영하는 어떤 시스템에서도 가장 많이 감사(audit)를 받는 100줄의 코드입니다. 버그는 내가 한 번도 찾아볼 생각을 하지 못한 곳에 있었습니다. 왜냐하면 그곳에서 내가 무언가를 결정하고 있다는 사실조차 인지하지 못했기 때문입니다. 나는 에피소드가 시간을 소유하도록 내버려 두었습니다.
해결책은 단 한 문장이었습니다: 열린 에피소드당 한 번이 아니라, 환자당 한 번 알림을 보내는 것. 환자는 에피소드를 경험하는 것이 아닙니다. 그녀는 저녁 식사 중에 휴대폰이 울리는 것을 경험합니다. 해결책을 입 밖으로 내뱉는 순간, 그것이 얼마나 당연한 것인지 들렸고, 바로 그 점 때문에 리뷰(review)를 통과해 살아남았던 것입니다. 그것은 결정처럼 보이지 않았습니다. 마치 배관(plumbing) 작업처럼 보였습니다.
무엇이 고장 났는가: 두 개의 열린 에피소드를 가진 환자가 두 배의 알림을 받았습니다. 내가 각 에피소드가 각자의 시계를 소유하도록 허용했기 때문입니다. 스케줄러는 무죄였습니다. 소유권(ownership)이 잘못되었습니다.
다른 산업에서의 동일한 버그
그다음은 인력 개발(workforce development) 분야였습니다. 나는 코치들이 의료 보조원 교육에 대해 후보자들에게 전화하는 아웃리치(outreach) 시스템을 구축했습니다. 산업도 다르고, 코드베이스(codebase)도 다르며, 두 시스템 사이에는 공유되는 코드가 단 한 줄도 없었습니다. 이 시스템에는 주기 게이트(cadence gate)가 있습니다: 코치가 동일한 후보자에게 다시 연락하기 전까지 얼마나 오래 기다려야 하는지를 결정하는 것입니다. 나의 첫 번째 초안은 모든 기록된 시도(logged attempt)마다 그 시계를 초기화하도록 설계되었습니다.
그것이 어떤 결과를 초래하는지 생각해 보십시오. 코치가 일주일 동안 다섯 개의 음성 메시지(voicemail)를 남깁니다. 메시지 하나하나가 시계를 초기화하므로, 시스템은 그 관계가 활발하다고 판단합니다. 실제로는 아무와도 대화하지 못한 사람에 대해 후속 조치(follow-up)를 권장하는 것을 중단해 버립니다. 다섯 개의 음성 메시지가 남겨지는 동안, 후보자는 그녀가 응답하지 않은 전화들로 인해 '신선함(fresh)' 상태로 표시된 채 대기열(queue) 속으로 조용히 가라앉습니다.
이번에는 제품이 출시되기 전에 이를 발견할 수 있었습니다. RTM 버그가 저에게 어떤 질문을 던져야 하는지 가르쳐 주었기 때문입니다. 해결책은 다시 한번 단 한 문장이었습니다. 게이트(gate)는 오직 하나의 결과, 즉 '연락됨 (reached)' 상태일 때만 초기화되어야 합니다. 시도(attempts)는 노트를 작성할 뿐, 시간을 작성하지는 않습니다.
음성 메시지는 관계가 아닙니다. 그것은 기록으로 남겨진, 당신이 당신 자신에게 말하는 것일 뿐입니다.
결과: 이제 다섯 개의 기록된 음성 메시지는 다섯 개의 노트를 작성하지만, 시계는 0초도 움직이지 않습니다. 하나의 동사가 게이트를 초기화합니다: '연락됨 (reached)'. 시스템은 노력을 접촉(contact)으로 착각하는 것을 멈췄습니다.
명사와 동사
의료 및 인력 개발 분야. 한쪽에는 Medicare 청구 규칙이 있고, 다른 한쪽에는 커리어 코칭이 있습니다. 공유된 코드는 전혀 없었지만, 버그는 동일했습니다. 두 번 모두 저는 스케줄러(scheduler)를 뒤지며 원인을 찾아 나섰고, 두 번 모두 스케줄러는 그저 자신의 할 일을 수행하는 메트로놈임이 드러났습니다. 결함은 제가 선택하고 있다는 사실조차 인지하지 못한 채 선택한 두 단어 속에 살고 있었습니다. 어떤 명사가 시계를 소유하는가, 그리고 어떤 동사가 시계를 초기화할 수 있는가 하는 점입니다.
RTM 엔진에서는 명사가 틀렸습니다. 환자가 소유해야 할 시계를 에피소드(episode)가 소유하고 있었습니다. 아웃리치(outreach) 시스템에서는 동사가 틀렸습니다. '시도함 (tried)'은 '연락됨 (reached)'일 때만 초기화되어야 함에도 시계를 초기화했을 것입니다. 이것이 분류 체계(taxonomy)의 전부입니다. 제가 어떤 산업에서든 출시했던 모든 케이던스(cadence) 버그는 이 두 단어 중 하나였습니다.
왜 두 버그 모두 버그처럼 보이지 않았을까요? 왜냐하면 두 결정 모두 결정처럼 보이지 않았기 때문입니다. 명사는 외래 키(foreign key) 속에 숨어 있습니다. 동사는 WHERE 절(WHERE clause) 속에 숨어 있습니다. 코드 리뷰는 잘못된 로직을 잡아내지만, 이것은 잘못된 로직이 아니었습니다. 그것은 잘못된 문법이었고, 그 문법은 컴파일(compile)되었습니다.
핵심 통찰: 모든 케이던스 버그는 스케줄러가 아니라 두 단어 속에 존재합니다. 어떤 명사가 시계를 소유하는가, 그리고 어떤 동사가 시계를 초기화할 수 있는가 하는 점입니다.
- 2개의 오픈 에피소드 (open episodes), 한 명의 환자, 두 배의 리마인더 (reminders)
- 시계를 단 0초도 움직이지 못한 5개의 음성 메시지 (voicemails)
- 아웃리치 게이트 (outreach gate)를 초기화할 수 있도록 허용된 1개의 동사 (verb)
- 두 시스템 사이에서 공유되는 0줄의 코드 (lines of code)
내가 지금 하는 일
시계가 포함된 무언가를 만들 때, 나는 코드를 작성하기 전에 그 두 단어를 먼저 적습니다. 명사 (noun)는 파일의 맨 위에 둡니다. 동사 (verb)는 그 옆에 둡니다. 그 외의 모든 것은 구현 (implementation)입니다.
- 시계를 소유하는 명사를 정하고, 인간이 경험하는 명사로 만드세요. 환자들은 전화기가 진동하는 것을 느낍니다. 그들은 에피소드 (episodes)를 느끼지 않습니다.
- 시계를 초기화할 수 있도록 허용된 모든 동사를 나열한 다음, 그 목록을 줄이세요. 대부분의 시계는 정확히 하나의 동사만을 가질 자격이 있습니다.
- 다른 모든 동사는 시간 대신 기록 (note)을 남기게 하세요. 이력 (history)은 저렴합니다. 초기화 (resets)는 비쌉니다.
- 복수형 (plurals)을 테스트하세요: 두 개의 에피소드, 다섯 번의 시도. 단수형 (singular) 케이스는 항상 통과하며, 그렇기 때문에 그것은 아무것도 증명하지 못합니다.
스케줄러 (scheduler)는 결코 흥미로운 부분이 아니었습니다. 흥미로운 부분은 당신이 쓰고 있다는 사실조차 인지하지 못한 채 작성한 그 문장입니다. 그러니 케이던스 (cadence)가 잘못되었을 때, 크론 표현식 (cron expression)을 먼저 읽지 마세요. 당신의 명사와 동사를 읽으세요. 그리고 만약 당신이 하나 이상의 산업군에서 시스템을 운영하며 서로 다른 옷을 입은 채 똑같은 작은 버그 (bug)를 계속 마주친다면, 그것을 기록하고 누군가에게 말하세요. 빌더 (builders)들은 서로를 찾아내야 합니다.
원문은 nabbilkhan.com에 게시되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기