Code › tail-villain
30초
버튼을 눌렀더니 30초가 걸렸다. 기술적으로는 아무 문제가 없었다. 그게 문제였다.
같은 주에 나름 의미 있는 작업을 하나 마쳤다.
로드맵에 소유권을 붙이는 거였다. 로드맵은 JD랑 이력서를 넣으면 맞춤 면접 준비 플랜을 짜주는 기능인데, 그때까지는 누가 만든 건지 구분이 없었다. ownerId 필드를 추가하고 라우트마다 AuthGuard를 달고 쿼리에 소유자 확인을 넣었더니, 예상대로 됐다. 빠르게 끝났다.
하지만 그날 진짜 이슈는 따로 있었다.
로드맵 분석은 Codex가 내 생각과 다르게 키워드 매칭 방식으로 구현해뒀었다. 미리 정해둔 단어 목록으로 JD를 분류하는 방식인데, 애초에 키워드 몇 개로 JD를 제대로 이해할 수 있을 리가 없었다. 직접 Gemini API를 붙여서 JD를 제대로 읽히는 방식으로 바꿨다.
Gemini를 무료 티어로 붙이기로 했다. (일단은)
처음 연결하고 테스트했을 때, 로드맵 생성 버튼을 누르고 기다렸다.
10초. 20초. 30초.
응답이 왔다.
결과물은 나쁘지 않았다. Gemini가 JD를 읽고 직군, 시니어리티, 강점과 부족한 부분까지 구조화해서 내놨는데, 키워드 뱅크로는 흉내도 못 낼 수준이었다.
근데 그보다 먼저 느껴진 게 있었다. 30초를 기다렸다는 사실.
사용자 입장에서 30초짜리 폼 submit은 고장난 것처럼 보인다. 진행 중이라는 신호가 없으면 사람들은 뭔가 잘못됐다고 판단하고 그냥 떠난다. 아무리 결과가 좋아도 소용없다.
기술적으로는 아무 문제가 없었다. 그게 문제였다.
버튼을 누르면 로드맵을 먼저 저장하고, AI 분석은 백그라운드에서 따로 돌리는 방식으로 바꿨다. 사용자는 버튼을 누르자마자 다음 화면으로 넘어가고, 분석이 끝나면 결과가 자동으로 채워진다.
음식 배달 앱에서 주문을 넣으면 주문 완료 화면이 바로 뜨는 것처럼. 주방에서 만드는 동안은 앱이 알아서 상태를 업데이트해줘서, 사용자는 주문이 처리되고 있다는 걸 알고 다른 일을 한다.
구현은 폴링(polling)으로 했다. 프론트엔드가 주기적으로 “분석 끝났어?”를 서버에 물어보다가, 완료되면 결과를 화면에 채우는 식이다. 원래 이런 비동기 작업엔 SQS 같은 메시지 큐를 쓰는 게 정석인데, 큐에 작업을 넣어두면 서버가 순서대로 처리하고 실패해도 재시도가 보장된다. 근데 지금 단계에서 큐 인프라까지 붙이는 건 배보다 배꼽이 더 컸다. 폴링으로 충분했다.
사용자 입장에선 기다리는 게 아니라 기다려지는 느낌으로 바뀌는 거다.
그날 두 번 막혔다.
하나는 백엔드가 뻗은 거였다. 새 컬럼을 추가해놓고 로컬 DB 마이그레이션을 빠뜨렸는데, Prisma가 없는 컬럼을 참조하려다 죽었다. 로그 보고 파악한 다음 마이그레이션을 돌리고 재시작했다.
다른 하나는 Gemini 타임아웃이었다. 무료 티어라 20초 언저리에서 끊기는 경우가 있었는데, 한도를 45초로 늘리고 에러 메시지도 “분석 실패” 한 줄로 묻어버리지 않고 실제 내용이 보이도록 바꿨다. 무슨 이유로 실패했는지 알아야 고칠 수 있으니까.
AI가 들어간 기능은 보통의 폼 submit과 다른 상호작용 모델이 필요하다.
키워드 뱅크였을 때는 분석이 빨랐는데, Gemini가 그 자리를 채우면서 시간이 필요해졌다. 그 순간 인터랙션 모델도 함께 바뀌어야 했다.
LLM이 뒤에서 돌아가는 기능을 만들 때, 사용자가 얼마나 기다려야 하는지는 선택이 아니다. 설계의 일부다.