동적 실행 엔진SSE스트리밍코드 생성

남의 스트림에 손을 대기로 했다

하루에 같은 열 줄을 다섯 번 고쳤다. 아무것도 잃지 않은 버전은 17분 만에 지운 것이었다.

2025년 12월 12일5

외부 모델 공급자를 붙일 때 우리가 한 약속이 하나 있다. 어느 공급자를 쓰든 클라이언트가 받는 응답 형태는 같아야 한다는 것.

공급자들의 SSE 페이로드는 제각각이다. 필드 이름이 다르고, 중첩 깊이가 다르고, 종료 신호도 다르다. 약속을 지키려면 스트림이 지나갈 때 각 이벤트를 열어서 우리 형식으로 바꿔야 한다.

그 순간부터 우리는 릴레이가 아니라 파서가 된다. 이 글은 그 대가에 관한 것이다.

열 줄, 24시간, 다섯 버전

2025년 8월 4일 오전 10시 17분부터 다음 날 오전 10시 1분까지, 같은 자리의 코드가 다섯 번 바뀌었다.

시각무엇을 했나
8/4 15:20줄 단위 읽기를 버리고 바이트 + 버퍼로 이벤트를 재조립
8/4 15:37버퍼 제거. 청크를 그대로 흘려보냄
8/4 17:21버퍼 복구 + SSE 이벤트 정규식 도입
8/5 10:01정규식 제거. "\n\n" 로 자르고 data: 있는 것만 통과

넣었다 뺀 것이 두 번이다. 버퍼를 넣고 17분 뒤에 뺐고, 정규식을 넣고 다음 날 아침에 뺐다.

커밋 메시지는 전부 "개선"이라고 말한다. 무엇이 어떻게 잘못됐는지는 적혀 있지 않다. 그래서 네 버전을 각각 떼어내서 같은 스트림을 먹여봤다.

같은 SSE 스트림, 네 가지 구현
입력 스트림
data: a\n\ndata: b\n\ndata: c\n\n
실제로 나온 것
1번째'data: a\n\nda'
2번째'ta: b\n\nda'
3번째'ta: c\n\n'
실패구분자 "\n\n" 는 두 글자인데 잘라내는 위치를 네 글자로 잡았다. 매 이벤트가 다음 이벤트의 앞 두 글자를 먹는다. 첫 번째를 빼고 전부 깨진다.

네 버전 중 아무것도 잃지 않은 건 하나다. 원시 통과. 그리고 그건 17분 만에 지워졌다.

왜 원시 통과로 돌아가지 않았나

이 글을 쓰면서 가장 궁금했던 게 그거였다. 손실이 없는 구현이 눈앞에 있었는데 왜 다시 파싱으로 갔을까.

답은 지금 코드에 있다. 최종 구현은 이벤트를 꺼내서 게이트웨이 공용 형식으로 다시 매핑하고, 종료 신호를 걸러내고, 특정 이벤트 타입에서는 필드를 비운다.

원시 통과는 아무것도 잃지 않는 대신 아무것도 바꾸지 않는다. 그런데 이 시스템이 존재하는 이유가 "공급자마다 다른 형태를 하나로 맞춘다"였다. 손대지 않고 흘려보내는 것과 형식을 통일하는 것은 동시에 성립하지 않는다.

열 줄이 열 줄이 아닌 이유

이 코드가 왜 하루 만에 다섯 번이나 손을 타야 했는지는 코드가 어디에 있는지를 봐야 이해된다. 이건 서버가 실행하는 코드가 아니라, 서버가 만들어내는 코드였다.

찍어냄덮어씀생성 템플릿문자열생성된 api.py공급자마다 하나서브앱마운트전체 재생성유지보수 API

노드에 커서를 올리면 연결된 것만 남는다

여기서 두 가지가 따라온다.

첫째, 템플릿의 버그는 이미 배포된 모든 공급자에게 복사된 상태다. 템플릿을 고치는 것으로는 아무것도 고쳐지지 않는다. 배포된 파일을 전부 다시 찍어내야 한다. 그래서 그 일만 하는 유지보수 엔드포인트가 따로 있다.

둘째, 이 코드는 디버거로 밟기 어렵다. 문자열 안에 있으니 편집기가 문법을 검사해 주지 않고, 자동 완성도 없고, 이스케이프가 한 겹 더 있다. "\n\n" 를 두 글자로 셀지 네 글자로 셀지 헷갈린 15:20 의 버그가 바로 그 한 겹에서 나왔다.

지금은 어떤가

넉 달 뒤 이 로직은 템플릿 문자열 밖으로 빠져나와 공용 함수가 됐다. 생성되는 파일은 이제 그 함수를 부르기만 한다. 문자열 안에서 편집하던 시절은 끝났다.

손실도 하나 복구됐다. 콜론으로 시작하는 주석 프레임은 다시 그대로 흘려보낸다. 하트비트가 살아났다.

남은 것

event:id: 만 있는 프레임은 지금도 버려진다. 최종 구현은 data 가 있는 프레임을 처리하고, 없으면 주석인지만 확인한다. 둘 다 아닌 프레임은 어디에도 도달하지 않는다. 8월 5일 아침에 생긴 이 손실은 그대로 남아 있다. 지금 붙어 있는 공급자들이 그런 프레임을 보내지 않아서 아무도 모르고 있을 뿐이다.

커밋 메시지가 증상을 남기지 않았다. 다섯 개 전부 "개선"이라고 적혀 있다. 무엇이 깨졌는지, 왜 되돌렸는지는 없다. 그래서 이 글을 쓰려고 네 버전을 다시 떼어내 돌려봐야 했다. 넉 달 뒤의 나에게 필요한 건 "개선"이 아니라 "이렇게 하면 두 글자가 잘린다" 였다.

하루에 다섯 번 고쳤다는 건 배포마다 확인할 방법이 없었다는 뜻이다. 스트림 하나를 먹여서 나온 것을 비교하는 스무 줄짜리 검사가 있었다면 15:20 의 버그는 커밋 전에 걸렸다. 그 검사를 이 글을 쓰면서 처음 만들었다. 순서가 반대였다.