카카오스타일 전시팀이 몇 년간 운영해온 Server Driven UI의 호출 구조를 단일 쿼리에서 컴포넌트별 분리 호출로 바꾼 기록이다. 화면 하나를 그리는 데 필요한 모든 데이터를 GraphQL 쿼리 하나로 받던 구조가 지연과 단일 장애점을 만들었다. 쿼리를 쪼개는 방식으로 ATF 로딩 시간을 670ms에서 10ms 수준으로 줄였다.
핵심 포인트- 기존 단일 쿼리는 가장 느린 데이터에 전체 응답 시간이 맞춰지는 구조였다.
- 예외 처리를 제대로 하지 않으면 컴포넌트 하나의 오류가 SDUI 쿼리 전체 실패로 번졌다.
- 새 구조는 template 쿼리로 컴포넌트의 type과 id만 먼저 받고, 앱이 스켈레톤을 그린 뒤 타입별 API를 개별 호출한다.
- template 쿼리는 저장된 값을 그대로 반환해 비즈니스 로직이 없으므로 P90 기준 4.5~5ms로 응답한다.
- ATF 영역 로딩 시간이 670ms에서 10ms로 줄었다.
- 라운드 트립을 줄인다는 GraphQL의 장점이 이 경우에는 오히려 지연과 SPOF를 만들었다는 것이 결론이다.
상세 정리- SDUI를 쓰는 이유: 서버가 클라이언트 UI를 동적으로 제어하면 빌드와 배포 없이 UI를 바꿀 수 있고 A/B 테스트도 효율적으로 돌릴 수 있다.
- 문제 1 컴포넌트 간 의존성: 모든 컴포넌트의 데이터가 준비돼야 앱이 데이터를 받을 수 있어 사용자 체감 속도가 가장 느린 데이터에 종속됐다.
- 문제 2 오류 처리: 하나의 쿼리에 다양한 컴포넌트가 섞여 있어 오류가 나면 어느 부분이 문제인지 빠르게 짚기 어려웠다.
- 문제 3 확장성: 새 컴포넌트가 추가될 때마다 단일 쿼리를 손봐야 했다.
- 문제 4 테스트: 특정 컴포넌트만 독립적으로 테스트하기 어렵고 일부 변경이 전체에 영향을 줘 안정적인 테스트가 힘들었다.
- 새 스키마: Query에 page_id를 받는 template 필드를 두고 Component 타입은 id와 ComponentType만 갖는다. 타입 예시는 BANNER, MENU, ITEM이다.
- 서버 구현: page_id로 저장소를 조회해 Component 목록으로 매핑해 반환하는 것이 전부다. 별다른 비즈니스 로직이 없어 응답이 빠르다.
- id의 용도: 나중에 type에 매핑되는 API를 호출할 때 노출할 데이터를 매핑하는 데 쓴다.
- type의 용도: 컴포넌트의 스켈레톤을 만들고 어떤 API를 호출할지 정하는 데 쓴다.
- 클라이언트 흐름: template 응답을 받아 타입별 스켈레톤을 먼저 그리고 각 컴포넌트가 자기 API를 지연 로딩한다.
- 성능 비교: 기존 screen 쿼리는 P90 기준 670~680ms였고, 새 구조에서는 template이 4.5~5ms에 상단 두 컴포넌트가 각각 4.9ms, 2.8ms였다.
- 추가 최적화 여지: template 쿼리와 상단 컴포넌트를 한 번에 요청하면 이론상 5ms까지 줄일 수 있지만 오류 처리가 복잡해져 거기까지는 하지 않았다.
- 체감 효과: ATF 로딩 속도가 눈에 띄게 빨라져 사용자가 페이지를 훨씬 빠르게 받아보게 됐다.
- 결론의 방향: 초기 모델은 GraphQL의 통상적인 장점을 그대로 따랐지만 실제로는 레이턴시와 SPOF를 겪었고, 쿼리를 분할하는 단순한 방법으로 대부분 해소됐다.
왜 읽나GraphQL 단일 쿼리로 화면 전체를 받아오는 SDUI 구조가 느려졌을 때 템플릿과 데이터를 분리하는 최소 변경 설계를 그대로 참고할 수 있다.