//
요즘 개발자를 위한 시스템 설계 수업 · 디렌드라 신하, 테자스 초프라
3부의 후반부는 실제 서비스를 설계하는 장들로 이어진다. 여기서부터 책은 하나의 템플릿을 반복 적용한다.
이 절차 자체가 이 책에서 가장 실무에 옮겨 쓰기 좋은 산출물이다. 특히 마지막 단계가 있다는 점이 중요하다. 설계를 끝낸 뒤 처음에 정의한 요구 사항을 다시 꺼내 하나씩 대조하는 습관이 설계 문서의 품질을 결정한다.
책은 설계에 들어가기 전에 "왜 이 서비스가 필요한가"를 먼저 나열한다.
| 사례 | 이유 |
|---|---|
| 소셜 미디어 공유 | 글자 수 제한, 추적 가능한 링크 |
| 제휴 마케팅 | 클릭률 추적, 깔끔한 표현 |
| 이메일 마케팅 | 사용자 경험 개선, 참여도 추적 |
| QR 코드 | 짧은 URL은 스캔이 더 쉽고 빠르다, 스캔 횟수 추적 |
| 인쇄 매체 및 오프라인 마케팅 | 기억하기 쉬운 링크, 성과 추적 |
| 내부 공유 링크 | 자료 공유 효율, 활용도 추적 |
| 모바일 애플리케이션 | 레이아웃 영향 최소화, 동적 링크로 앱 스토어 이동 |
| 커스터마이징 및 브랜딩 | 자체 도메인으로 브랜드 이미지 강화, 채널 간 일관성 |
목록을 보면 반복되는 단어가 있다. 추적이다. URL 단축의 표면적 목적은 링크를 짧게 만드는 것이지만, 실제 수요의 절반 이상은 클릭을 계측하려는 것이다. 이 관찰이 설계에 영향을 준다. 리다이렉트 경로에 분석 이벤트를 남기는 것이 부가 기능이 아니라 핵심 기능이 된다.
기능적 요구 사항 (핵심)
기능적 요구 사항 (고려 사항)
긴 URL 유효성 검사, 맞춤형 URL 생성 지원, 만료 기한 설정, 오류 상황에서의 정상 동작, 생성자의 긴 URL 수정, URL 매핑 삭제, 활용도 분석 및 모니터링, 사용자 계정 관리.
비기능적 요구 사항
| 요구 사항 | 내용 |
|---|---|
| 가용성 | 시스템이 항상 안정적으로 동작한다 |
| 확장성 | 1억 명 이상 지원, 갑작스러운 요청 증가에 대응한다 |
| 응답 속도 | 읽기와 쓰기 모두 지연 없이 처리한다 |
| 일관성 | 아래 세 조건을 만족한다 |
| 내구성 | 생성된 단축 URL 데이터가 손실되지 않는다 |
| 신뢰성 | 장애나 요청 폭주에도 정상 동작하며 요구 사항을 충족한다 |
일관성 조건이 구체적으로 명시된 것이 좋았다.
핵심 기능을 위한 API는 두 개다.
POST /shorturl
긴 URL을 입력받아 새로운 단축 URL을 생성한다
GET /shorturls/{shorturl}
단축 URL에 해당하는 원본 URL을 반환한다
설계 전에 대략적인 규모를 산정한다. 기준 항목은 다음과 같다.
이 계산이 왜 필요한지는 직접 해 보면 드러난다. 영문 대소문자와 숫자를 쓰면 문자 종류는 62개다. 길이가 n일 때 표현 가능한 조합은 62의 n승이다.
| 길이 | 조합 수 |
|---|---|
| 6자 | 약 568억 |
| 7자 | 약 3.5조 |
| 8자 | 약 218조 |
하루에 100만 개씩 생성한다고 가정하면 연간 3.65억 개다. 7자면 수천 년을 써도 남는다. 길이를 7자로 정하는 근거가 이 계산에서 나온다. 규모 산정은 감으로 정한 숫자에 근거를 붙이는 단계다.
면접이나 설계 리뷰에서 이 계산을 요구하는 이유는 정확한 숫자를 얻으려는 것이 아니다. 자릿수를 틀리지 않는지 보는 것이다. "하루 100만 건이면 초당 몇 건인가"를 즉시 답할 수 있어야 한다. 하루는 약 86,400초이므로 대략 초당 12건이다. 이 감각이 없으면 필요 없는 곳에 분산 시스템을 도입하거나, 반대로 감당 못 할 부하를 단일 서버에 맡기는 판단을 하게 된다.
이 장의 핵심이다. 책은 네 가지 방법을 순서대로 검토하며 각각의 문제를 지적한다.
| 방법 | 문제 |
|---|---|
| 1. 무작위로 URL 생성 | 중복 확인을 위해 최소 한 번의 읽기 작업이 발생해 비효율적 |
| 2. MD5 해시 | 해시의 일부 비트만 사용하므로 중복 가능성이 있다 |
| 3. 카운터 방식 | 단일 서버에서 카운터를 관리하면 장애 대응성이 떨어진다 |
| 4. 분산 카운터 방식 | 가장 적절한 방법으로 판단 |
분산 카운터 방식은 이렇게 동작한다. 요청이 들어오면 범위 값(예: 1~10000, 10001~20000)을 서버에 할당하고, 각 서버는 자기 범위 안에서만 카운터를 증가시킨다. 서버 간 조율이 범위를 할당받는 순간에만 필요하므로, 실제 URL 생성 시에는 어떤 조율도 없다.
앞의 세 방법이 각각 무엇에서 실패했는지 보면 네 번째의 설계 근거가 보인다.
방법 4는 스노플레이크(Snowflake) 계열 ID 생성기와 같은 아이디어다. 다만 카운터를 순차 증가시키면 단축 URL이 예측 가능해진다는 문제가 남는다. 남의 링크를 순서대로 훑어볼 수 있게 되므로, 실제 서비스에서는 카운터 값을 그대로 인코딩하지 않고 가역적인 치환을 한 번 거치는 편이 안전하다.
주요 기능이 단축 URL과 긴 URL을 매핑하는 것이므로 데이터는 키-값 쌍이다. 이 목적에 부합하는 것으로 레디스를 선택한다.
고수준 아키텍처의 구성 요소는 다음과 같다.
쓰기 흐름과 읽기 흐름이 서로 다른 경로를 타는 것이 핵심이다. 쓰기는 카운터에서 값을 받아 저장소에 기록하고, 읽기는 캐시를 먼저 확인한 뒤 없으면 저장소를 조회한다. URL 단축 서비스는 읽기가 쓰기보다 압도적으로 많은 워크로드이므로 이 비대칭이 설계에 그대로 반영된다.
마지막으로 앞서 정의한 기능적/비기능적 요구 사항을 현재까지 설계한 내용과 대조해 검토한다.
10장은 위치 기반 서비스를 설계한다. 위치 기반 마케팅, 스마트 교통 시스템, 주변 식당이나 가게 찾기처럼 사용자 위치 데이터를 활용해 경험을 개선하고 운영 효율을 높이는 서비스다.
절차는 URL 단축 서비스와 동일하다. 활용 사례 → API 설계 → 규모 파악 → 시스템 설계 → 요구 사항 검토. 그래서 이 장에서 새로 배울 것은 절차가 아니라 문제의 성격이 달라졌을 때 어떤 기술이 등장하는가다.
URL 단축 서비스에서는 저장 공간과 RPS를 계산했지만, 근접 서비스에서는 DAU(Daily Active Users)와 QPS(Queries Per Second) 단위를 사용한다. 사용자가 이동하면서 지속적으로 위치를 갱신하고 주변을 조회하므로, 사용자 수와 조회 빈도가 규모를 지배한다.
책은 세 가지 방법을 검토한다.
방법 1: 위치 값으로 쿼리문 작성하기
위도와 경도 범위로 SQL의 WHERE 절을 작성하는 가장 단순한 방식이다. 문제는 두 개의 독립적인 범위 조건이 되어 인덱스 효율이 떨어진다는 점이다. 위도 인덱스로 걸러낸 결과가 너무 넓고, 경도 조건은 그 안에서 순차 검사해야 한다.
방법 2: 쿼드 트리(Quad Tree) 사용하기
공간을 재귀적으로 4등분해 트리로 만드는 방식이다. 각 노드가 담을 수 있는 대상 수의 상한을 정해 두고, 초과하면 그 영역을 다시 4등분한다.
+--------+--------+ 밀집 지역은 더 깊이 분할된다
| | ++ ++ |
| | ++ ++ | 도심: 트리 깊이가 깊음
| +--------+ 교외: 트리 깊이가 얕음
| | |
| | |
+--------+--------+
밀도에 따라 분할 깊이가 자동으로 달라지는 것이 장점이다. 도심처럼 대상이 몰린 곳은 촘촘하게, 한적한 곳은 느슨하게 나뉜다.
방법 3: 지오해시(Geohash) 이용하기
2차원 좌표를 문자열로 인코딩하는 방식이다. 위도와 경도를 번갈아 이분하며 비트를 쌓고, 그 비트열을 문자로 변환한다.
지오해시 문자열이 길어질수록 좁은 영역을 가리킨다
wydm → 약 39km × 20km
wydm9 → 약 5km × 5km
wydm9q → 약 1.2km × 0.6km
wydm9qh → 약 153m × 153m
지오해시의 결정적 장점은 접두사가 같으면 지리적으로 가깝다는 성질이다. 그래서 2차원 근접 검색이 문자열 접두사 검색으로 바뀐다. 일반 데이터베이스의 B-트리 인덱스나 키-값 저장소로도 근접 검색을 구현할 수 있게 된다는 뜻이다.
지오해시에는 잘 알려진 함정이 있다. 접두사가 같으면 가깝지만, 가깝다고 접두사가 같지는 않다. 그리드 경계를 사이에 두고 마주 보는 두 지점은 물리적으로 몇 미터 떨어져 있어도 지오해시 문자열이 완전히 다를 수 있다. 그래서 실제 구현에서는 대상 셀 하나만 조회하지 않고 인접한 8개 셀을 함께 조회한 뒤 실제 거리로 필터링한다.
세 방법을 정리하면 이렇다.
| 방법 | 강점 | 약점 |
|---|---|---|
| 위치 값 쿼리 | 구현이 가장 단순 | 인덱스 효율이 낮아 규모 확장 시 실패 |
| 쿼드 트리 | 밀도에 적응, 정확한 범위 검색 | 트리를 메모리에 유지해야 하고 갱신 비용이 있다 |
| 지오해시 | 문자열 인덱스로 처리 가능, 분산 저장에 유리 | 경계 문제, 셀 크기가 고정 단계로만 조절됨 |
두 장은 같은 설계 템플릿을 서로 다른 문제에 적용한다. 그래서 절차의 재사용성은 확인되지만, 두 번째 장부터는 새로 얻는 것이 줄어든다. 근접 서비스 장은 상대적으로 가볍게 읽었다.
그럼에도 건질 만한 것이 있었다. 서비스 유형이 달라지면 규모 산정의 단위도 달라지고 핵심 기술도 달라진다는 점이다. URL 단축 서비스에서는 "고유한 짧은 문자열을 어떻게 충돌 없이 대량 발급하는가"가 핵심 문제였고, 근접 서비스에서는 "2차원 공간 검색을 어떻게 1차원 인덱스로 환원하는가"가 핵심 문제였다.
특히 URL 단축 서비스의 네 가지 생성 방법 비교는 설계 논증의 좋은 예다. 각 방법이 무엇에서 실패했는지 명시하고 그 실패를 회피하는 방법으로 넘어가는 구조라, 최종 선택의 근거가 분명하게 남는다. 설계 문서를 쓸 때 결론만 적지 않고 검토했다가 버린 대안을 함께 적어 두어야 하는 이유가 여기 있다.
이 글이 도움이 되셨나요?