Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4기 고예성] URL Shortener 과제 제출합니다. #53

Open
wants to merge 19 commits into
base: Dev-Yesung/url-shortener
Choose a base branch
from

Conversation

Dev-Yesung
Copy link
Member

@Dev-Yesung Dev-Yesung commented Oct 11, 2023

📌 과제 설명

과제 시연 동영상입니다.
https://vimeo.com/manage/videos/873085666

  • URL Shortner 서비스를 구현했습니다.
  • 프론트엔드와 백엔드 모두 구현했습니다.
  • Shortening key 알고리즘은 Base62, Short UUID, Adler Hashing을 사용했습니다.
  • 추가적인 팀 미션으로 배포를 진행했습니다.
  • 개인 미션으로 Redis를 활용한 캐싱을 구현했습니다.
  • 개인 미션으로 Docker를 이용한 배포를 진행했습니다.(도커 네트워크 활용)

Url Shortener 서비스 링크

👩‍💻 요구 사항과 구현 내용

  • URL 입력폼 제공 및 결과 출력

    리액트로 구현했습니다. (링크)

  • URL Shortening Key는 8 Character 이내로 생성

    base62 방식으로 인코딩 완료

  • 단축된 URL 요청시 원래 URL로 리다이렉트

    상태코드 301(MOVE_PERMANENTLY)

  • 단축된 URL에 대한 요청 수 정보저장

    MySQL 이외에 Redis를 활용하여 캐싱했습니다.

  • Shortening Key를 생성하는 알고리즘 2개 이상 제공하며 애플리케이션 실행중 동적으로 변경 가능

    추가적으로 8글자 이내의 UUID, Adler 알고리즘을 사용하였습니다.

Redis를 활용한 캐싱과 MySQL과의 데이터 일치전략

Redis는 서버가 다운될 것에 대비해 어느 정도 데이터를 백업해두는 기능을 갖고 있습니다.
하지만 완벽한 백업이 아니기 때문에 Redis에서 사용하는 캐싱 데이터는
다음의 조건을 만족하는 데이터에 사용하면 좋다고 생각합니다.

  1. 캐싱 했을 때의 성능(속도) 향상,
  2. 손실되어도 괜찮은 데이터

URL Shortener서버는 Redis를 두 가지 용도로 사용 중 입니다.

  1. 리다이렉션으로 보낼 원본 url을 빠르게 찾기 위해
  2. 인코딩된 url의 총 click 수를 빠르게 저장하고 조회하기 위해

인코딩된 shortening key에 매핑되는 원본 URL은 자주 변경되지 않습니다.
그래서 RDB 저장소까지 가서 읽기를 수행할 필요가 없고 캐시 저장소를 통해
빠르게 요청을 처리하면 좋을 거 같다고 생각했습니다.

클릭 수(API요청 횟수)에 관한 업데이트는 1차적으로 Redis에만 진행되도록 했습니다.
그 이유는 클릭 수는 손실되어도 타격이 큰 데이터가 아니라는 생각을 했습니다.
물론 선착순 당첨 이벤트와 같이 특수한 상황에서 클릭수의 경우 정확도가 중요하겠고
마케팅 데이터로 활용할 클릭수는 어느 정도 의미가 있겠지만,
현재는 그런 특수한 상황이 아니라 배제했습니다.

그래도 속도나 동시성을 어느 정도 고려해주는게 좋다는 생각을 해서 Redis를 활용했습니다.
Redis는 싱글 스레드 방식으로 작동하기 때문에 동시에 여러 스레드가 접근할 경우
순차적으로 요청을 처리하게 되어 데이터 정합성을 보장하고
인메모리 데이터베이스라 속도 또한 보장하기 때문입니다.

1차적으로 Redis에서 업데이트된 클릭수는 매일 새벽 3시(트래픽이 가장 적게 몰릴것 같읕 시간)에
MySQL로 데이터를 업데이트 합니다. 이때 처리하는 방법은 @scheduled(스프링 스케줄러)를 사용하였습니다. 클릭수에 관한 데이터를 다루는 방법으로, Redis 서버가 다운 될 것을 고려해
MySQL에도 클릭수를 저장할까 생각했지만, 클릭수가 크게 중요한 데이터가 아니고
서비스의 본질은 긴 URL을 줄이는 것과 빠르게 원본 URL을 찾아주는 거라 생각해 배제했습니다.

Redis에 계속해서 캐시 데이터를 두게 되면 메모리 낭비가 심할거라 생각해
최근에 클릭한 데이터들에는 만료시간을 연장하는 알고리즘을 적용할까 생각했지만,
구현할 시간이 없어 패스했습니다!

✅ PR 포인트 & 궁금한 점

============= 궁금한 점 ==============

  1. 원본 url을 찾는 트래픽이 몰리게 됐을 경우 Redis를 사용하는게 적절한지 의문에 관한 의문

Redis는 싱글 스레드로 작동해서 동시성이 제어되지만, 많은 트래픽이 몰릴 경우
멀티스레드로 동시성 제어를 하는 MySQL보다 속도가 느리지 않을까하는 생각이 듭니다.
거기다 데이터 쓰기 작업을 하지 않고 읽기 작업만 하는거라면, 데이터 정합성에도 문제가 없고
읽기/쓰기가 하나의 데이터 베이스에 몰리면 데이터 정합성과 속도에 문제가 발생할 수 있어
읽기전용 MySQL 저장소를 둬서 동시성 제어를 하는 것도 좋은 방법이라는 생각도 듭니다.

만일 클릭수가 현재 서비스와 같이 큰 의미가 없는 것이 아니라,
위에서 예시로 들었던 선착순 이벤트처럼 의미있는 경우에는 어떤 방식을 선택하는게
더 적절한지 의견이 궁금합니다!

  1. ConnectException을 AOP보다 먼저 잡는 방법

Redis나 MySQL 서버가 다운 됐을 경우 fallback이 작동하게 만들고 싶었는데 실패했습니다.
Redis나 MySQL에 연결하는 것이 실패할 경우 스프링에서 ConnectException을 던지는 것을
확인했습니다. 제 서비스 클래스나 컨트롤러 서비스에서 이 예외를 잡아
fallback 방식을 적용하려 했는데, 에러를 캐치하지 못하는 것을 발견했습니다.
구글링을 통해 아래와 같은 글을 스택 오버플로우에서 확인해서 코드에 적용해봤지만,
그래도 예외를 잡는데 실패했습니다.
스택 오버플로우 참고링크

디버깅으로 확인해보니 try-catch로 예외 클래스를 캐치하는 것보다,
이미 구현된 AOP에 의해 예외가 먼저 잡히는 것을 발견했습니다.
이러한 경우 어떻게 AOP보다 먼저 예외를 잡아서
제가 원하는 방식으로 로직이 처리되도록 만들 수 있는지 궁금합니다..!

==============

고려하지 못한 점

  1. 유효성 검증을 못했습니다🥲
  2. 테스트 코드를 못짰습니다🥲
  3. 의미없는 로깅이 많습니다
    일단 로그를 추가하고 의미있는 로그만 남기는 방식으로 바꾸려했는데,
    시간이 없었습니다...

short url을 만드는 알고리즘(DB의 index와 base62를 활용한 방식)과 RDB에 short url과 original
url의 저장 및 Redis에 캐싱하여 조회 속도를 높이는 구현을 완료했습니다.
original url은 0번 DB에 저장되고 clicks는 1번 DB에 저장됩니다.
CacheMigrationScheduler 클래스의 migrateClicksCacheDataToMasterDatabase를 통해 매일 새벽 3시에 클릭수가 mysql에 업데이트 됩니다.
기존에 base62 방식으로만 암호화하던 방식에서 short uuid, adler 알고리즘을 추가했습니다. 그리고
UrlEncoder라는 정적 팩토리 클래스를 만들어 다양한 방식으로 암호화하도록 했습니다.
http://localhost:3000으로부터 오는 요청을 모두 허가했습니다.
http:// 혹은 https://가 url에 붙어있지 않을 경우 redirect가 정상적으로 작동하지 않는 에러를 고쳤습니다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant