API Sorting 전략 (성능 부하, 유연성, 설계 원칙)
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
정렬 기능 하나 잘못 설계했다가 쿼리 응답 시간이 3초를 넘어버린 경험이 있습니다. 그때 처음으로 API Sorting이 단순한 편의 기능이 아니라는 걸 뼈저리게 깨달았습니다. 최신순, 인기순, 가격순처럼 사용자 입장에서는 당연한 기능이지만, 서버 입장에서는 꽤 까다로운 처리가 뒤따릅니다. 이 글은 그 경험에서 출발해, 실제 설계에서 부딪힌 문제와 해결 방향을 솔직하게 풀어봅니다.
정렬 요청 하나가 서버를 흔든 날
처음 API에 Sorting 기능을 붙였을 때는 꽤 뿌듯했습니다. 쿼리 끝에 ORDER BY 한 줄만 추가하면 됐고, 기능도 바로 동작했습니다. 그런데 데이터가 수십만 건으로 불어나면서 상황이 달라졌습니다. 특정 정렬 요청이 들어올 때마다 응답이 눈에 띄게 느려졌고, 결국 모니터링 도구에서 슬로우 쿼리 경고가 떴습니다.
문제는 풀 테이블 스캔(Full Table Scan)이었습니다. 풀 테이블 스캔이란 데이터베이스가 정렬 기준이 되는 컬럼에 인덱스가 없을 때, 테이블의 모든 행을 처음부터 끝까지 읽어야 하는 상황을 말합니다. 인덱스(Index)란 책의 목차처럼 특정 컬럼에 대한 탐색 경로를 미리 만들어둔 구조인데, 이게 없으면 데이터베이스는 전체 데이터를 뒤져야 합니다. 제가 직접 실행 계획(EXPLAIN)을 돌려봤을 때, 정렬 대상 컬럼에 인덱스가 아예 없다는 걸 그 순간에야 확인했습니다. 부끄럽지만 처음 설계할 때 인덱스를 전혀 고려하지 않았던 겁니다.
더 곤란했던 건 다중 필드 정렬이었습니다. 사용자가 "가격 낮은 순 + 최신 등록순" 같이 두 가지 조건을 동시에 요청하면 쿼리 복잡도가 크게 올라갑니다. 실행 계획이 예측과 다르게 흘러가면서 인덱스를 타지 않는 경우도 생겼습니다. 솔직히 이건 예상 밖이었습니다. 단순히 정렬 조건 두 개를 묶었을 뿐인데 성능이 이렇게까지 달라질 줄은 몰랐습니다.
이 사건 이후로 저는 정렬 기능을 추가할 때 반드시 인덱스 설계부터 함께 논의하는 습관이 생겼습니다. Use The Index, Luke라는 사이트가 이때 많이 도움이 됐습니다. 인덱스 동작 원리를 쿼리 흐름과 연결해서 설명해주는 곳인데, 제 경험상 이 수준의 이해가 없으면 Sorting 설계에서 반드시 한 번은 넘어지게 됩니다.
유연성과 통제, 둘 다 포기할 수 없는 이유
API Sorting의 매력은 유연성입니다. 클라이언트가 원하는 기준으로 데이터를 요청하면 서버가 이미 정렬된 결과를 돌려줍니다. 클라이언트 측 정렬(Client-side Sorting)이란 서버에서 전체 데이터를 받은 뒤 브라우저나 앱에서 직접 순서를 바꾸는 방식인데, 이 경우 불필요한 데이터 전송이 발생하고 대용량 환경에서는 성능 저하로 이어집니다. 서버에서 정렬을 처리하면 이 문제를 깔끔하게 해결할 수 있습니다.
그런데 문제는 허용 범위를 어디까지 열어두느냐입니다. 이론적으로는 모든 컬럼에 대해 정렬을 허용할 수 있지만, 제 경험상 이건 꽤 위험한 선택입니다. 어떤 클라이언트가 인덱스가 없는 컬럼에 대해 정렬을 요청하면 그 순간 전체 시스템 응답이 느려질 수 있습니다. 실제로 한 프로젝트에서 정렬 필드를 무제한으로 열어뒀다가, 특정 요청이 DB 커넥션 풀(Connection Pool)을 잠식해버린 일이 있었습니다. 커넥션 풀이란 데이터베이스 연결 자원을 미리 확보해두는 공간으로, 이게 꽉 차면 다른 요청들이 줄줄이 대기 상태로 빠집니다.
그래서 저는 허용 가능한 정렬 필드를 명시적으로 화이트리스트 방식으로 관리하는 것이 맞다고 봅니다. 화이트리스트(Whitelist)란 허용할 항목을 미리 목록으로 정해두고, 그 외의 요청은 차단하는 방식입니다. 클라이언트에게 유연성을 주되, 서버가 감당할 수 있는 범위 안에서만 허용하는 것입니다. "모든 걸 다 지원한다"는 건 API 설계에서 꽤 무서운 말이라는 걸 직접 겪어보니 알게 됐습니다.
참고로 Microsoft Azure API 설계 모범 사례에서도 정렬 파라미터를 허용할 때 서버가 지원하는 필드 범위를 명확히 문서화하고 제한하도록 권고하고 있습니다. 이건 저도 실제로 공감하는 방향입니다.
실전에서 통하는 Sorting 설계 원칙
여러 번 시행착오를 겪고 나서 지금은 몇 가지 원칙을 갖고 있습니다. 이걸 처음부터 알았더라면 꽤 많은 시간을 아낄 수 있었을 텐데, 하는 아쉬움이 있어서 가능한 구체적으로 적어보겠습니다.
- 정렬 허용 필드를 사전에 확정하고 인덱스를 함께 설계한다. 기능 구현보다 인덱스 설계가 먼저입니다.
- 페이지네이션(Pagination)과 반드시 결합한다. 페이지네이션이란 전체 데이터를 한 번에 반환하지 않고 일정 단위로 나눠 제공하는 방식입니다. Sorting과 Pagination을 함께 쓰면 정렬 대상 데이터 양 자체를 줄일 수 있어 성능 부담이 크게 낮아집니다.
- 정렬 기준 조합이 복잡해질수록 실행 계획(Execution Plan)을 직접 확인한다. 실행 계획이란 데이터베이스가 쿼리를 처리하는 방법을 미리 보여주는 분석 도구로, 인덱스가 제대로 활용되고 있는지 눈으로 확인할 수 있습니다.
- 기본 정렬 값(Default Sort)을 반드시 설정해둔다. 클라이언트가 정렬 파라미터를 넘기지 않았을 때 서버가 임의의 순서로 데이터를 반환하면 결과 일관성이 깨집니다.
- 복잡한 맞춤 정렬은 클라이언트에 위임하는 것도 선택지다. 서버에서 표준 정렬을 지원하고, 그 이상의 조건은 클라이언트가 받아서 처리하게 하면 API 복잡도를 낮출 수 있습니다.
이 중에서 제 경험상 가장 자주 놓치는 게 네 번째, 기본 정렬 값 설정입니다. 작은 것처럼 보이지만 페이지네이션과 맞물리면 데이터 중복이나 누락이 생기는 원인이 됩니다. 실제로 한 번 이 문제로 클라이언트 팀과 꽤 오래 원인을 추적한 적이 있습니다.
API Sorting은 결국 사용자 경험과 시스템 안정성 사이에서 줄을 타는 설계입니다. 너무 닫아두면 클라이언트가 불편하고, 너무 열어두면 서버가 흔들립니다. 제 결론은 "자주 쓰이는 것만 잘 지원하자"입니다. 모든 경우를 커버하려다 아무것도 제대로 못 하는 것보다, 실제 사용 패턴 기반으로 핵심 정렬 기준을 골라 안정적으로 제공하는 편이 훨씬 낫습니다. 처음 설계할 때 사용 로그나 기획 단계의 사용자 시나리오를 꼼꼼히 보는 습관, 저는 이게 API Sorting 설계의 출발점이라고 생각합니다.
관련 글
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기