728x90
728x90

[URL github 링크]

모 회사에서 링크 단축 서비스를 구현하라는 코딩 테스트 과제를 받았습니다.

 

DB 구성

확장성을 고려하고 사용자가 많아 질 것을 고려하라는 단서를 주었고

확장성을 고려해 postgresql를 선택, 사용자가 많아질 것을 고려해 응답을 빠르게 할 수 있는 redis를 선택했습니다.

 

단축 알고리즘

검색 해보니 URL을 알파벳 대문자 + 소문자 + 숫자로 된 62진법으로 변환하는 것이 대중적이였습니다.

보안성과 효율성을 고려해야 하므로 URL에 Hash 알고리즘에 salt를 적용하기로 했습니다.

def base62_encode(num: int) -> str:
    base62_chars = string.ascii_letters + string.digits
    if num == 0:
        return base62_chars[0]
    base62 = []
    while num:
        num, rem = divmod(num, 62)
        base62.append(base62_chars[rem])
    return ''.join(reversed(base62))


def generate_short_key(url: str) -> str:
    return base62_encode(int(hashlib.sha256((url + os.urandom(16).hex()).encode()).hexdigest()[:12], 16))

 

URL에 길이 16의 랜덤 문자를 적용하여 Hash 값의 맨 앞 12자리만 사용하기로 했습니다.

 

URL 표준화

과제 요청사항에는 없었지만 https://www.youtube.com/https://www.youtube.com 또는

http://www.youtube.comwww.youtube.com가 같은 단축 값을 return 해야 한다고 생각해 URL 표준화를 하기로 했습니다.

def standardize_url(url: str) -> str:
    if not urlparse(url).scheme:
        url = 'http://' + url
    parsed_url = urlparse(url)
    netloc = parsed_url.netloc.replace('www.', '') if parsed_url.netloc.startswith('www.') else parsed_url.netloc
    path = parsed_url.path or '/'
    standardized_url = urlunparse((parsed_url.scheme, netloc, path, '', parsed_url.query, ''))
    return standardized_url.rstrip('/')

 

스케쥴러

추가 구현으로 각 단축 URL에 유효 기간을 기록 후 유효 기간이 지난  데이터는 삭제를 하라고 했습니다.

유효 기간이 지난 후 데이터 삭제를 위해 pg_cron을 써도 되지만 python 스케쥴러를 사용하고 싶었습니다.

Python 스케줄러는 Python 코드 내에서 직접 실행할 수 있어, FashAPI로 구현된 URL 단축 서비스 Python 환경에서 쉽게 관리할 수 있기 때문입니다.

def delete_expired_urls():
    db = SessionLocal()
    try:
        now = datetime.now()
        db.query(models.URL).filter(models.URL.expires_at <= now).delete()
        db.commit()
        print(f"Expired URLs deleted at {now}")
    except Exception as e:
        print(f"An error occurred while deleting expired URLs: {e}")
    finally:
        db.close()

 

브라우저 캐싱

URL 조회수 기능을 구현했지만 브라우저에서 동일한 short_url을 반복적으로 요청할 경우, 브라우저의 캐싱된 정보로 인해 서버를 거치지 않아 count 값이 증가하지 않습니다. 이를 해결하기 위해 브라우저나 캐시 서버가 응답을 캐시하지 않도록 지시했습니다.

response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
response.headers["Pragma"] = "no-cache"

 

조언은 언제나 환영입니다.

728x90
728x90