블로그

서버마다 울리지 않고 한 번만 울리는 알림

Mythex의 운영 알림 중 하나인 빌더 앱의 "크레딧 부족" 알림은 하루에 최대 한 번만 도달하도록 설계되어 있습니다. 그런데 어느 오후에는 다섯 번 도착했습니다. 원인은 API의 각 복사본이 이미 보낸 알림을 자기 메모리에 기억하고 있었다는 점입니다. 서버 두 대가 각각 알림을 보냈고, 배포할 때마다 그 메모리가 지워져 알림이 다시 나갔습니다. 같은 패턴 때문에 백그라운드 작업 하나도 두 번 실행되고 있었습니다. 이제 둘 다 공유 저장소를 통해 조율하며, 그 저장소를 사용할 수 없을 때는 안전한 쪽으로 실패하도록 되어 있습니다.

증상

Mythex는 무언가 확인이 필요할 때 팀에 알림을 보냅니다. 서비스가 다운되었거나, 작업이 멈췄거나, 빌더가 게시한 앱의 크레딧 잔액이 부족해지는 경우입니다. 각 알림에는 중복 제거 구간이 있습니다. 같은 알림을 그 구간 동안에는 한 번만 보내는 기간입니다. 크레딧 부족 알림의 구간은 24시간입니다.

한 오후에 같은 알림이 다섯 번 왔다는 것은 그 구간이 제대로 지켜지지 않았다는 뜻입니다. 더 나쁜 점은 약 11분 간격으로 두 개씩 짝지어 왔다는 것입니다.

왜 그런 일이 생겼나

우리 알림 패키지는 중복 제거 정보를 메모리에 보관했습니다. "알림 키 → 마지막으로 보낸 시각"이라는 표를 실행 중인 프로세스 안에 두었던 것입니다. 이 방식은 프로세스가 한 개뿐이고 절대 재시작하지 않을 때만 작동합니다. 운영 환경에는 둘 다 해당하지 않습니다.

  • API 복제본 두 개. 운영 환경은 API를 두 벌 실행합니다. 각자 자신의 표를 가지고 있었으므로 각각 한 번씩 알림을 보냈습니다. 이것이 짝으로 온 원인입니다.
  • 배포. 배포할 때마다 두 복제본이 모두 새 프로세스와 빈 표로 교체됩니다. 배포 후에는 알림이 마치 한 번도 보내지지 않은 것처럼 다시 나갔습니다.

각 쌍의 두 알림이 약 11분 간격으로 온 것은 이 알림을 발생시키는 작업 때문이었습니다. 이 작업은 두 복제본 모두에서 실행되고 락을 잡는데, 복제본들이 번갈아 락을 가져가서 각자 차례가 되었을 때 알림을 발생시켰습니다.

같은 코드에는 더 조용한 버그도 하나 있었습니다. 표가 무한정 커지지 않도록 500개 키가 쌓이면 스스로 정리했는데, 이때 구간과 관계없이 15분보다 오래된 항목을 모두 지웠습니다. 부하가 걸리면 24시간짜리 중복 제거가 15분 남짓으로 줄어들 수 있었습니다.

해결책

알림마다 공유 기록 하나

이제 API는 알림을 보내기 전에 Redis에서 해당 알림의 중복 제거 키를 먼저 점유합니다. Redis는 두 복제본이 함께 사용하는 메모리 기반 저장소이며, API가 재시작해도 사라지지 않습니다. 이 점유는 "키가 없을 때만 설정하고, 구간이 지나면 만료시킨다"는 단일 연산입니다. 먼저 점유한 복제본이 알림을 보내고, 다른 복제본은 키가 이미 찼다는 것을 보고 조용히 있습니다. 배포 후 새로 뜬 프로세스도 키가 아직 남아 있는 것을 확인합니다.

키와 구간은 이제 알림 패키지가 쓰는 것과 같은 규칙에서 가져오므로, 두 계층이 "같은 알림"이 무엇인지 서로 다르게 판단할 수 없습니다.

닫힌 채로 실패하지 않고, 열린 채로 실패합니다

가장 중요한 결정은 Redis에 연결할 수 없을 때 무엇을 할지였습니다. 우리는 그래도 알림을 보내기로 했습니다. Redis가 다운되었거나 1.5초 안에 응답하지 않으면 알림은 나갑니다. 중복 알림은 소음일 뿐이지만, 누락된 알림은 누군가 알아야 할 일을 모르게 되는 것입니다.

가장 심각한 알림인 P0는 중복 제거에 의해 아예 보류되지 않습니다.

각 항목은 자기 구간을 유지합니다

알림 패키지 자체의 메모리 중복 제거도 이제 각 항목에 구간이 끝나는 시각을 저장하고, 정리할 때는 구간이 끝난 항목만 제거합니다. 24시간짜리 키는 다른 알림이 아무리 많이 들어와도 24시간 동안 남아 있습니다.

같은 버그가 다른 곳에도

같은 패턴이 백그라운드 작업 하나도 두 배로 만들고 있었습니다. 각 워크스페이스의 상태 기록을 샌드박스 제공업체와 맞춰 주는 조정 작업입니다. 매번 실행할 때마다 계정의 모든 샌드박스를 나열하는데(운영 환경에서는 수천 개이고 약 5초가 걸립니다), 여기에 실행 중이거나 일시 정지된 모든 프로젝트도 포함됩니다. 두 복제본 모두 2분마다 이 작업을 실행하고 있었습니다.

이 작업의 쓰기는 멱등적입니다. 즉 두 번 실행해도 한 번 실행한 것과 결과가 같습니다. 그래서 깨진 것은 없었습니다. 단지 일이 두 배였을 뿐입니다. 이제 각 2분 구간은 사용량 미터가 이미 쓰고 있던 방식과 같은 식으로 Redis에서 점유되며, 그 구간을 점유한 복제본만 작업을 실행합니다. Redis에 연결할 수 없다면 작업은 그래도 실행됩니다. 작업을 두 번 하는 것은 해롭지 않지만, 아예 하지 않는 것은 해롭기 때문입니다.

테스트 방법

테스트는 실제 환경을 모델링합니다. 알림 모듈을 두 번 불러와서 두 개의 "복제본"으로 취급하고, 둘 다 하나의 공유 저장소를 바라보게 합니다. 각 복제본에서 같은 알림을 발생시킨 뒤, 배포를 흉내 내기 위해 새로 불러온 세 번째 사본에서 다시 발생시킵니다. 그러면 알림은 정확히 한 번만 보내집니다. 다른 테스트들은 다른 빌더의 알림은 여전히 나가는지, P0는 두 복제본 모두에서 나가는지, 저장소가 다운되었거나 시간 초과가 발생해도 알림이 나가는지를 확인합니다. 정리 기능의 수정에도 자체 테스트가 있습니다. 24시간짜리 키는 20분 뒤 다른 알림 600개가 도착해도 살아남습니다.

조정 작업의 경우, 같은 구간에 두 복제본이 동시에 실행되고 같은 구간에 세 번째 실행이 더해져도 목록 조회는 한 번만 이루어집니다.

얻은 교훈

  • 메모리 상태는 프로세스마다 따로 있습니다. 서버들 전체에 걸쳐 참이어야 하는 것, 즉 "한 번만 보냄", "한 번만 실행됨"은 서버들이 공유하는 곳에 있어야 합니다.
  • 배포는 재시작입니다. 다음 재시작까지만 유지되는 상태는 보통의 근무일 하루도 버티지 못합니다.
  • 어느 쪽으로 실패할지 정하세요. 알림의 경우 공유 저장소가 다운되면 알림이 줄어드는 것이 아니라 늘어나야 합니다.
  • 인스턴스만이 아니라 패턴을 찾으세요. 알림은 눈에 띄는 사례였고, 두 배로 실행된 조정 작업은 같은 버그가 조용히 작업량을 낭비하고 있던 사례였습니다.

이 일은 Mythex 안에서는 보이지 않습니다. 플랫폼을 안정적으로 유지하는 방법에 대해서는 배포 시 502 오류를 0으로 줄인 과정과 빠진 스왑 파일 이야기를 참고하세요. 서비스를 여러 복제본으로 실행한다는 개념이 낯설다면, 서버란 무엇인가 가이드에서 기본을 다룹니다.

Mythex · Pricing · AI로 무엇이든 만들기 · 웹사이트 및 앱 템플릿 · Mythex 비교 · AI 앱 개발 용어집 · 무료 AI 앱 개발 도구 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)