SSE 프레임 파싱을 하루 동안 고친 기록
24시간 동안 같은 열 줄을 고치고 또 고쳤다. 아무것도 잃지 않은 원시 통과 버전은 두 시간이 안 돼 지워졌고, 그렇게 자주 손을 탄 건 그 코드가 생성되는 문자열 안에 있었기 때문이다.
외부 모델 공급자를 붙이면서 약속한 게 하나 있었다. 어느 공급자를 쓰든 클라이언트가 받는 응답 형태는 같아야 한다는 것이다.
공급자들의 SSE 페이로드는 필드 이름도, 중첩 깊이도, 종료 신호도 제각각이다. 약속을 지키려면 스트림이 지나갈 때 이벤트를 하나씩 열어서 우리 형식으로 바꿔야 하고, 그러면 이 층은 그냥 넘겨주는 릴레이에서 이벤트를 뜯어보는 파서가 된다.
8월 4일에서 5일 사이
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분 뒤에 뺐고, 정규식을 넣고 다음 날 아침에 뺐다.
커밋 메시지는 전부 "개선"이라 무엇이 어떻게 잘못됐는지는 적혀 있지 않다. 같은 스트림을 넣으면 네 버전은 이렇게 나온다.
네 버전 중 아무것도 잃지 않은 건 원시 통과 하나였는데, 그건 두 시간이 안 돼서 지워졌다.
왜 원시 통과로 돌아가지 않았나
손실이 없는 구현이 있었는데도 다시 파싱으로 간 이유는 최종 구현을 보면 나온다. 최종 구현은 이벤트를 꺼내서 게이트웨이 공용 형식으로 다시 매핑하고, 종료 신호를 걸러내고, 특정 이벤트 타입에서는 필드를 비운다.
원시 통과는 스트림을 손대지 않고 흘려보내니 그런 일을 할 수가 없다. 그리고 이 층이 있는 이유가 공급자마다 다른 형태를 하나로 맞추는 거였다.
코드가 문자열 안에 있었다
이 코드가 하루 만에 이렇게 자주 손을 탄 데는 코드가 있던 자리도 한몫한 것 같다. 릴레이 로직은 생성 템플릿의 문자열 안에 들어 있었고, 서버는 이 템플릿으로 공급자마다 파일을 찍어냈다.
노드에 커서를 올리면 연결된 것만 남는다
그래서 템플릿에 버그가 있으면 그 버그는 이미 배포된 모든 공급자에게 복사돼 있다. 템플릿만 고쳐서는 소용이 없고 배포된 파일을 전부 다시 찍어내야 해서, 그 일만 하는 유지보수 엔드포인트가 따로 있다.
디버거로 밟기도 어렵다. 문자열 안에 있으니 편집기가 문법을 검사해 주지 않고, 자동 완성도 없고, 이스케이프가 한 겹 더 있다. "\n\n" 를 두 글자로 셀지 네 글자로 셀지 헷갈린 15:20 의 버그가 그 한 겹에서 나왔다.
지금은 어떤가
넉 달 뒤 이 로직은 템플릿 문자열 밖으로 빠져나와 공용 함수가 됐고, 생성되는 파일은 이제 그 함수를 부르기만 한다.
손실도 하나 복구됐다. 콜론으로 시작하는 주석 프레임을 다시 그대로 흘려보내면서 하트비트가 살아났다.
다만 event: 와 id: 만 있는 프레임은 버려진다. 최종 구현은 data 가 있는 프레임을 처리하고 없으면 주석인지만 확인하니, 둘 다 아닌 프레임은 어디에도 도달하지 않는다. 이 손실은 8월 5일 아침부터 그대로인데, 지금 붙어 있는 공급자들이 그런 프레임을 보내지 않아서 드러나지 않았다.
커밋 메시지에는 다섯 개 전부 "개선"이라고만 적혀 있어서, 무엇이 깨졌는지와 왜 되돌렸는지는 코드를 다시 돌려 봐야 안다. 넉 달 뒤의 나에게 필요했던 건 "이렇게 하면 두 글자가 잘린다" 같은 한 줄이었다. 그때는 배포마다 확인할 방법도 없었다. 스트림 하나를 넣고 나온 것을 비교하는 검사만 있었어도 15:20 의 버그는 커밋 전에 걸렸을 것이다.
끝