Loading the catalog…
Loading the catalog…
경영진이 매일 보는 대시보드를 만들었다. 카드마다 숫자가 큼직하게 박혀 있고, 그 숫자를 보고 사람이 움직인다. "반 배정 대기 227명"이면 누군가는 오늘 227명을 배정해야 한다. 그런데 그 227명을 실제로 배정하려던 담당자가 물었다. "이 사람들 명단은 어디서 봐요?" 명단을 뽑아봤다. 조건에 맞는 사람은 0명 이었다. 이 글은 그 뒤로 카드를 하나씩 운영 DB에서 직접 세어보며 찾은 것들에 대한 기록이다. 결론부터 말하면, 틀린 이유가 카드마다 전부 달랐다. 1. 전칭을 MAX 로 쓰면 조건이 통째로 죽는다 「반 배정 대기」의 정의는 이랬다. 정식 계정이고, 수강권 결제가 끝났고, 환불되지 않았고, 신청한 학기가 아직 진행 중이고, 반 배정만 안 된 사람 이 다섯 개를 모두 만족하는 사람은 운영 DB에 0명이었다. 그럼 227명은 어디서 왔나. 분류 로직은 이렇게 생겼다. 자녀 한 명을 여러 유형 중 하나로 보내고, 어디에도 안 걸리면 맨 끝 catch-all 로 떨어뜨린다. 그 catch-all 이 하필 「반 배정 대기」였다. 그리고 그 앞에 있어야 할 제외 규칙이 운영에서 한 번도 발동하지 않고 있었다. // 의도: "이 자녀 말고 다른 정상 자녀가 있는 경우는 제외 대상에서 빼자" // 부모 단위로 모아 집계한다 MAX(CASE WHEN child.isNormal THEN 1 ELSE 0 END) AS hasOtherNormalChild // ... if (signals.hasOtherNormalChild !== 1) { exclude(child); // ← 운영에서 단 한 번도 실행되지 않았다 } 문제는 MAX 가 판정 대상 자녀 본인까지 포함해서 집계한다는 것이다. "다른(other)"이라는 단어가 변수 이름에만 있고 쿼리에는 없었다. 대상 자녀가 정식 계정이면 본인 때문에 이 값이 항상 1 이 된다. 가드는 !== 1 일 때만 제외하므로, 제외는 영원히 일어나지 않는다. 그렇게 걸러졌어야 할 사람들이 전부 catch-all 로 흘러들어 227명이 됐다. 재밌는 건, 일부 계정은 우연히 살아남았다 는 것이다. 계정 번호 접두사가 다른 유형들은 isNormal 이 0 이라 MAX 도 0 이 됐고, 그래서 가드를 통과해 제외됐다. 같은 버그가 데이터 모양에 따라 어떤 줄은 통과시키고 어떤 줄은 안 통과시킨 것이다. "일부는 맞게 나온다"가 로직이 맞다는 증거가 되지 않는다. 고친 방법 제외 신호 세 개를 전부 MIN 집계로 바꿨다. // "자녀 전원이 그 유형일 때만 제외한다" — 전칭을 전칭으로 표현 MIN(CASE WHEN child.isMigratedInactive THEN 1 ELSE 0 END) AS allMigratedInactive 그리고 의미가 없어진 hasOtherNormalChild 가드를 지웠다. catch-all 도 옮겼다. 나머지를 받아내는 칸은 하나여야 하고, 「반 배정 대기」가 그 역할을 겸하면 항목의 뜻 자체가 흐려진다. 그래서 catch-all 은 「미등록」으로 보냈다. 운영 DB 실측으로 227명을 분해해보니 이렇게 나뉘었다. 실제로는 인원 원래 가야 할 곳 학기 종료·미배정 88명 전역 집계에서 제외 이관·무활동 69명 전역 집계에서 제외 신청만 하고 결제 미완료 69명 임시 계정 분류 나머지 1명 미등록 반 배정 대기 0명 — 교훈 — 전칭(∀, 모두)과 존재(∃, 하나라도)를 SQL 집계로 옮길 때 MIN/MAX 를 거꾸로 쓰면 조건이 소리 없이 죽는다. 에러도 안 나고 로그도 안 남는다. 그냥 평생 참이거나 평생 거짓일 뿐이다. 2. 주문 상태와 구독 상태는 다른 것이다 「자동결제 실패」 카드는 운영에서 30건을 보여주고 있었다. 담당자가 30명에게 연락해야 한다는 뜻이다. 처음 고칠 때는 이렇게 생각했다. *"이미 닫힌 주문은 빼면 되겠지."* 그래서 주문 상태가 취소·실패인 건을 뺐다. 그래도 숫자가 이상해서 구독 테이블을 직접 조인해 세어봤다. 구독 상태 건수 조치 대상인가 진행 중 6 ✅ 일시 중지 3 ✅ 운영자가 연락해야 풀림 해지 19 ❌ 총 회차 완료 1 ❌ 구독 없음 1 ❌ 구독이 해지돼도 주문 상태는 결제 완료로 남아 있었다. 30건 중 21건이 이미 끝난 구독이었던 것이다. 주문은 "그때 결제가 어떻게 됐나"의 기록이고, 구독은 "지금 살아 있나"의 상태다. 둘이 같은 질문에 답한다고 가정한 게 틀렸다. 여기서 하나 더 배운 게 있다. 일시 중지(3건)를 포함할지 말지 가 애매했는데, 코드를 따라가 보니 구독이 일시 중지되는 경로는 하나뿐이었다. 학부모가 자동결제 카드를 삭제했는데 대체 카드가 없을 때. 스케줄러는 진행 중인 구독만 결제하므로 그 상태로는 저절로 풀리지 않는다. 운영자가 연락해야 하는 건이라 카드에 포함했다. 그리고 이 판단을 도메인 문서에 「살아 있는 구독 = 진행 중 + 일시 중지」라고 정의로 적어두고 , 같은 판정을 쓰는 다른 코드들과 공용 상수로 묶었다. 다음 사람이 같은 고민을 반복하지 않게. 3. 시안의 글자가 그대로 배포돼 있었다 세 번째 카드의 부제는 「본원 결재 대기 3건」이었다. <CardSubtitle>본원 결재 대기 3건</CardSubtitle> 하드코딩된 문자열이었다. 실제 건수와 무관하게 영원히 3건 이었다. 변명의 여지가 있긴 하다. 퍼블리싱 단계에서 서버에 해당 값이 없었고, 그때 PR 에 "서버 값이 없어 시안 그대로 둔다"고 적어두긴 했다. 그 메모는 PR 에만 남았고, 화면에는 아무 표시도 없었다. 그리고 몇 주가 지났다. 지금은 집계를 붙여 실제 값을 넣었다. 하지만 진짜 교훈은 다른 데 있다. "나중에 연결할 값"을 진짜처럼 생기게 두면 안 된다. 세 가지 중 하나를 했어야 했다. 값을 — 로 두고 "연동 전"을 화면에 표시한다 그 영역을 아예 렌더링하지 않는다 최소한 // TODO: 가 아니라 실패하는 테스트 를 남긴다 PR 본문의 메모는 그 PR 을 읽는 사람에게만 보인다. 몇 주 뒤 그 화면을 보는 사람에게는 안 보인다. 네 번째는 안 고쳤다 카드가 하나 더 있었다. 재택 강사 미답변 건수. 이것도 당연히 틀렸을 거라 생각하고 운영 DB 에서 세어봤는데 — 화면 값과 정확히 일치했다. 그래서 안 건드렸다. 이것도 기록해둘 만하다고 생각한다. 세 개가 틀렸다고 네 번째도 틀렸을 거라 단정하고 "개선"했으면, 맞던 걸 망가뜨렸을 것이다. 남은 세 가지 1 지표는 출처가 전부다. 카드에 적힌 숫자가 아니라 그 숫자가 어느 테이블의 어느 조건에서 나왔는지 가 지표의 정의다. 「미답변」, 「대기」, 「실패」 같은 단어는 사람마다 다르게 읽히고, 코드는 그중 하나만 구현한다. 어느 하나인지를 문서에 적지 않으면 아무도 모른다. 2 화면을 믿지 말고 DB 에서 세어봐라. 이번에 고친 것 중 코드만 읽어서 찾은 건 하나도 없다. 전부 운영 DB 에서 직접 세어보다가 "어? 이 숫자 왜 이러지" 하고 발견했다. 집계 코드는 조용히 틀린다. 3 숫자가 사실인지는 그 숫자로 일하는 사람이 가장 먼저 안다. 이 모든 건 "명단 어디서 봐요?"라는 질문에서 시작됐다. 대시보드를 만들었으면, 그 숫자로 실제로 일을 해보거나 일하는 사람 옆에 앉아 있어야 한다. 집계 카드 체크리스트 다음에 집계 화면을 만들 때 쓰려고 정리해뒀다. 이 숫자의 명단 을 뽑을 수 있나? 못 뽑으면 그 숫자는 검증된 적이 없는 것이다 전칭/존재 조건을 집계 함수로 옮길 때 MIN/MAX 가 맞나? 판정 대상 본인이 집계에 섞이지 않나? 상태를 세고 있나, 지금 살아 있는 것 을 세고 있나? 둘은 다른 테이블일 수 있다 화면에 "아직 연동 안 됨"을 표시할 자리가 있나? 없으면 가짜 값이 진짜로 보인다 세부 항목의 합이 큰 숫자와 항상 같은가? 이 불변조건을 테스트로 고정했나? 안 틀린 카드도 확인은 했나? 다음 글에서는 이 작업을 하다가 만난 더 불편한 문제를 쓰려고 한다. 집계가 틀린 게 아니라, 데이터 자체가 틀어져 있던 계정 334개를 되살리면서 내가 쓴 분석을 스스로 뒤집은 이야기다.
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
227명은 사실 0명이었다 — 운영 대시보드의 숫자를 DB에서 직접 세어본 날. 경영진이 매일 보는 대시보드를 만들었다. 카드마다 숫자가 큼직하게 박혀 있고, 그 숫자를 보고 사람이 움직인다. "반 배정 대기 227명"이면 누군가는 오늘 227명을 배정해야 한다. 그런데 그 227명을 실제로 배정하려던 담당자가 물었다. "이 사람들 명단은 어디서 봐요?" 명단을 뽑아봤다. 조건에 맞는 사람은 0명 이었다. 이 글은 그 뒤로 카드를 하나씩 운영 DB에서 직접 세어보며 찾은 것들에 대한 기록이다. 결론부터 말하면, 틀린 이유가 카드마다 전부 달랐다. 1. 전칭을 MAX 로 쓰면 조건이 통째로 죽는다 「반 배정 대기」의 정의는 이랬다. 정식 계정이고, 수강권 결제가 끝났고, 환불되지 않았고, 신청한 학기가 아직…
Open source