Skip to content

[perf] record_agent_memory 가 O(n²) 로 전체 파일을 재직렬화 #3

Description

@jisung-02

요약

UpdateMemory 가 매 호출마다 memory.json 전체를 다시 읽고 JSON 을 재직렬화해 다시 씁니다. 노트가 누적될수록 단일 추가 비용이 2차 증가합니다. 보안 취약점(자원 고갈)과 버그(보호 패턴 replace 의미)도 같은 코드에서 발생합니다.

문제

// internal/agent/memory.go:88-118
func UpdateMemory(patch MemoryPatch) (Memory, error) {
    memory, err := LoadMemory()   // ← 매번 전체 파일 재읽기
    ...
    if patch.Note != "" {
        memory.Notes = append(memory.Notes, patch.Note)  // ← 무한 누적
    }
    if err := SaveMemory(memory); err != nil { ... }      // ← 매번 전체 재직렬화 + 재작성
}

// internal/agent/memory.go:106-108 — 보호 패턴은 append 변수명이지만 실제는 replace
if len(patch.ProtectedInstancePatterns) > 0 {
    memory.ProtectedInstancePatterns = append([]string(nil), patch.ProtectedInstancePatterns...)
}

재현 / 증거

8KB 노트 100개씩 추가하며 단계별 누적 비용 측정:

PERF-4: 누적 노트 100개일 때 다음 100개 추가 = 397.924584ms
PERF-4: 누적 노트 200개일 때 다음 100개 추가 = 1.079682084s
PERF-4: 누적 노트 300개일 때 다음 100개 추가 = 2.045627417s
PERF-4: 누적 노트 400개일 때 다음 100개 추가 = 2.837943334s
PERF-4: 누적 노트 500개일 때 다음 100개 추가 = 3.659746958s
누적 노트 다음 100개 추가 배율
100개 398ms 1.0×
300개 2.05s 5.1×
500개 3.66s 9.2×

거의 완벽한 2차 성장.

영향

성능: 노트 500개 누적 시 1회 추가에 3.66초. 장기 운용에서 record_agent_memory 가 눈에 띄게 느려짐.

보안: 노트 개수/길이 제한이 없어 클라이언트가 반복 호출로 디스크와 IO 를 고갈시킬 수 있음 (CWE-400, 로컬 DoS).

버그: 보호 패턴 갱신이 append 변수명이지만 실제로는 replace. 운용자가 "추가"라고 생각하고 기존 보호 패턴을 날릴 수 있음.

제안 수정

세 문제를 한 번에 해결하는 재설계:

  1. 노트 상한: 최근 N 개(예: 100개)만 유지하고 오래된 것은 자동 삭제
  2. 노트 별도 저장: 노트는 별도 JSONL 파일로 O_APPEND 한 줄 추가 → SaveMemory 가 전체 재작성 안 함
  3. 보호 패턴 의미 정비: 진짜 append 로 변경하거나, replace 라면 입력/문서에 명확히 표시
// 노트 상한 예시
const maxNotes = 100
if patch.Note != "" {
    memory.Notes = append(memory.Notes, patch.Note)
    if len(memory.Notes) > maxNotes {
        memory.Notes = memory.Notes[len(memory.Notes)-maxNotes:]
    }
}

영향받는 코드

  • internal/agent/memory.go:88-118 (UpdateMemory)
  • internal/agent/memory.go:71-86 (SaveMemory)
  • internal/agent/memory.go:106-108 (보호 패턴 replace)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions