Dumb Game Lab

블로그 · 개발 이야기

1인 개발 실수 기록

알람 하나가 읽기 한도 500만 행을 혼자 썼다: Durable Objects 1101 추적기

Justin Lee, Dumb Game Lab ·

도트혈맹 자동 사냥 화면. 픽셀 아트 던전에서 캐릭터가 몬스터와 싸우고 위에 금화와 보석 숫자가 보인다

11월 7일 오류 수집 서버가 500 오류를 내기 시작했고, 에러 코드는 1101 «Exceeded allowed rows read»였습니다. 범인은 같은 계정의 게임 서버(도트혈맹)가 5초마다 도는 알람이었습니다. 이 알람이 users·feed 표를 인덱스 없이 훑어 하루 읽기 한도 500만 행 중 498만 행을 혼자 썼습니다. 한도는 계정 전체가 나눠 쓰기 때문에 초대 서버와 오류 수집 서버가 같이 멈췄습니다. 인덱스 2개와 10분 캐시로 고쳤고, 이제는 매시간 서버별 읽기 행을 재서 60%가 넘으면 범인 이름과 함께 알립니다.

숫자부터

오류 수집 서버가 멈춘 걸 알고 나서 처음 한 일은 숫자를 모으는 것이었습니다. 한도가 얼마이고, 누가 얼마나 읽었는지부터 봤습니다.

항목숫자메모
하루 읽기 한도(무료)500만 행계정 안의 모든 서버가 같이 씁니다. 00:00 UTC(한국 09:00)에 초기화
도트혈맹 알람 하나498만 행11월 7일 하루 합계
알람이 도는 횟수하루 17,280번5초마다. 86,400초를 5로 나눈 값
한 번에 읽은 행약 290행498만을 17,280으로 나눈 계산값
오류 수집기 요청하루 2~3천 건읽은 행은 하루 140만~514만
하루 한도 500만 행과 도트혈맹 알람 하나가 읽은 498만 행을 나란히 그린 막대 그래프
알람 하나가 한도의 99.6%를 읽었습니다.

처음엔 엉뚱한 서버를 의심했다

증상은 오류 수집 서버에서 먼저 보였습니다. 세션 목록과 이슈 목록을 부르면 500이 나왔고, 본문에는 error code: 1101만 적혀 있었습니다. 같은 시각 친구 초대 서버도 같은 코드로 멈춰 있었습니다.

긴급 알림 띠에는 초대 서버가 범인으로 적혔습니다. 멈춘 서버를 그대로 범인이라고 적은 겁니다. 하지만 초대 서버는 하루 요청이 몇천 건뿐이라 그 정도 읽기를 만들 수 없습니다.

오류 수집기도 마찬가지였습니다. 요청은 하루 2~3천 건인데 읽은 행이 140만에서 514만이었습니다. 요청 수와 읽은 행이 이렇게 안 맞으면 요청이 아니라 다른 누군가가 읽고 있다는 뜻이었습니다.

범인은 5초 알람이었다

한도는 계정 전체가 나눠 쓰니까, 서버별 사용량을 따로 봐야 했습니다. Cloudflare API로 서버(워커)별 읽기 행을 뽑아 보니 한 곳이 거의 전부였습니다. 도트혈맹 게임 서버였습니다.

이 서버는 5초마다 알람이 돕니다. 알람 안에서 접속 중인 사람 수를 세고 봇 명단을 읽는 질의가 users·feed 표에 인덱스가 없어서 매번 표를 처음부터 끝까지 훑고 있었습니다.

한 번에 290행은 작아 보입니다. 그런데 알람은 하루 17,280번 돌고, 곱하면 498만이 됩니다.

도트혈맹 강화 화면. 대장간 배경에서 무기가 +7에서 +8이 되는 성공·파괴 확률이 보인다
범인은 이 게임의 서버 쪽 알람이었습니다.

고친 것

도트혈맹 서버와 오류 수집 서버를 각각 고쳤습니다. 둘 다 원인은 같았습니다. 인덱스 없이 범위를 조회하는 질의였습니다.

  • 고친 서버를 배포해도 그날 한도는 이미 바닥이라 바로 살아나지 않았습니다. 오류 수집 서버는 다음 초기화(00:00 UTC)에 풀립니다.
  • 인덱스를 걸면 한 번에 읽는 행이 표 전체 크기에서 필요한 행 수로 줄어듭니다.
서버바꾼 것
도트혈맹users(bot, seen)·feed(bot, ts) 인덱스 2개
도트혈맹봇 명단을 10분 동안 메모리에 캐시
오류 수집기issues(last_at)·hits(day) 인덱스
오류 수집기90일 지난 hits·sessions를 하루 한 번 삭제

같은 일이 또 안 나게

고치는 것만으로는 부족했습니다. 이번엔 한도가 바닥난 뒤에야 알았고, 누가 먹는지는 한참 뒤에 알았습니다. 그래서 두 가지를 더 넣었습니다.

첫째는 시험입니다. 알람을 400번 돌리는 동안 실행된 질의를 모아 EXPLAIN QUERY PLAN으로 계획을 보고, users·feed를 인덱스 없이 훑는 질의가 하나라도 있으면 시험이 실패합니다. LIMIT이 붙어 앞에서 멈추는 질의는 뺍니다.

둘째는 감시입니다. 매시 7분에 서버별 읽기 행을 합쳐 한도의 60%가 넘으면 범인 서버 이름과 행 수를 붙여 알림을 올립니다. 초대·오류 수집 서버를 직접 찔러서 1101이 나오면 그것도 올리고, 살아나면 내립니다.

도트혈맹 혈맹 채팅 화면. 혈맹원들이 대화하는 채팅창이 보인다
혈맹 채팅이 있는 도트혈맹 화면입니다.

내가 틀린 것

틀린 건 쿼리 하나가 아니었습니다. 서버 여러 개가 무료 한도 하나를 나눠 쓰는데, 서버마다 얼마 읽는지 재는 곳이 없었습니다. 한 서버가 한도를 다 쓰면 멀쩡한 서버가 대신 죽고, 죽은 쪽만 보게 됩니다.

그래서 도트혈맹에는 알람 경로 질의가 인덱스를 타는지 보는 시험을 넣었습니다. 한도가 계정 공용이면 서버별 사용량 표가 먼저 있어야 한다는 걸 이번에 배웠습니다.

FAQ

Cloudflare Durable Objects 1101 오류는 무슨 뜻인가요?

무료 한도를 넘었다는 뜻일 수 있습니다. 제 경우 본문에 «Exceeded allowed rows read»가 같이 나왔고, 하루 읽기 행 한도(계정 전체 500만 행)를 다 쓴 것이었습니다.

Durable Objects 읽기 한도는 서버별인가요, 계정 전체인가요?

제 계정에서는 계정 전체가 하루 500만 행을 나눠 썼습니다. 그래서 한 서버가 거의 다 쓰자 다른 서버 둘도 같이 멈췄습니다.

한도가 바닥난 날 서버를 고치면 바로 살아나나요?

바로는 아닙니다. 고쳐서 배포해도 그날 한도는 그대로라서, 00:00 UTC(한국 시간 오전 9시) 초기화를 기다려야 했습니다.

알람이 표 전체를 훑는지 어떻게 확인하나요?

알람 안에서 실행되는 질의를 모아 EXPLAIN QUERY PLAN을 돌려 보면 됩니다. 계획에 SCAN 표이름이 인덱스 없이 나오면 표 전체를 읽는 겁니다.

다른 글

새 게임과 작은 소식을 한 달에 한 번쯤 보냅니다.

다른 언어English한국어日本語繁體中文