
생성 진행률에 SSE를 쓰고 폴링을 남겨둔 이유
Porta는 인증이 필요한 생성 화면에서 SSE를 우선으로 쓰고, 연결이 깨질 때만 폴링으로 내려가도록 설계했어요.
요구사항은 단순했어요. 사용자가 Porta에 place URL을 넣고 생성 화면으로 들어오면, 지금 무엇이 일어나고 있는지 바로 보여줘야 했어요.
새로고침을 눌러 보며 추측하게 만들고 싶지는 않았어요. 문제는 구현 쪽이었어요. 실시간처럼 보이는 진행률은 필요했지만, 인증이 있는 요청, 백그라운드 작업, 실제 네트워크 환경의 불안정함까지 같이 감당해야 했어요.
왜 SSE가 잘 맞았는가
생성 화면은 거의 일방향 통신이에요. 브라우저가 서버와 양방향 대화를 계속할 필요는 없어요.
지금 필요한 건 "다음 상태가 무엇인지"를 계속 받아보는 일이에요.
예를 들면 이런 이벤트들이에요.
- place 데이터 가져오기
- brief 작성하기
- HTML chunk 스트리밍하기
complete또는error에 도달하기
이런 흐름에는 SSE가 잘 맞아요.
HTTP 위에서 동작하고, 클라이언트 계약이 작고, "다음 변경 사항만 알려달라"는 성격과 잘 맞기 때문이에요.
왜 메인 경로는 EventSource 대신 fetch를 썼는가
브라우저의 EventSource는 간단해요. 하지만 Porta의 스트림 엔드포인트는 인증이 필요해요.
생성 진행률 라우트도 다른 프로젝트 API와 같은 접근 제어를 받아야 했고, 제품 관점에서 가장 자연스러운 규칙은 "스트림 요청에도 bearer token을 보낸다"였어요.
그래서 실제 메인 경로는 fetch 기반 SSE 파서를 택했어요. 브라우저는 여전히 표준 data: 이벤트를 받지만, 요청 자체는 fetch로 보내기 때문에 Authorization 헤더를 붙일 수 있어요.
이 선택은 작아 보이지만 책임 경계를 더 깔끔하게 만들었어요.
- 프론트엔드는 인증된 스트림 연결과 이벤트 파싱을 담당하고
- 백엔드는 권한 확인, 이벤트 전송, terminal 이벤트 보장을 맡아요
왜 폴링을 일부러 남겨뒀는가
많은 시스템은 "스트리밍을 붙였다"에서 멈춰요.
하지만 우리는 UI가 그 낙관적인 가정에 매달리게 두고 싶지 않았어요.
실시간 연결은 생성 작업 자체와 무관한 이유로도 실패할 수 있어요.
- 모바일 네트워크가 중간에 바뀌는 경우
- 브라우저나 프록시의 버퍼링 특성
- 일시적인 연결 끊김
- 긴 응답보다 일반 HTTP polling이 더 안정적인 환경
이럴 때 폴백이 없으면, 실제 작업은 잘 돌아가는데 화면만 멈춘 것처럼 보이에요.
그게 가장 나쁜 경험이에요. 백엔드는 정상인데 제품은 고장난 것처럼 느껴지기 때문이에요.
그래서 Porta는 polling을 부끄러운 임시방편이 아니라 의도적인 폴백 경로로 둬요.
행복 경로는 단순해요.
- 우선 인증된 SSE를 시도한다
- 들어오는 이벤트로 running 화면을 갱신한다
- 연결이 실패하면 project 상태 polling으로 내려간다
이렇게 하면 제품이 사용자에게 더 솔직해져요. 우리는 실시간 채널을 선호하지만, 전체 경험을 거기에만 걸지는 않아요.
중요한 건 전송 방식보다 이벤트 계약이었다
여기서 배운 점은 "SSE냐 polling이냐"가 설계의 전부가 아니라는 점이에요.
더 중요한 질문은 프론트엔드가 무엇을 믿고 움직일 수 있느냐였어요.
Porta에서 유용했던 계약은 이래요.
- 백엔드는 순서가 있는 JSON 이벤트를 보낸다
done,error는 명시적인 terminal 이벤트다- 프론트엔드는 상태, 미리보기, chunk 결과를 점진적으로 반영한다
- 완료 후에는 상세 화면을 다시 불러와 최종 상태를 본다
이 계약이 있으니 폴백도 견딜 만해져요. 생성이 끝났는지 화면이 추측하는 게 아니라, 작은 상태 머신을 읽고 있는 셈이기 때문이에요.
일부러 과하게 만들지 않은 부분
우리는 곧바로 WebSocket으로 가지 않았어요. 브라우저와 서버 사이에 별도 실시간 프로토콜을 만들지도 않았고, 낮은 지연의 양방향 통신을 요구사항으로 키우지도 않았어요.
그 절제가 중요했어요.
Porta에 필요했던 건 "가장 화려한 실시간"이 아니라, "진행률이 분명하고, 충분히 안정적으로 흐르고, 연결이 어그러져도 끝까지 도달하는 경험"이었기 때문이에요.
마무리
이 설계에서 가장 유용한 점은 SSE가 우아하다는 사실이 아니에요.
우아한 경로가 실패해도 제품이 계속 움직인다는 점이에요.
SSE는 생성 화면에 실시간으로 움직이는 느낌을 줘요. Polling은 네트워크가 협조하지 않을 때도 결국 결과에 도달하게 해줘요.
현실적인 제품에서는 이런 조합이 종종 가장 좋은 선택이에요.


