/shorten_url

url 단축 서비스

Primary LanguagePython

shorten url

실행

git clone https://github.com/lcw3176/shorten_url.git
cd shorten_url

docker-compose up

# 재실행 시
# docker-compose down --rmi all
# docker-compose up

문서

url 서버

http://localhost:8000/docs

counter 서버

http://localhost:8001/docs

설계

구조도

스크린샷 2024-08-11 154811

스키마

class Url(BaseModel):
    origin_url: str
    short_url: str
    clicks: int = 0
    created_at: datetime
    updated_at: Optional[datetime] = None
    expiration_date: Optional[datetime] = None

url 생성

url 설명 응답
POST :8000/shorten 짧은 url 생성 {"short_url": 단축 url} or http 400

shorten_url 서버에 요청이 오면 counter 서버에 순차적으로 증가하는 숫자 값을 받아옵니다. 받아온 count 값을 7자리의 base62 값으로 인코딩한 후, 해당 값을 단축 url 값으로 사용합니다.

오리진 url을 해싱 처리후 앞의 7자리 문자열을 사용하는 방식도 생각해 보았으나, 문자열이 중복될 수도 있고 그로 인해 DB에 exists 쿼리를 추가적으로 날려야 한다는 점이 부하를 높일 것이라 생각했습니다. 고유한 숫자값을 서버에서 할당받고 해당 값으로 단축 url 값을 생성한다면 해당 문제를 해결할 수 있을 것이라 생각합니다.

url 생성만 요청하거나, 만료기간을 같이 설정해서 요청할 수 있습니다. 스케줄러가 주기적으로 동작하여 만료된 url을 제거합니다.

url 리다이렉트

url 설명 응답
GET :8000/{short_key} 원본 URL로 리다이렉트 http 301, 404

요청한 값이 존재하면 오리진 url로 리다이렉트(301), 존재하지 않으면 404가 반환됩니다. 요청한 값의 updated_at이 최신화되고, 조회수가 증가합니다.

조회수 반환

url 설명 응답
GET :8000/stats/{short_key} url 클릭수 반환 {"clicks": 조회수 } or http 404

요청한 값의 조회수를 반환합니다.

카운터

url 설명 응답
GET :8001/count 증가하는 카운트 반환 카운트 숫자 값

값이 하나씩 증가하고, 중복이 허용되지 않는 카운터 값을 만들기 위해 어떤 시스템을 사용해야 할지 고민했습니다. 여러 가지를 고려한 끝에, 주키퍼를 사용하는 것이 적절하다고 생각했습니다.

먼저 떠올랐던 건 카프카와 같은 메세지 큐 시스템이었습니다. 카프카는 분산 환경에서 대규모 데이터 스트림을 처리하는데 매우 적합하다고 알려져 있고, 메시지를 파티션 단위로 나누어 처리할 수 있습니다.

단일 컨슈머에서 카운트 값을 관리하며 DB에 값을 넣으려 하였으나, 만약 서버가 증설될 경우 각 파티션에 할당된 컨슈머들이 독립적으로 카운터 값을 계산하게 됩니다. 이렇게 되면 컨슈머마다 다른 카운터 값을 가지게 되며, 이를 동기화하고 일관성을 유지하는 데 상당한 어려움이 따를 것이라 생각했습니다.

반면에, 주키퍼는 분산 환경에서 데이터의 일관성과 동기화를 유지하는 데 특화된 시스템이라고 들었고, 자동으로 숫자가 증가하는 znode 생성 기능을 지원했습니다. 이 방법을 통해 카운터 범위를 동적으로 할당하고, 각 서버가 자신의 범위 내에서 카운터 값을 증가시키는 구조를 가진다면 값이 중복되지 않도록 보장할 수 있으며 일관된 순차적 증가 값을 가질 수 있을 것이고 향후 서버 증설과 같은 확장성에도 용이하다고 생각했습니다.

# 개선 전
# count = 1
1000000

# count = 2
2000000


# 개선 후
# count + int(time.time() * 1000)
# count = 15402, time = 1723448809093
'uldJD7p'

# count = 15403, time = 1723448902076
'uldK1j9'

하지만 순차적으로 증가하는 카운트 값을 가지고 url 값을 만들어보니 값의 유추가 쉽다는 단점이 있었습니다. 이러한 문제를 해결하기 위해 순차적으로 증가하는 count 값에 현재 시간을 ms 단위로 얻어와서 서로 더해준 후, 이를 base62 인코딩 해준 결과 전보다는 더 다양한 결과값이 도출되었습니다.

이 시스템에서 중복된 값이 생성되려면 0.001초만에 다음 요청이 들어와서 시간 계산까지 처리되는 케이스뿐이고, 이는 현실적으로 극히 드문 현상이라고 생각합니다.

db

데이터끼리 연관 관계가 많이 생성되는 구조가 아니라고 생각했습니다. 단축 url을 생성하는 서비스의 특성은 간편한 사용성이라고 생각했고, 유저 정보를 입력받는 순간 사용성에 큰 허들이 될 것이라 생각했습니다.

url 생성 기능에 집중한다면 테이블의 구조는 단순할 것이라고 판단했고, 분산 DB와 같은 확장성이나 전반적인 성능을 고려했을 때 RDBMS보다는 NoSQL이 적절할 것이라 생각했습니다.

클릭 수와 같은 통계를 보기 위해서는 휘발성인 Redis보다는 디스크에 기록되는 MongoDB가 나을 것이라고 판단했습니다.