변수 중복 선언이 전부를 멈추다
이날은 폴백 플러그인의 치명적인 버그를 잡은 날이었다.
변수 하나가 중복 선언되어 있어서 모든 이벤트 핸들러가 멈추고 있었다.
그리고 컨텍스트 압축에서 경쟁 조건도 잡았다.
동시성 버그가 연달아 터진 날이었다.
가장 치명적인 버그는 변수 중복 선언이었다.
증상은 극단적이었다.
아무 이벤트 핸들러도 동작하지 않았다.
에러 로그도 안 나오고, 폴백도 안 돌아가고, 아무 일도 안 일어났다.
“전부 죽었다”는 표현이 맞는 수준이었다.
원인을 찾아보니 변수가 중복 선언되어 있었다.
같은 이름의 변수가 두 번 선언되니까, 에러가 나면서 핸들러 파이프라인 전체가 멈춘 거다.
이런 버그는 조용하다.
에러 로그가 안 보일 수도 있고, 어디서 멈췄는지 파악이 어렵다.
수정은 단순했다.
중복 선언을 제거하면 됐다.
근데 이 버그가 미친 영향은 컸다.
핸들러가 안 돌면 폴백이 안 일어나니까, 에러가 나도 그냥 세션이 죽는다.
폴백 플러그인이 있으나마나인 상태였다.
컨텍스트 압축 경쟁 조건
두 번째 버그는 더 미묘했다.
컨텍스트 압축에서 경쟁 조건이 있었다.
로그를 보니 0.075초 만에 자체 압축 카운터가 1에서 2, 3으로 치솟았다.
근데 실제 압축은 수십 초가 걸린다.
0.075초 만에 압축이 세 번 끝날 리가 없다.
뭔가 잘못되고 있었다.
원인은 동시 진입
원인을 추적해보니 세션 idle 이벤트가 연달아 들어오고 있었다.
핸들러가 비동기로 동작하는데, 첫 번째 이벤트가 대기하는 동안 두 번째, 세 번째 이벤트가 같은 핸들러에 진입한 거다.
첫 번째 이벤트가 컨텍스트 임계값을 확인하는데, 이 확인이 대기 지점이다.
대기하는 동안 이벤트 루프가 풀려서 다음 idle 이벤트가 들어온다.
그 이벤트도 같은 핸들러에 진입한다.
이렇게 세 개의 이벤트가 동시에 같은 핸들러를 실행하고 있었다.
그러니까 자체 압축 카운터가 동시에 세 번 올라간 거다.
카운터를 올리는 코드가 동기적이긴 하지만 세 개의 이벤트가 각자 올리니까 세 번이 된다.
그리고 각자 압축을 시도하니까, 세 개의 압축이 동시에 돌아간다.
실행 중 집합으로 해결
해결은 실행 중 집합을 만드는 거였다.
자체 압축이 진행 중인지 추적하는 집합을 뒀다.
첫 번째 이벤트가 들어오면 집합에 추가한다.
두 번째 이벤트가 들어왔을 때 집합에 있으면, 그 이벤트는 그냥 반환한다.
압축을 시도하지 않는다.
압축이 끝나면 집합에서 제거한다.
이 패턴은 이전에도 썼던 거다.
다른 상태 관리에서도 실행 중 집합으로 동시 진입을 막고 있었다.
이번에 같은 패턴을 자체 압축에도 적용한 거다.
이중 압축 문제
비슷하지만 다른 문제도 있었다.
플러그인이랑 호스트가 각자 독립적으로 압축을 실행하는 거다.
플러그인이 자체 압축을 돌리고 있으면서 호스트도 압축을 돌린다.
둘이 서로 모르니까, 같은 세션에 압축이 두 번 돌아간다.
약 29초 간격으로 두 번의 압축이 일어나는 거다.
이건 더 근본적인 구조 문제였다.
플러그인이랑 호스트가 압축을 누가 담당하는지 명확하지 않았다.
각자 판단해서 돌리니까 충돌이 났다.
조기 복귀 현상
그리고 조기 복귀 문제도 잡았다.
플러그인이 큰 모델에서 작업을 끝내면 원래 모델로 돌아간다.
근데 너무 일찍 돌아가고 있었다.
자식 에이전트가 아직 실행 중인데도 복귀해버린 거다.
자식 에이전트가 돌아가고 있으면 기다렸다가 복귀해야 한다.
근데 자식 상태를 추적하지 않으니까, 일찍 복귀해버린 거다.
자식 활성 상태 추적을 추가해서 자식이 다
끝날 때까지 복귀를 미루게 만들었다.
압축 프롬프트 단순화
버그 수정과 함께 압축 프롬프트도 단순화했다.
토큰 제한도 줄였다.
압축 프롬프트가 너무 길면, 압축 자체가 토큰을 많이 쓴다.
압축은 컨텍스트를 줄이는 작업인데, 압축 자체가 비싸면 본말전도다.
프롬프트를 줄이니까 압축 비용도 내려갔다.
마무리
이날은 동시성 버그와 싸운 날이었다.
변수 중복 선언이 전부를 멈추는 버그부터, 0.075초 만에 압축 카운터가 세 번 오르는 경쟁 조건까지. 동시성 버그의 특징은 재현이 어렵다는 거다.
순서대로 실행하면 안 나타난다.
타이밍이 겹쳐야 나타난다.
그래서 로그 기반 디버깅이 중요하다.
“0.075초 만에 카운터가 3이 됐다”는 로그가 없었으면 원인을 못 찾았을 거다.
그리고 실행 중 집합 패턴이 다시 증명됐다.
비동기 핸들러에서 같은 작업이 중복으로 실행되는 걸 막으려면, 실행 중 집합으로 가드하는 게 가장 확실하다.
앞서 다른 곳에서 쓰던 패턴을 또 써서 해결했다.
검증된 패턴을 재사용하는 게 빠르고 안전하다.