블로그

이미지가 많은 에이전트 턴을 메모리 안에 담아 두기

프로덕션에서 한 에이전트 턴이 카탈로그 스캔 여섯 장에서 제품을 잘라 내고 그 잘라 낸 이미지를 확인하는 도중 952MB에서 메모리를 모두 썼습니다. 누수는 하나가 아니라 세 개였습니다. 모델을 호출할 때마다 그 턴의 이미지를 원본 크기 그대로 다시 보냈고, 모델을 호출하는 데 쓰는 라이브러리는 턴이 끝날 때까지 모든 요청을 살려 두었으며, 에이전트 프레임워크의 추적 기능은 턴 내내 모든 호출의 입력, 이미지까지 포함해서 보관하고 있었습니다. 세 가지를 모두 고쳤습니다. 이미지는 보내기 전에 줄이고, 각 호출의 이미지는 호출이 끝나는 즉시 놓아 줍니다.

에이전트가 이미지를 보는 방법

Mythex에서 만들 때 에이전트는 프로젝트의 클라우드 샌드박스 안에서 작업합니다. 이미지를 볼 수 있습니다. 첨부한 스크린샷, 목업, 프로젝트 안의 파일, 또는 에이전트가 방금 직접 만든 이미지를 대화와 함께 모델에 보내는 방식입니다.

턴은 당신의 메시지 하나에 대해 에이전트가 하는 모든 일입니다. 한 턴 안에서 에이전트는 모델을 여러 번 호출합니다. 생각하고, 도구를 실행하고, 결과를 보고, 다시 생각합니다. 각 호출은 지금까지의 대화를 보내며, 여기에는 턴의 이미지도 포함됩니다. 이미지는 텍스트로 인코딩됩니다(base64이며, 약 3분의 1만큼 커집니다).

충돌한 턴은 합리적인 일을 하고 있었습니다. 카탈로그 스캔 여섯 장, 각각 약 2.4MB를 받아서 개별 제품을 잘라 내고, 잘라 낸 이미지를 계속 다시 읽어 확인했습니다. 큰 이미지 여러 장을 담은 모델 호출이 많이 일어났습니다.

원인 1: 모든 호출이 원본 크기 이미지를 다시 보냈다

각 모델 호출은 턴의 이미지를 원본 크기로 담아 요청 본문을 다시 만들었습니다. 호출 한 번에 약 20MB의 base64가 쌓였습니다. 본문은 에이전트 메모리 안에서 하나의 문자열로 만들어지므로, 호출할 때마다 약 20MB가 새로 할당되었습니다.

원본 크기로 보내도 얻는 것은 없었습니다. 모델이 긴 변을 약 1,300픽셀로 스스로 줄여서 보기 때문에, 추가 픽셀은 메모리와 업로드 시간만 낭비했습니다.

해결: 한 번 줄이고, 작게 보내기

  • 400KB를 넘는 이미지는 보내기 전에 긴 변이 최대 1,568픽셀인 JPEG로 변환합니다. 더 작은 이미지는 그대로 보냅니다.
  • 변환에는 모든 Mythex 샌드박스에 들어 있는 ImageMagick을 사용하며, 파일당 한 번만 실행합니다. 모든 호출에서 같은 스캔을 보여 주는 턴은 줄어든 사본을 재사용합니다. 캐시 키는 파일 경로, 크기, 수정 시각이므로, 수정된 파일은 다시 변환됩니다. 캐시에도 자체 크기 상한이 있어서, 캐시 자체가 메모리를 채우는 일은 없습니다.
  • 한 모델 호출에서 이미지에 쓸 수 있는 예산은 16MB에서 6MB로 줄었습니다.

같은 여섯 장의 스캔이 이제 호출당 19.7MB가 아니라 4.4MB만 씁니다.

원인 2: 모델 클라이언트가 요청을 놓지 않았다

요청 하나의 크기는 일부일 뿐이었습니다. 메모리는 호출이 거듭될수록 계속 늘어났습니다. 스테이징 환경에서 같은 종류의 턴은 완전한 가비지 컬렉션을 한 뒤에도 369MB의 살아 있는 메모리를 붙잡고 있었습니다. 완전한 가비지 컬렉션은 도달할 수 없는 것이 모두 해제되는 시점입니다. 힙 샘플링 결과, 증가분은 모두 모델 클라이언트 라이브러리에서 요청 본문을 인코딩하는 부분에 몰려 있었습니다.

원인은 abort 리스너였습니다. Stop 버튼을 누르면 턴의 abort 시그널이 발생하고, 이를 듣고 있던 모든 요청이 취소됩니다. 우리는 그 턴 전체의 시그널 하나를 모든 요청에 넘겼습니다. 클라이언트 라이브러리는 받은 시그널에 "abort" 리스너를 추가하고, 그것을 제거하지 않았습니다.

그래서 턴의 모든 요청은 턴이 지속되는 동안 턴의 시그널에서 계속 도달 가능한 상태였습니다. 리스너가 컨트롤러를 붙잡고, 컨트롤러가 요청을 붙잡고, 요청이 이미지를 포함한 전체 본문을 붙잡았습니다.

해결: 요청마다 시그널 하나

이제 각 요청은 자기만의 시그널을 가집니다. 요청이 실행되는 동안에만 턴의 시그널에 연결되고, 스트리밍 응답이라면 마지막 청크를 읽을 때까지 연결되어 있다가 바로 분리됩니다. Stop은 여전히 진행 중인 요청에 닿지만, 턴의 시그널은 한 번에 리스너 하나만 갖습니다.

테스트는 세 부분을 모두 확인합니다.

  • 호출 12번 후 턴의 시그널에 리스너가 전혀 남지 않습니다. 수정 전에는 12개였습니다.
  • 8MB짜리 요청 15개를 보내는 동안 메모리는 평평하게 유지됩니다. 수정 전에는 128MB 늘었습니다.
  • Stop은 여전히 스트림 중간에 있는 요청을 중단시킵니다.

원인 3: 추적이 모든 호출의 입력을 보관했다

힙 스냅샷에서 세 번째 보유자가 드러났습니다. 모델 호출 하나당 약 6MB를 붙잡고 있었습니다. 이것은 에이전트 프레임워크의 이벤트 스트리밍에서 나왔습니다. 턴의 모든 모델 호출을 턴의 자식으로 기록하며, 입력까지 포함해서 턴이 끝날 때까지 보관합니다. 모든 호출에 첨부된 모든 이미지가 함께 메모리에 남았습니다.

해결: 호출이 반환되면 이미지를 비우기

이미지는 특정 모델 호출을 위해 만든 메시지에만 붙습니다. 대화의 저장된 상태에는 절대 저장되지 않습니다. 따라서 호출이 반환되면 그 메시지들의 이미지 데이터를 비워도 안전합니다. 추적에는 호출의 텍스트 기록이 남고, 픽셀은 사라집니다.

한 가지 세부 사항이 중요했습니다. 이미지는 제자리에서 비워야 합니다. 추적기는 한 메시지의 여러 참조를 통해 같은 이미지 데이터에 닿을 수 있으므로, 새로운 빈 리스트로 바꿔 끼우면 원본(픽셀 포함)이 여전히 도달 가능한 상태로 남습니다. 저장된 대화 상태는 전혀 수정되지 않습니다.

테스트는 에이전트 그래프를 같은 이벤트 스트리밍 아래에서 실행하고, 모든 호출에 4MB 이미지를 붙입니다. 수정 전에는 16번 호출하는 동안 메모리가 77MB 늘었습니다. 수정 후에는 평평하게 유지됩니다.

얻은 교훈

  • 메모리 문제는 여러 겹으로 올 수 있습니다. 세 가지 원인이 각각 이미지를 너무 오래 붙들고 있었고, 턴이 메모리 안에 머무르려면 셋 모두 없어져야 했습니다.
  • 라이브러리에 넘긴 것은 유지될 수 있습니다. 시그널과 추적 모두 자신이 맡은 호출보다 오래 살아남았습니다. 메모리가 호출마다 늘어난다면, 그 호출이 건드리는 장수 객체가 무엇인지 보세요.
  • 상대가 버릴 것은 보내지 마세요. 모델이 어차피 버릴 픽셀은 매 호출마다 우리 메모리를 차지했습니다.
  • 메모리를 직접 테스트하세요. 각 수정에는 힙 증가나 리스너 수를 측정하는 테스트가 함께 있어서, 회귀는 충돌이 아니라 숫자로 드러납니다.

짧은 기간에 에이전트 쪽에서 나온 두 번째 메모리 이야기입니다. 첫 번째는 하루 41,000번 다시 보내진 진행 라벨이었습니다. 에이전트가 무엇이며 어떻게 작동하는지 알고 싶다면 AI 에이전트 가이드와 채팅 문서를 참고하세요.

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