정지시킨 수강 등록이 다시 살아나 있다는 보고가 왔고 한두 건이 아니라 열일곱 건이었다. Moodle 어딘가에서 되살리고 있다는 뜻인데 그 자리가 한 곳이라는 보장이 없었다.
Table of contents
Open Table of contents
증상 — 다시 살아난 열일곱 건
한 곳을 찾아서 막는 방식으로 가면 나머지 경로가 남아 있을 때 같은 일이 다시 난다. 그래서 막기 전에 경로가 몇 개인지를 먼저 확정하기로 했다.
열일곱 건 각각이 어떤 처리로 살아났는지가 mdl_logstore_standard_log 에 남아 있었다.
SELECT userid, eventname, origin, timecreated
FROM mdl_logstore_standard_log
WHERE userid IN (...) AND component LIKE 'enrol%'
ORDER BY userid, timecreated;
user_enrolment_updated 로 정지된 다음에 다시 user_enrolment_updated 가 들어와 살아나는 패턴이 보였고 그 이력을 어느 컬럼으로 GROUP BY 하느냐가 경로 수를 세는 방법이 된다.
이벤트 컬럼으로 본 경로
eventname 과 origin 으로 GROUP BY 해 COUNT(*) 를 내 보니 세 종류였다.
origin = web, enrol_manual 관리자가 화면에서 상태를 직접 바꿈
origin = web, enrol_database 사용자가 로그인할 때 외부 인사 DB 를 보고 되살림
origin = cli, enrol_database 정기 배치 동기화
Moodle 3.6 의 enrol/database/lib.php 를 열어 보니 뒤의 둘은 함수부터 다르다. sync_user_enrolments($user) 가 로그인 시점에 그 사용자만 맞추고 sync_enrolments() 가 cli/sync.php 크론에서 전체를 맞춘다. 세 종류는 서로 다른 화면과 다른 배치에서 오는 것이었고 eventname 과 origin 두 컬럼으로 GROUP BY 하는 것만으로 세 경로가 한눈에 드러난 셈이었다.
하나를 막아도 둘이 남는다
건수가 가장 많은 경로만 막으면 그 순간에는 줄어든 것처럼 보인다. 그러나 나머지 둘이 남아 있으므로 며칠 뒤에 다시 같은 보고가 온다.
이런 상황에서 부분적으로 막는 것은 문제를 없애는 것이 아니라 발생 속도를 늦추는 것이다. 늦어진 만큼 원인이 해결된 것으로 오해되기 쉬워서 오히려 나쁠 수도 있었다.
상위 상태라는 공통 지점
세 경로의 코드를 나란히 놓고 보니 셋 다 외부 인사 DB 의 재직 뷰를 보고 진행 여부를 정하고 있었다. 거기서 재직으로 남아 있으면 등록을 되살리는 것이 각 경로의 공통 동작이었다.
그러면 막을 자리는 세 곳이 아니라 그 상위 상태 하나가 된다. 세 경로를 각각 막는 것 대신 그 하나를 바꾸는 쪽을 골랐다. 이유는 경로가 하나 더 생겨도 같은 뷰를 볼 것이라서다. 영구 정지를 할 때 mdl_user_enrolments.status 만 UPDATE 하지 않고 인사 DB 의 수강 대상 뷰에서도 함께 빼도록 바꿨다.
실재하는 불일치와 점검 쿼리
바꾸면서 인사 뷰와 mdl_user_enrolments 를 JOIN 해 이미 어긋나 있는 건을 COUNT(*) 로 세어 봤는데 실제로 상당수가 있었다. 이 불일치는 이론적인 가능성이 아니라 지금 자료에 들어 있는 상태였다.
계속 생길 수 있는 것이므로 어긋난 건을 뽑는 점검 SELECT 를 만들어 cron 으로 주기적으로 돌리게 했다. 경로를 다 세고 나서 막았기 때문에 이 점검이 무엇을 잡는지도 분명했다.
정리
- 정지된 등록이 되살아나는 경로가 셋이었다
- 하나를 막아도 나머지로 살아난다
- 부분적으로 막으면 발생 속도만 늦춘다
- 로그 표의
eventname·origin으로GROUP BY하면 경로가 한눈에 나온다 - 여러 경로가 상위 상태를 보고 있으면 거기가 공통 지점이다
- 영구 정지는 상위까지 함께 바꾼다
- 상위와 하위의 상태 불일치가 실재한다
- 경로를 다 세고 나서 막는다