이름에서 알 수 있듯이 말그대로 url을 줄이는 기술을 말한다.
shorten URL을 구현하기 위해서는 URL을 줄여야 한다.
순서는 대략 이렇다.
1. 클라이언트가 줄이려고 하는 URL을 입력한다.
2. 입력한 URL을 줄이는 로직을 수행한다.
3. 줄인 URL을 클라이언트에게 Response
4. 유저가 줄인 URL로 접속 시 원본(Original) URL로 Redirect 하게 해준다.
여기서 URL을 줄이기 위해 어떤 로직을 사용해야 할까?
해시 알고리즘을 사용해 Key에는 해시화 된 값, Value에는 기존(Original) URL을 입력하면 어떨까 생각했다.
하지만 위 사진에도 나오듯이 url이 오히려 길어지기 때문에 제외하게 되었다.
중복될 확률이 적은 UUID 식별자를 생성 후 위와 같이 기존 URL과 uuid를 같이 DB에 저장하면 어떨까?
위 사진의 UUID를 보면 알 수 있듯이 상당히 길다.
결국에 ASCII 문자로 구성되어있는 URL을 줄이는 방법이 떠오르지 않았다.
그래서 새로운 식별자를 찾고 식별자를 인코딩 해서 줄이는 방법을 결정하게 되었다.
그러면 어떤 식별자를 사용 하는게 적절할까?
Auto Increament하는 PK값을 고려한 이유는 아래와 같습니다.
가장 큰 이유는 복잡한 로직을 생각하지 않고 고유한 값을 가질 수 있다는 장점이였습니다.
여기서 고려해야 할 점은 데이터가 쌓이면 쌓일수록 URL이 길어진다는 점이다.
그래서 이런 문제를 해결하기 위해 PK 값을 인코딩하는 방식을 사용하기로 했다.
여기서 우리는 컴퓨터 기초를 배울 때 한번쯤 본 '2진수보다 16진수가 더 짧고 가독성이 좋다.'
라는 내용을 봤을 것이다.
PK 값을 10진수로 표현이 되어 있는데 이것을 64진수로 표현하면 더욱 짧아질 것이다.
*출처 : 초보 개발자 URL Shortener 서버 만들기 1편 : Base62와 춤을
베이스64 인코딩은 3바이트 데이터를 4문자로 표현한다. 3바이트 데이터의 24비트를 네 가지 6비트 덩어리로 나누고, 각 덩어리의 6비트값에 출력 가능한 문자를 할당해 표현한다.
예전에는 네트워크 통신으로 6bit를 요구했다고 한다.
그 이유는 각 기기별로 특수문자, 제어문자의 처리방식이 달라서 위와 같은 base64를 만들어 알파벳과 일정의 문자를 사용 하도록 처리 한 것 같습니다.
(형식이 정해지면 각 기기별 혹은 나라별로 인코딩과 디코딩의 편의성이 생긴다.)
위와 같은 형식으로 6bit씩 4개의 청크로 분리를 합니다.
*자세한 내용은 참고자료란에 있는 링크에 있습니다.
결국에 base64 즉 64진법을 사용해서 PK값을 더욱 짧게 만들 것입니다.
그런데 여기서 문제가 한 개 있습니다.
base64 표를 보면 알 수 있듯이 62, 63번째 인덱스에 있는 특수문자는 URL 양식에서 사용하는 문자이기 때문에 제외해야 합니다.
그리고 이미 구현되어 있는 base64 함수를 사용하면 '=' 문자인 패딩 값이 붙기 때문에 안됩니다.
그래서 BASE62라는 방법을 사용해야 합니다.
위 표에서 보면 알 수 있듯이 URL 예약어에 걸리지 않는 문자 혹은 숫자들만으로 이루어져 있습니다.
base62는 위와 같은 62자로 이루어진 인코딩 체계를 말합니다.
아래는 Java로 구현한 base62 코드입니다.
public class Base62 {
private static final String BASE62_CHARS =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public String encode(long number) {
if(number == 0) {
return Character.toString(BASE62_CHARS.charAt(0));
}
StringBuilder sb = new StringBuilder();
while (number > 0) {
int remainder = (int) (number % 62);
sb.append(BASE62_CHARS.charAt(remainder));
number /= 62;
}
return sb.reverse().toString();
}
public long decode(String str) {
long result = 0;
for (int i = 0; i < str.length(); i++) {
result = result * 62 + BASE62_CHARS.indexOf(str.charAt(i));
}
return result;
}
}
2150000
값이 입력되었다고 하면 4iUi
String 값으로 인코딩해서 리턴 해줍니다.
반대로 인코딩된 값을 입력 하면 PK값으로 디코딩해서 리턴해줍니다.
플로우와 DB 구조 상당히 심플하게 되어있다.
구현에 사용된 기술 스택은
이다.
소스코드 보러가기 => 깃 레포로 이동
긴 URL을 저장 하고 나서 PK값을 조회하고, base62로 변환해서 update해야 하는 로직이다.
위 로직은 3번의 과정을 거치는 번거로움을 보이고 있다.
고유 값을 DB가 생성하도록 하는 장점을 취하지만 성능적인 요소를 많이 포기한 방법이다.
상당히 라이트하게 구현했기 때문에 서버에서 직접 redirect를 하는 방식을 적용했습니다.
현재 HttpServletResponse를 사용해서 redirect를 하는데,
서블릿 컨테이너가 추가 작업을 수행하게 되기 때문에 트래픽이 많아지면 부하가 높아질 거라고 생각합니다.
PK값을 단순히 base62로 인코딩 한 것이기때문에 쉽게 PK값을 알 수 있습니다.
그리고 악의적인 사용자가 무작위 URL을 입력해서 기존 URL을 접근 할 수 있습니다.
일정한 패턴으로 값이 저장되고 인코딩 되어 있기 때문에 쉽게 접근이 가능하다는 문제가 있습니다.
이 외에도 상당히 많은 부분에서 부족한 것들이 있습니다.
하지만 배운 것들은 아래와 같습니다.