본문 바로가기

AI 콘텐츠 자동화

텔레그램 봇으로 일일 보고 시스템을 만들며 겪은 API의 매운맛

텔레그램 봇으로 일일 보고 시스템을 만들며 겪은 API의 매운맛


텔레그램 봇 API를 활용해 매일 아침 자동으로 서비스 현황을 보고받는 시스템을 구축하려 했다. 그런데 메시지가 오지 않거나, 5번씩 중복으로 쏟아지거나, 텍스트가 외계어로 깨지는 문제가 연속으로 터졌다. 텔레그램 API의 3가지 숨겨진 함정을 직접 뚫어본 기록이다.

이러한 문제들을 해결하기 위해 처음 작성했던 파이썬 코드의 일부를 살펴보면 그 원인을 짐작할 수 있다.

위 화면은 텔레그램 봇이 서비스 일일 현황 데이터를 전송하는 코드 작업 화면이다. 이 코드는 완성 직후 서버에 올렸을 때 메시지가 오지 않거나, 반대로 동일한 메시지가 연달아 5번 쏟아지는 예상치 못한 문제를 일으켰다. 보기엔 단순한 API 연동 코드지만, 메시지 길이 제한·Rate Limit·마크다운 파싱 오류라는 3중 함정이 숨어있었다.

수동 모니터링이 가져다준 아침의 피로

서비스를 운영하는 대표의 아침은 늘 데이터와 함께 시작된다. 전날의 신규 가입자 수, 결제 건수, 그리고 밤사이 서버가 뱉어낸 에러 로그까지. 처음에는 날것의 데이터를 확인하는 것 자체가 즐거운 일과였다. 하지만 매일 아침 눈을 뜨자마자 노트북을 켜고, 관리자 페이지를 띄우거나 서버에 SSH로 접속해 직접 SQL 쿼리를 작성하는 일은 점차 무거운 피로감으로 다가왔다.

그러던 어느 날, 바쁜 일정 탓에 아침 모니터링을 건너뛴 적이 있었다. 하필 그날 오전에 결제 시스템에 작은 문제가 발생했고, 나는 한참이 지나서야 이를 뒤늦게 파악했다. 매번 사람이 직접 시스템을 파헤쳐야만 현황을 알 수 있는 '수동적인 모니터링'은 더 이상 지속 가능하지 않다는 것을 뼈저리게 깨달은 순간이었다.

알림 하나로 우아하게 시작하는 아침을 꿈꾸며

해결책으로 떠올린 것은 '텔레그램 봇을 활용한 일일 보고 시스템'이었다. 슬랙이나 이메일도 후보에 올랐지만, 언제 어디서나 스마트폰으로 가볍고 직관적으로 확인할 수 있는 매체로는 텔레그램이 제격이었다.

머릿속에 그린 그림은 꽤 단순하고 우아했다. 매일 아침 8시 30분이 되면, 텔레그램 봇이 내 폰으로 깔끔하게 정리된 어제자 요약 리포트를 보내주는 것이다. 파이썬으로 DB의 핵심 데이터를 조회하는 스크립트를 작성하고, 이를 텔레그램 API에 연결해 서버의 크론탭(crontab)에 등록하면 끝날 일이었다. 코드 몇 십 줄이면 충분해 보였고, 반나절이면 이 우아한 시스템이 완성될 것이라 자신했다.

아래는 에러가 발생한 후 원인을 분석하는 과정을 담은 화면이다. 서버 설정과 타임존 문제로 헛다리를 짚던 시간이 담겨 있다.

파이선생 분석
파이선생 분석

이 분석 과정이 의미 있는 이유는, 처음에 서버 타임존(KST/UTC) 불일치나 크론탭 중복 실행이라는 엉뚱한 원인을 의심하며 시간을 낭비했기 때문이다. 진짜 문제는 서버 인프라가 아니라 텔레그램 API 자체의 3가지 규칙(메시지 4096자 제한, Rate Limit, MarkdownV2 엄격 파싱)을 코드에 전혀 반영하지 않은 것이었다.

침묵, 폭주, 그리고 박살 난 외계어

하지만 호기롭게 스크립트를 서버에 올린 다음 날 아침, 내 스마트폰은 불길할 정도로 고요했다. 서버 로그를 뒤져보니 스크립트는 실행되었으나, 텔레그램으로 데이터를 전송하는 과정에서 에러를 뿜어내며 장렬하게 전사해 있었다.

부랴부랴 코드를 수정하고 다시 테스트를 돌렸다. 이번에는 메시지가 오긴 했는데, 똑같은 리포트가 1초 간격으로 5번이나 연달아 쏟아지며 스마트폰이 폭주했다. 설상가상으로 가장 중요한 에러 로그 데이터는 텍스트가 중간에 툭 끊겨 있었고, 폰트와 굵기를 지정하려던 마크다운(Markdown) 서식은 모조리 깨져 알아볼 수 없는 외계어가 되어 있었다. 편해지려고 만든 봇이 아침부터 사람의 진을 쏙 빼놓고 있었다.

엉뚱한 곳에서 헤맸던 원인 찾기

처음에는 서버 인프라의 설정 문제라고 짚었다. 메시지가 안 오거나 여러 번 겹쳐 오는 현상을 보며 서버의 타임존(KST와 UTC)이 꼬였거나, 크론탭 프로세스가 중복 실행되고 있다고 의심한 것이다. 그래서 리눅스 시간 설정을 다시 맞추고 백그라운드의 파이썬 프로세스들을 강제 종료한 뒤 세팅을 처음부터 다시 했다. 마크다운 서식이 깨진 것도 단순히 내가 괄호를 빼먹은 오타 문제일 것이라 가볍게 넘겼다.

하지만 인프라를 아무리 깔끔하게 정비해도 봇은 여전히 제멋대로 작동했다.

숨어있던 진짜 원인 3가지

문제를 깊게 파고들자, 진짜 원인은 서버가 아니라 '외부 API의 특성을 전혀 고려하지 않은 내 코드'에 있었다.

1. 4096자의 벽 (메시지 길이 제한) 텔레그램 API는 한 번에 보낼 수 있는 메시지의 최대 길이를 4096자로 엄격하게 제한한다. 그런데 나는 전날 발생한 에러 로그 전체를 하나의 변수에 욱여넣어 전송하려 했다. 로그가 짧은 날은 요행히 전송됐지만, 로그가 4096자를 넘기는 날에는 어김없이 API가 Bad Request를 뱉어내며 시스템이 멈춰버린 것이다.

2. 무자비한 재시도가 부른 'Too Many Requests (HTTP 429)' 전송 실패에 대비해 나름의 재시도(Retry) 로직을 넣어둔 게 화근이었다. 텔레그램 서버가 요청을 거부할 때마다 내 코드는 아무런 딜레이 없이 즉각 재요청을 때려버렸다. 결국 API 호출 제한(Rate Limit)에 걸렸고, 요청 타이밍이 엉키면서 한 번에 메시지가 중복으로 쏟아지는 폭주가 발생했다.

3. 깐깐하기 그지없는 MarkdownV2 파싱 에러 텔레그램의 MarkdownV2는 일반적인 마크다운보다 훨씬 엄격한 문법을 요구한다. 데이터 원문에 포함된 이메일 주소나 이름의 언더바(_), 대시(-), 마침표(.) 같은 특수문자들이 이스케이프(Escape, \\ 처리)되지 않으면, 문법 오류로 간주해 메시지 전송 자체를 막아버린다. DB의 날것 그대로를 마크다운 텍스트에 끼워 넣은 나의 안일함이 문제였다.

쪼개고, 달래고, 우회하는 코드 대수술

원인을 명확히 알았으니 코드를 전면적으로 수정할 차례였다.

가장 먼저 텍스트 청킹(Chunking) 로직을 구현했다. 전송할 텍스트의 길이를 측정하고, 4000자가 넘어갈 경우 문맥이 끊기지 않도록 줄 바꿈 기준으로 문자열을 여러 개의 배열로 쪼갰다. 그리고 반복문을 통해 이를 순차적으로 보내게 만들었다.

다음으로는 API를 달래주는 예외 처리를 추가했다. 429 에러가 발생하면, 응답 헤더에 담긴 Retry-After(N초 뒤에 다시 시도하라) 값을 읽어와 그 시간만큼 time.sleep()으로 대기하도록 안전장치를 걸었다. 또한 성공적으로 전송된 메시지 ID를 임시 기록해 중복 전송을 차단하는 방어 로직도 더했다.

마지막으로 가장 골치였던 마크다운 문제는 정규표현식을 활용한 커스텀 함수로 해결했다. 텔레그램으로 텍스트를 쏘기 직전, 데이터 원문 내의 예약어 특수문자들을 찾아내 일괄적으로 역슬래시(\\)를 붙여주는 escape_markdown() 함수를 거치도록 구조를 개선했다.

방어적 프로그래밍, 외부 API를 대하는 자세

이 일련의 과정을 통해 외부 API 연동이 단순히 '문서를 보고 데이터를 쏘는' 1차원적 작업이 아님을 뼈저리게 배웠다.

내가 통제할 수 없는 외부 시스템과 통신할 때는 네트워크 지연, 데이터 규격 초과, 호출 제한 등 무수히 많은 변수가 발생한다. 코드가 정상적으로 돌아가는지 확인하는 것만큼이나, 비정상적인 상황(에러)을 어떻게 우회하고 시스템의 붕괴를 막을 것인지 고민하는 '방어적 프로그래밍(Defensive Programming)'이 필요했다. 남이 만든 API를 사용할 때는 무작정 데이터를 던지는 것이 아니라, 그들의 규칙에 맞게 데이터를 정제하고 예의를 갖춰 요청해야 한다는 귀중한 교훈을 얻었다.

일방향 보고를 넘어, 양방향 소통의 가상 비서로

우여곡절 끝에 완성된 텔레그램 봇은 이제 매일 아침 8시 30분이면 깔끔하게 정제된 일일 보고서를 안정적으로 배달해 준다. 아침마다 DB를 뒤적이며 소모하던 에너지가 사라지니 하루의 시작이 훨씬 가볍고 산뜻해졌다.

이번 성공을 발판 삼아 나는 다음 단계를 기획하고 있다. 지금은 봇이 정해진 시간에 일방적으로 데이터를 쏟아내는 형태지만, 조만간 텔레그램의 '인라인 키보드(Inline Keyboard)' 기능을 도입할 예정이다. 리포트 하단에 버튼을 달아, 내가 "상세 에러 로그 보기"나 "어제자 가입자 목록"을 누르면 실시간으로 데이터를 추가 조회해 답변하는 '양방향 소통 봇'으로 업그레이드하는 것이 목표다.

점진적인 코드 개선을 통해, 이 작은 봇을 나만의 든든한 가상 비서로 키워낼 생각이다.