개발 도중 이러한 의문이 생겼다
" 아... 텔레그램 봇으로 메시지 3000개정도 보내는데 10분이 넘게 걸리네... 파이썬으로 짜긴 했는데...어떻게 하면 시간을 줄일 수 있을까??"
언어를 다르게 해서 500개를 보내고 시간을 한번 재보자!
5000개로 테스트하고 싶은데...5000개는 봇이 정지를 먹을 것 같다..
import telegram
import time
bot = telegram.Bot(token="이것은 나의 비밀 키")
start = time.time()
for i in range(0, 500):
bot.send_message(chat_id="나의 아이디", text="testing in python")
end = time.time() - start
print(end)
#261.1625602245331, 약 4분 21초정도
package main
import (
"log"
"time"
"github.com/go-telegram-bot-api/telegram-bot-api"
)
func main() {
bot, err := tgbotapi.NewBotAPI("이것은 나의 비밀 키")
if err != nil {
log.Panic(err)
}
start := time.Now()
text := "testing in go"
msg := tgbotapi.NewMessage("나의 아이디", text)
for i:=0;i<500;i++{
bot.Send(msg)
}
end := time.Since(start)
log.Printf("It took %s", end)
}
// It took 4m24.135826424s
???? 분명히 Go가 파이썬보다는 더 빠를텐데 속도가 비슷하게 나온다. cpu바운드한 로직을 실행했을 때는 Go가 더 빠른 것을 확인할 수 있었고, 네트워크 시간을 찍어본 이후 네트워크가 병목임을 확인할 수 있다
아 이거 멀티쓰레드나 멀티프로세스를 적용하면 빨라지겠다!
import telegram
import time
import threading
def send(val):
bot.send_message(chat_id="이것은 나의 아이디", text=str(val), timeout=30)
bot = telegram.Bot(token="이것은 나의 비밀 키")
start = time.time()
for i in range(0, 500):
tmp = threading.Thread(target=send, args=(i,))
tmp.start()
end = time.time() - start
print(end)
#telegram.error.RetryAfter: Flood control exceeded. Retry in 4.0 seconds
너무 빨라서 도배방지에 걸렸다. 속도가 매우매우매우 빨랐다(이후에 이것은 단순히 속도가 빨라서 그런 것은 아님을 발견하게 됐지만, 이때는 속도가 문제라고 생각했다. 90개로 테스트 했을 때에는 메시지를 다 보내는데 10초가 안걸렸다. io bound가 매우 큰 프로그램이라서 도배방지 룰만 아니였다면 고민없이 이 방법을 택했을 것이다
이 또한 비슷한 이유로 속도는 빨랐지만 도배방지에 걸렸다.
import telegram
import time
from multiprocessing import Pool
def send(val):
bot.send_message(chat_id="이것은 나의 아이디", text="testing in python", timeout=30)
bot = telegram.Bot(token="이것은 나의 비밀 키")
start = time.time()
pool = Pool(4)
pool.map(send, range(0, 500))
end = time.time() - start
print(end)
음...한번에 보내는 속도자체는 빨라진것 같은데...100의 단위가 될 때 마다 프로그램이 잠깐씩 실행을 멈추더니 Timeout이 뜨길래 send_message에 timeout을 30초로 일단 주고 돌렸더니 flood error라고 도배하지 말라는 메시지가 왔다.
공식 깃헙 위키의 도배 판단의 기준에 따르면 1초에 30개 이상의 메시지 & 1분에 1개 그룹에 20개 이상의 메시지를 보낼 경우에 해당 제제를 받을 수 있다고 나와있다. 100개를 보내는데 24초정도가 걸림에도 도배방지에 걸리는 것이 의아했지만, 우선은 풀의 크기를 줄여서 다시 시도해보기로 했다
import telegram
import time
from multiprocessing import Pool
def send(val):
bot.send_message(chat_id="이것은 나의 아이디", text="testing in python", timeout=30)
bot = telegram.Bot(token="이것은 나의 비밀 키")
start = time.time()
pool = Pool(2)
pool.map(send, range(0, 500))
end = time.time() - start
print(end)
#167.7538857460022, 약 2분 47초정도
병렬 정도를 낮춘 뒤 처음으로 성공을 할 수 있었다! 여전히 느리지만 나름의 성과는 있었다. 좋아해야 하는건가...?
어디까지 봐주려나?
한번 허용범위의 상한에 도전해보기로 했다. 다시 시도해도 프로세스 2개까지는 무난하게 통과했다.
import telegram
import time
from multiprocessing import Pool
def send(val):
bot.send_message(chat_id="이것은 나의 아이디", text="testing in python", timeout=30)
bot = telegram.Bot(token="이것은 나의 비밀 키")
start = time.time()
pool = Pool(3)
pool.map(send, range(0, 500))
end = time.time() - start
print(end)
#telegram.error.RetryAfter: Flood control exceeded. Retry in 3.0 seconds
3개부터는 다시 도배방지에 걸렸다
그렇다면 프로세스를 2개로 줄이고 메시지를 5000개로 늘렸을 때 flooding error가 뜨지 않으면 현재 서비스에 적용해도 될 것 같다고 판단했다.
과연...?
import telegram
import time
from multiprocessing import Pool
def send(val):
bot.send_message(chat_id="이것은 나의 아이디", text="testing in python", timeout=30)
bot = telegram.Bot(token="이것은 나의 비밀 키")
start = time.time()
pool = Pool(2)
pool.map(send, range(0, 5000))
end = time.time() - start
print(end)
#656.8754236698151 약 10분 56초
에러가 나지 않고, 실행이 됐다.
- 텔레그램 봇은 멀티프로세싱을 2개까지는 안정적으로 지원한다.
- 텔레그램 봇은 참 사용하기 쉽고 편리하지만, 사용자가 커졌을 때는 메시지를 보내는 속도에 한계가 있다. 정확하게는 한계를 공식 독스에서 지정하고 있다.
2배정도는 빨라졌으니 나름의 수확이 있지만... 언젠가는 메시징 서비스를 다른 플랫폼으로 이전하거나 자체 앱으로 이전해야 할 것이라는 생각이 든다.
언어를 바꾸면서 성능 측정을 다시 하게 됐다
21년 4월에 프로그램 언어를 자바로 바꾸게 되면서 성능 측정을 다시 하게 됐다
@Test
void send() {
long chatId = Auth.Telegram.admin;
List<Message> messages = new ArrayList<>();
for (int count = 1; count <= 500; count++) {
messages.add(new Message(String.valueOf(count)));
}
Bot bot = new Bot(Auth.Telegram.token);
bot.send(messages, chatId);
}
//시간 측정 로직, 메시징 로직을 외부에 두었다.
//it took 4 min 2sec
기본 속도가 조금 차이가 생겼지만, 노트북도 좋아졌고 네트워크 환경도 좋아졌기 때문에 자바 자체의 성능이 좋아서 이러한 결과가 나왔다고는 말할 수 없다
parallelstream을 사용하고, 시스템의 설정을 제한해서 스레드를 2개로 제한하고 실험하였다.
// it took 2m 26s
3개부터는 여전히 flooding error가 떴다. 즉, 현재 시점에서의 안정적 상한은 1초에 약 3.5개의 메시지를 보내는 것이라고 이해하고 넘어가려고 한다. 제제 기준이 1초에 5개라는 말도 있지만 속도를 4.5/s 정도로 두는 것은 네트워크 상황에 따라 제한을 초과할 여지가 있기 때문에 적용하지 않기로 한다.
10분이 걸리던 작업을 약 3분정도로 단축할 수 있게 됐다.
실제 환경에서는 테스트 환경과 달라 비동기io를 사용하지 않는 이상 도배 방지의 기준이 훨씬 느슨했다
메시지를 10000개로 늘려서 테스트하니 쓰레드를 1개로 돌리건, 2개로 돌리건 429에러(too many sends)가 떴다...! 속도가 초과할수가 없는데 왜 도배방지에 걸리는 것일까 하고 공식 문서를 뒤지던 중 원인을 찾을 수 있었다.
실험 상황이 문제였던 것인데, 실제 운영 환경에서는 여러 명에게 메시지를 전달해서 이 문제를 마주하지 않았을 수 있었겠지만 관리자 계정을 통해 테스트하는 환경에서는 일정 수준(약 3000개 정도)이 넘어가는 메시지를 그냥 보내면 에러가 뜨는 것이었다
FAQ에 따르면, 1명에게 보내려면 1초에 1개, 여러명에게 보내려면 1초에 30명을 넘기는 행위가 일정 기간 이상 지속될 경우 429 에러가 뜰 것이라고 한다. 스테이징 환경을 구축해서 여러 사용자들에게 메시지를 실제로 보내면서 테스트한 이후 안정성이 보장되는 한도 하에 가장 빠른 속도를 다시 찾을 필요가 있어 보인다.
실제 환경에서 한명의 사용자가 100개 이상의 메시지를 연속하여 받지 않도록 하였을 때는 4 쓰레드까지 무리없이 동작하는 것을 확인할 수 있었다.
3분이 걸리던 작업을 약 1분 30초 정도로 단축할 수 있게 됐다
이후 메시지를 낱개가 아니라 모아서 보내는 작업을 통해 1분 30초가 걸리던 작업을 약 20초 이내로 끊을 수 있게 됐다!