API 응답은 4ms였지만, 네비게이션은 여전히 느리게 느껴졌다
SvelteKit과 Rust API로 구축된 프로젝트 관리 앱을 디버깅하고 있었습니다.
로컬 환경에서는 모든 것이 즉각적으로 느껴졌습니다. 하지만 VPS에서는 네비게이션이 무겁게 느껴졌습니다. Tickets나 Timeline 같은 페이지를 여는 데 너무 오래 걸렸고, 티켓을 클릭하면 미리보기가 나타나기 전까지 눈에 띄는 지연이 발생했습니다.
저는 서버가 문제라고 생각했습니다. 느린 데이터베이스 쿼리나 사양이 낮은 VPS를 의심했습니다.
하지만 측정 결과는 제 생각이 틀렸음을 보여주었습니다.
feature list 엔드포인트를 확인했습니다. 단 52개의 티켓에 대해 API는 4ms 만에 응답했습니다. 매우 빠른 속도였죠. 하지만 응답 크기는 354KB였습니다.
SvelteKit route payload에서도 동일한 문제가 나타났습니다. 단순한 리스트치고는 데이터 크기가 너무 컸습니다.
문제는 속도가 아니었습니다. 문제는 무게였습니다.
설명(description) 데이터만으로 전체 응답의 80%를 차지하고 있었습니다. 리스트 엔드포인트가 모든 항목에 대해 전체 Markdown 설명을 반환하고 있었던 것입니다.
이는 흔히 빠지는 함정입니다. 리스트 엔드포인트가 상세 정보(detail) 엔드포인트가 되어버린 것입니다. 페이지에서 리스트가 필요할 때마다, 사용하지도 않는 데이터에 대한 비용을 지불하고 있었습니다.
UI 사용 방식이 페이로드를 결정하는 것이 아닙니다. loader의 반환 형태(return shape)가 결정합니다.
loader가 전체 객체를 반환하면, SvelteKit은 해당 객체 전체를 route data로 직렬화합니다. 특정 필드를 화면에 렌더링하지 않더라도, 그 데이터는 여전히 네트워크를 통해 전송됩니다.
저는 요약 데이터(summary data)와 상세 데이터(detail data)를 분리하여 이 문제를 해결했습니다.
두 가지 서로 다른 컨트랙트(contract)를 만들었습니다:
• 요약을 위한 리스트 컨트랙트 (ID, title, status, dates) • 전체 정보를 위한 상세 컨트랙트 (descriptions, commands)
API를 두 개의 엔드포인트로 나누었습니다:
- GET /features (요약 반환)
- GET /features/:id (상세 정보 하나 반환)
프론트엔드 작동 방식도 변경했습니다. 이제 앱은 요약 데이터를 사용하여 리스트를 즉시 렌더링합니다. 티켓을 클릭하면 앱은 셸(shell)을 먼저 보여주고, 백그라운드에서 무거운 상세 데이터를 가져옵니다.
결과는 엄청났습니다:
• Feature list API: 89.6% 감소 • Tickets route data: 91.4% 감소 • OpenSpec docs API: 98.9% 감소
데이터베이스는 항상 빨랐습니다. 진짜 병목 현상은 API와 페이지 사이의 데이터 컨트랙트였습니다.
리스트 중심 앱을 위한 교훈:
- 응답 시간뿐만 아니라 응답 크기도 측정하세요.
- 목록(list)과 상세(detail) 페이로드를 별개의 것으로 취급하세요.
- 목록 뷰에서 큰 텍스트 필드를 반환하지 마세요.
- SSR 로더가 실제로 무엇을 직렬화하고 있는지 확인하세요.
- 상세 데이터는 지연 로딩(lazy-load)하되, UI 셸(shell)은 즉시 보여주세요.
- 무거운 페이로드를 숨기기 위해 로딩 스피너를 사용하지 마세요.
빠른 쿼리가 빠른 페이지를 보장하지는 않습니다.
출처: https://dev.to/ahikmah/my-api-responded-in-4-ms-but-navigation-still-felt-slow-1hk8