이번 단축 url 서비스 토이 프로젝트를 진행함에 있어서 나는 단축된 url로 제공할 임의의 문자열이 필요했다.
그래서 초기에 나는 random 값을 사용하여 ASCII CODE에서 무작위로 문자, 숫자를 불러오자! 라고 생각하여
아래와 같이 코드를 작성했었다.
public void makeKey() {
randomKey = levelingKey(random.ints(AsciiCodeIndexForRandomString.NUMBER_START_IN_ASCII_CODE.index, AsciiCodeIndexForRandomString.LOWER_ALPHABET_LIMIT_IN_ASCII_CODE.index)
.filter(i -> (i <= AsciiCodeIndexForRandomString.NUMBER_LIMIT_IN_ASCII_CODE.index || i >= AsciiCodeIndexForRandomString.UPPER_ALPHABET_START_IN_ASCII_CODE.index)
&& (i <= AsciiCodeIndexForRandomString.UPPER_ALPHABET_LIMIT_IN_ASCII_CODE.index || i >= AsciiCodeIndexForRandomString.LOWER_ALPHABET_START_IN_ASCII_CODE.index))
.limit(KEY_LENGTH)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString());
}
하지만 이렇게 작성할 시에는 문자열을 생성하고, 이 문자열이 기존의 데이터들과는 중복되지 않는지 검증해야만 했고, 중복이 될 시에는 다시 문자열을 생성해 주어야 한다는 단점이 있었다.
이것을 해결하기 위해 다른 단축 url 서비스 프로젝트들을 찾아봤더니 거의 대부분의 사람들이 base62라는 진수법을 사용하여 문자열을 생성해 주고 있었다.
base62에 대해서 이해하기 위해서는 먼저 base64에 대한 이해가 필요했다.
BASE64란?
BASE64 encoding은, 주어진 data를 6bits (2의 6승) 으로 쪼개어 각각을 ASCII character로 변환합니다.
** 여기서 64란, 0~9까지의 숫자(10), 알파벳 대문자(26), 소문자(26), 임의의 문자 2개(+, /) 로 encoding한다는 의미이다. (10 + 26 + 26 + 2) **
이와 같이 64진법으로 구성되어 있습니다. 이처럼 base64로 문자를 표현하기 위해서는 6비트가 필요한데,
일반적인 컴퓨터는 데이터나 ASCII 문자를 8비트로 구성되어 있습니다.
따라서 문자열을 6비트 단위로 짤라서 묶어야 하는 과정이 base64 인코딩 과정에 포함되어야 합니다.
예를 들면 아래와 같이 Man이라는 단어를 base64로 인코딩하는 과정입니다.
문자열 -> ASCII Code -> 6 bit 묶음 -> base64 encode의 방식으로 진행됩니다.
그런데 8비트의 데이터를 6비트로 쪼개다 보면 비는 부분이 생길 수 있습니다, 이럴 때는 뒤에 부족한 bit만큼 0을 붙여서
6비트 묶음을 완성시킵니다.
아래는 비어있는 공간을 0으로 채우는 과정 예시 입니다.
위의 사진을 보면 인코딩된 문자열의 마지막이 '='으로 채워진 것을 볼 수 있는데, 이는 ASCII는 8비트를 사용하고 base64는 6비트를 사용하다 보니 6비트씩 묶을 때 하나의 6과 8의 최소공배수인 24비트로 6비트 * 4로 완성시키도록 하였기 때문입니다.
정리하자면,
6비트를 완성시키는데 비트자리가 빈다? = 0을 집어넣는다.
6비트 * 4의 구성이 안된다? = '='을 집어넣는다.
라고 이해할 수 있습니다.
이렇게 base64에 대해서 알아봤습니다, 이젠 base62에 대해 알아보자면 base62는 base64에서 62번째와 63번째인
" / "과 " + "를 제외한 62진법이라고 이해하시면 됩니다.
그러면 이걸 왜 쓰느냐?
base62는 주로 url을 단축시키는데 많이 사용됩니다, url에는 쿼리나 파라미터라는 것이 존재하는데, 이를 인코딩 했을 시에 /이나 +가 있다면 오류가 날 가능성이 있기 때문입니다.
따라서 base62 = ( base64 - " / " - " + " )로 url을 단축시킬 때 사용하기 위해 존재하는 것 입니다.
이렇게 base62에 대해 알아본 나는 내 프로젝트의 key 생성 알고리즘을 고쳐보았다, base62 인코딩 디코딩을 구현하는 것을 매우 쉬웠기에 금방 코드를 고칠 수 있었다.
public String makeKey(int id) {
StringBuilder sb = new StringBuilder();
do {
int value = id % BASE_62_LENGTH;
sb.append(BASE_62_CHAR[value]);
id /= BASE_62_LENGTH;
} while (id > 0);
if (sb.length() < KEY_LENGTH) {
keyAppendInBlank(sb);
}
return sb.toString();
}
public int decodeKey(String newUrl) {
int result = BASE62.BASE_62_COMPLETED_MOD.value;
int power = BASE62.BASE_62_DECODING_POWER.value;
for (int i = 0; i < newUrl.length(); i++) {
int digit = new String(BASE_62_CHAR).indexOf(newUrl.charAt(i));
result += digit * power;
power *= BASE62.BASE_62_LENGTH.value;
}
return result;
}
리다이렉트를 위해서 decoding하는 메서드도 추가되었다.
public enum BASE62 {
BASE_62_LENGTH(62),
BASE_62_COMPLETED_MOD(0),
BASE_62_DECODING_POWER(1);
public final int value;
BASE62(int value) {
this.value = value;
}
}
base62를 사용하니 중복 유효성 검증을 할 필요가 없어졌지만, 문제점이 있었다.
base62로 인코딩하기 위해서는 기존의 url에서 고유한 id가 필요했다. 원래라면 기존 url을 바이트 코드로 바꾼 후 6비트로 나누어 묶은 뒤 인코딩 하는 형식이겠지만 그렇게 한다면 기존 url의 바이트 코드수가 너무 클 것 같았다.
따라서 나는 DB에 저장할 때 사용되는 auto increased되는 ID값을 인코딩하여 단축 url로 사용하기로 했고, id값이 1부터 시작된다면 인코딩 된 값이 한글자 혹은 두글자 처럼 너무 짧게 나올 가능성도 있기에 id값을 100000부터 시작하도록 하였다.
또한 사용자들에게 정형화된 단축 url을 보여주기 위해 기본 단축 url 길이를 8로 고정시킨 뒤 길이가 모자라면 뒤에 A를 모자란 만큼 붙히도록 했다.
아래는 Postman으로 지금 작성하고 있는 사이트를 단축 테스트 해본 결과이다.
정상적으로 단축 된 것을 볼 수 있고,
리 다이렉트 또한 정상적으로 작동되었다.
고작 기능 몇개가 변경된 걸로 구조가 망가지는거 같아 기분이 안좋았다....
참 갈 길이 멀다는 걸 새삼 느끼는 리팩터링 과정이었다. 열심히 해야겠다!
아직 모르는게 많아 틀린 정보가 있을 수 있습니다!!
'백엔드 멘토링' 카테고리의 다른 글
좋아요 기능 성능 개선해보기 (1) (1) | 2024.02.16 |
---|---|
@OneToMany, @ElementCollection (1) | 2023.12.01 |
자바의 신 - 복습 정리(2) (0) | 2023.09.23 |
Transaction이란? (0) | 2023.09.23 |
2023-08-29 DB 관련 질문 정리 (0) | 2023.08.30 |