그렇다면 고정 메시지 아카이브를 만들면 좋지 않을까? 하는 생각이 들었다. 일반 텍스트 채널의 메시지 제한은 없다. 따라서 고정된 메시지를 다른 채널에 옮기는 것은 가장 가깝고도 납득할만한 선택이다. Show 이 작업을 대신 해주는 봇 pinee가 하는 일가장 기본적으로 메시지의 고정 이벤트를 감지해서 아카이브 채널에 복사해야 한다. 그리고 막 고정된 메시지 아카이브메시지의 고정을 감지해서 아카이브를 생성하는 것은 두 작업으로 나눌 수 있다.
다행히 둘 다 어렵지 않았다. 메시지 업데이트 이벤트가 감지되었을 때, 아래 조건을 만족하면 그것은 메시지 고정 이벤트로 간주된다:
고정된 메시지를 확보했을 때, 이를 아카이브하기 위해서는 적절한 채널이 있어야 한다. 길드에 존재하는 모든 채널 중에서, 토픽에 기존 메시지 아카이브가장 어려웠던 부분이 기존의 메시지를 확보하는 것이었다. Discord API는 고정된 메시지를 바로 가져오는 endpoint를 제공한다. 이를 사용하면 현재 특정 채널에 존재하는 고정 메시지를 모두 가져올 수 있다. 그런데 이렇게 하면 좀 아쉬운 점이 있었다. 고정 메시지 50개 제한은 항상 부족했고, 새 메시지를 고정하기 위해 기존의 메시지들은 고정 해제되었다. 그 메시지들은 위에서 언급한 endpoint로 가져올 수 없었다. 옛 고정 메시지 가져오기다행히 방법이 있었다. 메시지가 고정되면, 시스템이 이를 알려주는 메시지를 보낸다. 해당 메시지에는 고정된 메시지에 대한 레퍼런스가 있다. 아래와 같은 형태이다.
레퍼런스의 3개 필드의 조합은 Discord상에서 유일한 메시지를 식별하는 데에 사용할 수 있다. 이것만 있으면 고정되었다가 해제된 메시지도 가져올 수 있다.
이렇게 구현하기로 결정했다:
성능 이슈
메시지를 가져오는데, 16만개째에서 16만개째에서 램을 255MB를 잡아먹으니, 1GB라면 64만개 정도는 감당할 수 있겠지 싶어 Heroku dyno를 Standard-2X로 업그레이드 했다.
그리고 Node 런타임이 메모리를 충분히 쓰도록 0 옵션도 추가해 보았다.괜찮은가 싶더니 55만개 째에서 메모리 사용량 1GB에 근접하며 또 뻗었다. 스케일링으로 될 문제가 아니다 싶어 새롭게 접근하기로 했다. 모든 메시지를 한 번에 가져오는게 맞는 걸까? 1 부터 의심하기 시작했다.Discord API는 봇이 권한만 있다면 모든 채널, 모든 메시지를 가져올 수 있도록 허용한다. 대신 1회 요청에 100개까지만 허용한다. 100개씩 나누어서 처리하지 않고, 74만개에 달하는 메시지를 모두 한 인스턴스에 담아두고 처리하는게 옳은 걸까? 하는 물음이 들었다. 당연히 이런 짓은 하면 안 되는 거였다.
에러 한번만 생기면 처음부터 다시 시작해야 하는게 맞는 걸까?두 시간에 걸쳐 메시지를 수십만개 가져오던 도중 Discord 서버가 500 응답을 한 번이라도 준다든가, Heroku dyno가 하루에 한 번 강제로 재시작되었다든가(프로세스가 무슨 일을 하든 그냥 죽여버린다…) 하면 두 시간짜리 노력이 물거품이 되어버린다.
물론 의도한 건 아니고 대용량 데이터와 마주할 상상을 하지 못 했기 때문이다. 그래서 fetch 세션 동안에 가져온 데이터는 Redis를 사용해 적당히 보관하기로 했다. 개선제일 먼저, exception handling에서 벗어난 API 호출을 2로 감쌌다. 그리고는 가져온 데이터를 캐싱하도록 뜯어고치기 시작했다.수십만개의 메시지 중 실제로 건져야 하는 건 400개 내외이다. 수천번의 fetch는 모두 저 400개 내외의 메시지를 위한 것이다. 건져낸 메시지는 모두 Redis에게 맡겼다. 앱 메모리에 인스턴스로 담아두지 않고 Redis를 주 저장소로 사용했다. 이렇게 하면 이런 것들이 좋다:
Redis는 또한 현재까지 fetch한 메시지의 갯수나 마지막으로 처리된 메시지의 id와 같은 정보를 가지고 있다. 이들은 fetch 세션을 시작할 때에 초기 값으로 활용된다.
|