블로그

진행 상태 레이블에 숨어 있던 메모리 부족 충돌

9월 말의 어느 저녁, 한 사용자의 워크스페이스에서 AI 에이전트가 턴 도중 메모리 부족 오류로 충돌했습니다. 가장 먼저 의심한 원인은 동시에 실행된 서브 에이전트가 너무 많거나, 컨텍스트가 너무 커진 것이었습니다. 둘 다 틀렸습니다. 진짜 원인은 상태 레이블이었습니다. 모델이 큰 셸 명령을 몇 글자씩 스트리밍하는 동안, 우리는 조각마다 지금까지의 전체 명령을 진행 레이블로 다시 보내고 있었습니다. 124KB짜리 명령 하나가 약 41,000개의 이벤트와 약 690MB의 대기 중인 문자열이 되었습니다.

에이전트가 작업을 스트리밍하는 방식

Mythex에서 빌드할 때, 에이전트는 프로젝트의 비공개 클라우드 샌드박스 안에서 실행됩니다. 모델이 작성하는 동안 출력은 브라우저로 스트리밍됩니다. 텍스트는 단어 단위로 나타나고, 도구를 실행하면 — 예를 들어 파일을 쓰는 셸 명령 — 그 도구의 레이블이 실시간으로 업데이트되어 무엇을 하고 있는지 알 수 있습니다.

모델은 작은 조각 단위로 스트리밍합니다. 우리가 사용하던 모델에서 그 조각은 종종 세 글자뿐이었습니다.

처음 세운 가설은 틀렸습니다

충돌은 확장성 문제처럼 보였고, 우리는 그쪽부터 살펴보았습니다.

  • 병렬 서브 에이전트? 턴에서는 두 개만 사용했고, 각각 약 1.5MB의 상태를 가지고 있었습니다. 충분하지 않습니다.
  • 보존되는 추적 데이터? 추적 기능은 실행 트리를 메모리에 보관했지만, 충돌 시점의 힙 크기에 비하면 훨씬 작았습니다.

둘 다 그럴듯한 이야기였고, 둘 다 믿기 쉬웠습니다. 어느 쪽도 충돌을 재현하지 못했습니다. 우리가 계속 다시 배우는 교훈은 이것입니다. 실패를 재현하지 못하는 가설은 원인이 아닙니다.

실제로 재현하기

프로덕션과 같은 메모리 한도를 가진 임시 샌드박스를 만들고, 큰 파일을 현실적인 세 글자 조각으로 스트리밍하는 가짜 모델을 사용했습니다. 소켓 반대편의 수신자는 실제 네트워크처럼 느리게 만들었습니다.

그러자 재현되었습니다. 힙이 올라가고 같은 오류와 함께 프로세스가 죽었습니다.

일단 지켜볼 수 있게 되자 메커니즘은 단순했습니다. 모델이 큰 명령을 작성하는 동안 — 이 경우 124KB 파일을 쓰는 heredoc — 스트리밍 코드는 조각마다 도구의 레이블을 업데이트했습니다. 레이블에 들어가는 것은 새 세 글자가 아니라 지금까지의 전체 명령이었습니다. 그래서 이벤트는 점점 커졌습니다. 3자, 6자, 9자… 124KB까지, 약 41,000번. 소켓은 생산되는 속도만큼 빠르게 보낼 수 없었고, 이벤트는 쓰기 버퍼에 문자열로 쌓였습니다. 약 690MB나 되었습니다.

수정

  • 레이블을 제한합니다. 상태 레이블은 무슨 일이 일어나는지만 말하면 됩니다. 이제 200자로 제한하고, 부분 명령을 다시 훑는 빈도도 조절합니다.
  • 2차 복잡도의 문자열 연결을 없앱니다. 스트리밍된 조각을 반복 문자열 연결로 이어 붙이는 것은 큰 출력에서 O(n²)입니다. 선형 시간에 추가하는 청크 기반 방식으로 바꿨습니다.

그런 다음 대량의 출력 홍수에서 무한히 커질 수 있는 다른 모든 것을 찾아 제한했습니다.

  • 채팅에 표시되는 실시간 명령 출력은 묶여서, 마지막 8,000자만 유지됩니다.
  • 개별 이벤트 줄은 4MB로 제한됩니다.
  • 브라우저가 재연결 후 따라잡기 위해 쓰는 리플레이 버퍼는 프로젝트당 8MB로 제한됩니다.
  • 오래된 체크포인트는 턴이 진행되면서 정리되며, 최신 상태와 이전 턴에 필요한 기준점은 남깁니다.

홍수를 정직하게 테스트하기

이 문제를 테스트하기 어려운 이유는 두 가지였습니다.

  1. 실제 모델은 협조하지 않습니다. 거대한 파일을 출력하라고 하면 모델은 보통 거절하거나, 요약하거나, 더 합리적으로 나눠서 씁니다. 홍수를 테스트하려면 여러분이 말한 대로 정확히 행동하는, 스크립트된 모델이 필요하고, 그것이 실제 에이전트 코드를 통해 실행되어야 합니다.
  2. 스크립트는 모델처럼 행동해야 합니다. 우리의 첫 스크립트 모델은 모든 응답마다 같은 응답 ID를 재사용했습니다. 그러자 에이전트 프레임워크는 각 응답을 이전 응답의 대체물로 취급해서 턴을 일찍 끝내 버렸습니다. UI 새로고침 버그처럼 보였지만 그렇지 않았습니다. 테스트 하네스가 거짓말을 하고 있었던 것입니다. 또한 스크립트에서 모델의 실제 컨텍스트 한도를 강제해야 했습니다. 그렇지 않으면 메모리 수치가 비현실적이었습니다.

우리가 얻은 교훈

  • 스트리밍 UI는 작은 실수를 증폭시킵니다. “조각마다” 보내는 것은 수만 번 보내지는 것입니다. 변화분(델타)만 보내거나, 페이로드를 제한하세요.
  • 느린 경계를 넘는 모든 버퍼를 제한하세요. 소켓, 리플레이 로그, 체크포인트 모두 상한이 필요합니다.
  • 고치기 전에 재현하세요. 처음 두 가지 설명은 그럴듯했지만 틀렸습니다.

최근 Mythex 채팅에 에이전트가 멈추지 않으면서 아주 긴 파일이 나타나는 것을 보셨다면, 이것이 그 이유 중 일부입니다.

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 简体中文 繁體中文(香港) 繁體中文(台灣)