운영 알림을 종류별로 다른 텔레그램 방에 보내는 봇에서 방이 바뀌어 설정을 고쳐야 했다. 라우팅이 어디서 정해지는지 찾다가 DEFAULT_SUBSCRIPTIONS 상수 배열에 닿았다.
Table of contents
Open Table of contents
원인 — 상수에 들어 있던 라우팅
alert_subscriptions 테이블이 있고 거기서 라우팅이 결정되는 구조였다. 그런데 그 테이블을 채우는 초기 데이터 상수가 코드에 따로 있었다.
종류별 chat_id 가 그 배열에 그대로 적혀 있었으므로 사실상 그것이 라우팅 정의였다. 테이블은 그 배열의 사본에 가까웠다.
증상 — 조건부 삽입이 증상을 감춘다
삽입이 INSERT IGNORE 라서 이미 행이 있으면 아무것도 하지 않는다. 운영 중인 서버는 MariaDB 가 이미 있으니 상수가 낡아도 아무 일이 일어나지 않는다.
반대로 데이터베이스를 새로 만드는 환경에서는 그 상수가 라우팅을 결정한다. 낡은 채로 오래 있다가 재생성 시점에 죽은 chat_id 로 조용히 되돌아가는 구조다.
식별자가 바뀌는 두 경로
chat_id 가 왜 바뀌는지 보니 경로가 둘이었다. 방 자체를 교체한 경우는 명확하지만 승격에 의한 무효화는 예상 밖이었다.
Telegram Bot API 공식 문서대로 그룹이 슈퍼그룹으로 승격되면 옛 chat_id 가 무효가 되고 실패 응답의 migrate_to_chat_id 에 새 값이 담겨 온다. 우리가 아무것도 안 해도 값이 바뀌는데 그 응답 본문을 안 보면 sendMessage 실패로만 보인다.
판단 기준 — 옛 값을 남기지 않은 이유
상수를 고치면서 두 가지 방법이 있었다. 옛 chat_id 를 주석이나 배열 끝에 남겨 두는 것과, 현재 유효한 것만 두는 것이다. 뒤쪽을 골랐다. 이유는 옛 값을 배열에 남겨 두면 INSERT IGNORE 때문에 새 데이터베이스에 아무 데도 가지 않는 구독 행이 하나 더 생겨서다. 대신 옛 값을 알고 싶을 때 git log 를 뒤져야 하는 것을 감수했다.
이력이 필요하면 버전 관리에 있으므로 상수에는 현재 유효한 것만 둔다.
조치 — 사고 이력이 아니라 규칙으로
원래 그 상수 위에는 날짜와 함께 무슨 일이 있었는지가 적혀 있었다. 읽으면 사정은 알겠는데 지금 무엇을 해야 하는지는 알려 주지 않는 주석이었다.
방이 바뀌면 교체든 승격이든 이 목록을 현재 값으로 갈아 끼우고 옛 값은 남기지 않는다는 문장으로 바꿨다. 주석 이력을 보니 이 상수는 여러 번 갱신 대상이 됐으므로 일회성 경위가 아니라 반복 적용될 규칙이 필요했다.
정리
- 초기 데이터 상수가 사실상 설정 정의가 되는 경우가 있다
INSERT IGNORE면 상수가 낡아도 증상이 안 보인다- 재생성 시점에 죽은 값으로 조용히 되돌아간다
chat_id는 우리가 안 바꿔도 슈퍼그룹 승격으로 바뀔 수 있다- 실패 응답 본문의
migrate_to_chat_id에 새 값이 담겨 온다 - 조건부 삽입 목록에 옛 값을 남기면 죽은 행이 쌓인다
- 이력은 버전 관리에 있으므로 상수에는 유효한 것만 둔다
- 주석은 사고 이력이 아니라 다음 사람이 실행할 규칙으로 쓴다