Cabi의 현재 레코드 수와 매 달 추가되는 레코드 수
2024-01-17 기준으로 Cabi의 테이블별 레코드의 개수는 다음과 같다.
•
cabinet : 398개
•
cabinet_place : 21개
•
admin : 21개
•
user : 958개
•
ban_history : 564개
•
lent_history : 10465개
•
lent_extension : 2049개
이 중 cabinet, cabinet_place의 경우 레코드의 개수는 실질적으로 사물함이 추가되지 않는다면 늘어나지 않는다. 현재의 42 서울 경제적 상황을 고려하면 절대 늘어날 일이 없다고 생각해도 좋을 것이다. 또한 admin의 경우 Cabi 인원들의 관리자 페이지 접속을 위한 구글 계정이니, 매 기수별 6명씩 뽑는 Cabi 특성상 일 년에 많이 늘어도 12개 언저리일 것이다.
user 테이블의 경우 42 서울에서 본과정에 합류하는 인원들만 포함되기 때문에, 일년 400명씩 늘어나게 된다. 추가적으로 2024 42 서울 증원 계획이 없기 때문에, 앞으로 user 테이블의 레코드 수가 늘어날 수 있을 지 불투명한 상황이다.
ban_history 테이블은 Cabi 정책 변경 전에는 월마다 40~70개 정도, 정책 변경 후에는 패널티 증가로 인해 30개 내외로 감소했다.
lent_history 테이블은 정책 변경 전에는 500~700개 정도, 정책 변경 후에는 연장권 도입으로 반납과 재대여가 줄어 평균 100개 내외로 감소했다.
lent_extension 테이블의 경우에는 11월에 첫 시작하여, 12월에는 550개의 레코드가 증가했다.
분석
현재 레코드 수와 매 달 레코드가 증가하는 수를 보았을 때, Cabi는 데이터가 그리 많지 않아 별도의 샤딩이 필요없다고 판단된다. 또한 MSA가 적용되어 있지 않아 샤딩으로 인한 Scale-out이 각 테이블 별로 이루어지는게 아니라, 전체 DB에 이루어지게 되어 오히려 샤딩을 적용함으로 인해 조회 성능적 저하와 Join 연산을 수행할 수 없는 점, 애플리케이션 사딩을 적용하기 위한 오버헤드 등이 예상된다. 이러한 점들을 생각해봤을 때는, 현재의 Cabi는 샤딩을 적용해서는 안된다고 말할 수 있다.
그럼에도 공부를 위해서 상황을 가정하고 샤딩 전략을 세워보고자 한다. 이후에 꾸준히 데이터가 쌓이거나 많은 인원의 유입으로 인해 데이터가 많아진다고 가정을 하고, MSA와 CQRS가 적용되어 위의 문제들이 해결된 상황에서 필요한 DB에 샤딩을 적용한다면 어떤 전략을 사용하는 것이 Cabi 애플리케이션에 어울리는지 생각해보자.
현재 가장 많은 데이터가 쌓여있고, 앞으로 가장 많이 데이터가 쌓일 것이라 예상되는 테이블은 lent_histroy와 lent_extension 테이블이다.
LentHistory 샤딩 전략
먼저 MSA가 적용된 lent_history DB에 샤딩을 적용한다고 생각해보면, 샤딩 전략을 선택할 기준들에 대해 고민해보았다.
•
해시 샤딩
해시 샤딩의 장점인 데이터를 샤드마다 골고루 분배하기 쉽다는 점을 살려, 모듈러 연산을 통해 샤드를 결정하는 모듈러 샤딩이 좋을 것 같다. 특히 lent_history의 경우 id를 통한 조회보다 cabinet_id나 user_id로 조회하는 일이 압도적으로 많기 때문에, lent_history 레코드의 cabinet_id 값이나 user_id 값으로 모듈러 연산하여 샤딩하는 것이 가장 적합해 보인다. 추가적으로 두 컬럼 중 하나를 선택하여 샤딩을 한다면, 동일한 사물함에 대해서 혹은 동일한 사용자에 대해서 서로 다른 레코드가 필요할 때 데이터를 여러 샤드에서 조회하지 않아도 된다.
또한 lent_history는 쌓여있는 레코드는 조금 있지만 정책 변경으로 인해 레코드가 쌓이는 속도가 느리기 때문에, 해시 샤딩의 단점인 새로운 샤드 추가 시 데이터 정렬이 필요하다는 단점도 크게 부각되지 않아보인다.
•
범위 샤딩
lent_history의 여러 컬럼 중 반납 시 날짜가 저장되는 ended_at이나 연장권 사용 시 날짜가 변경되는 expired_at은 범위 샤딩에 적용시키기에 무리가 있다.
user_id는 나중에 들어오는 학생일수록 user_id가 크고, 그러한 이유로 user_id가 작을수록 취업을 하거나 개인적인 이유로 42 서울 활동을 더이상 하지 않는 사람이 많다. 때문에 user_id로 범위 샤딩을 적용하면 Hotspot이 될 확률이 무척 높아진다.
cabinet_id를 선택하는 경우, 현재 42 서울에는 사물함의 수가 늘어날 확률이 무척 작기 때문에 범위 샤딩을 적용하더라도 샤드를 새로 추가하면 데이터 재정렬이 필요해진다. 이는 범위 샤딩의 장점을 전혀 살리지 못하기 때문에 사용할 수 없다.
그나마 가장 나은 선택지가 대여 시작 날짜인 started_at인데, Hotspot을 피하기 위해서는 날마다 저장되는 샤드를 바꿔야한다. 현재 바뀐 정책상 이전 사물함의 이용자가 반납을 해야 새로운 대여가 발생하는데, 연장권 도입으로 인해 반납 날짜를 예상할 수 없는 만큼 started_at도 정확히 예측할 수 없다는 문제가 있다. 하지만 이는 그리 큰 문제는 아닌 것처럼 보이기 때문에, 굳이 범위 샤딩을 적용한다면 started_at 컬럼을 사용하는 것이 좋아보인다.
•
디렉토리 샤딩
사실 디렉토리 샤딩의 경우 별로 큰 메리트가 없다. 데이터가 그리 빨리 쌓이지 않아 적은 수의 샤드를 가져가게 될 것으로 예상되는 시스템 특성상 샤딩 알고리즘의 유연함 별로 장점이 되지 못한다.
이렇듯 장점은 별로 살리지 못하지만 lookup table 오버헤드나 lookup table 구성으로 인한 추가적인 리소스 사용, lookup table이 단일 장애 포인트가 될 수 있다는 단점은 그대로이다. 여러 방안을 통해 단점을 극복한다하더라도, 장점이 별로 부각되지 않으니 디렉토리 샤딩 전략은 선택하지 않는 것이 좋아보인다.
위의 세 가지 샤딩 전략을 고려해보았을 때, 샤드를 추가하여 샤드 수를 늘리는 일이 자주 발생하지 않을 것으로 예상되는 lent_history DB 특성상 해시 샤딩이 가장 적합해 보인다.
LentExtension 샤딩
다음으로 MSA가 적용된 lent_extension DB에 샤딩을 적용한다고 생각해보면, 샤딩 전략을 선택할 기준들에 대해 고민해보았다.
•
해시 샤딩
해시 샤딩을 선택한다면 lent_extension도 lent_history와 마찬가지의 이유로 user_id를 통한 조회가 잦다는 점과 동일한 유저에 대해 여러 샤드를 찾아보지 않아도 된다는 점 동일한 유저에 대해 여러 샤드를 찾아보지 않아도 된다는 점을 보아 user_id 컬럼을 통한 해시 샤딩이 적합해보인다.
하지만 lent_history에 비해 데이터의 쌓이는 수가 많기 때문에(물론 월에 500건이 많은 건 아니지만 현재 상황에 비례하여 샤딩을 적용할만큼 많아진다는 가정하에), 샤드 추가 시 데이터 재정렬이 필요하다는 단점에 대해서는 조금 더 깊이 생각해볼 필요가 있어보인다.
•
범위 샤딩
범위 샤딩 전략을 사용한다고 하면, lent_history와 동일한 이유로 user_id 컬럼과 used_at 컬럼은 사용하지 못한다.
특별한 경우가 아니라면 이전 달 시간을 충분히 채워서 일괄적으로 연장권을 지급하는 것이 거의 전부인 상황인데, 매월 초에 지급하여 매월 말에 만료되는 연장권 특성상 expried_at 컬럼은 매달 지급되는 연장권이 같은 값을 가지고 있으므로 이 컬럼을 사용하면 Hotsopt이 발생한다.
또한 같은 이유로 extension_period 컬럼과 name 컬럼도 사용하지 못한다.
결국 lent_extension은 범위 샤딩을 적용하기 위한 적합한 특성을 가진 컬럼이 없기 때문에, 범위 샤딩을 적용하는 것은 부적절해 보인다.
•
디렉토리 샤딩
현재 Cabi에는 재화 시스템을 도입하여 연장권을 구매하여 사거나 팔 수 있도록 할 계획이 잡혀있다.
데이터가 빨리 쌓이는 lent_extension의 특성과 위와 같이 추후 어떤 이벤트를 통해 다양한 연장권이 도입될 지 모르는 상황이기 때문에, 디렉토리 샤딩의 알고리즘을 유연하게 가져갈 수 있다는 점은 꽤나 적합해 보인다. 당장은 모듈러 연산과 유사한 형태의 lookup table을 구성해두고, 추후 별개의 이벤트가 발생하여 새로운 형태의 연장권이 생긴다면 적절한 알고리즘을 추가하는 식으로 적용하는 것이 좋아보인다.
단점에 대한 대안은 replica나 백업을 통해 단일 장애 포인트에 대해 해결하고, 조회 오버헤드는 CQRS를 통해 이미 해결이 되어있으니 큰 문제가 되지 않는다. 장점이 명확하니 이처럼 단점에 대한 대안을 마련해둔다면 디렉토리 샤딩을 사용하는 것도 괜찮아 보인다.
위의 세 가지 샤딩 전략을 고려해보았을 때, 데이터가 빨리 쌓이기도 하고 다양한 이벤트에 대응하기에 좋기 때문에 디렉토리 샤딩이 가장 적합해 보인다.
결론
직접 lent_history와 lent_extension에 대해 각 전략의 장단점을 명확하게 생각해보고 DB 특성을 생각해보는 과정으로 어떤 샤딩 전략을 사용하는게 적합할지를 고민해보면서, 여러 샤딩 전략들에 대해 공부할 때는 그렇구나 하고 넘어갔던 부분들이 직접 생각하는 동안에 많이 와닿았다.
특히 디렉토리 샤딩 같은 경우에는 알고리즘을 유연하게 사용할 수 있다는 장점 대비 단일 장애 포인트나 lookup table의 추가 리소스로 인해 저걸 언제 쓰지라는 생각을 했었는데, 직접 샤딩 전략을 선택해보니 추후 어떤 이벤트가 생길지 모르는 상황에서 여러 이벤트에 맞춰 지급해야하는 lent_extension에 적용하기에는 굉장히 적합하다고 생각이 들었다.
아쉽게도 범위 샤딩 전략이 적합한 경우를 찾지 못했지만 위 과정을 통해서 여러 샤딩 전략들의 장단점에 대해 깊게 고민해볼 수 있는 기회였고, 추후 Cabi나 다른 프로젝트에서 샤딩을 고려할 때 많은 도움이 될 것 같다. 사실 샤딩을 구현하면서 여러 DB를 연결하는 인프라 과정에서 배우는게 많을 것 같은데, 이를 직접 해보지 못해 아쉽다. 추후 다른 프로젝트에서 적용해보는 걸로…