개요
Dayner 에는 매달 정기 휴무일을 비롯한 연중 휴일, 급한 일정으로 인한 운영시간 변경등을 등록할수 있는 기능을 가지고 있다.
최초의 디자인인 경우에는 대부분 매주 월요일에 있는 정기 휴무만 표시를 하고 있었기에 영업시간변경/휴일 을 표시하는 type, date, description 이 존재하고 있었다.
이를 ~/yyyyMM 형식으로 표현된 엔드포인트를 이용해 api 요청을 하면 영업일정(id,type, date, description) 로 된 db에 between 첫일~ 말일 쿼리를 이용해 제공을 해주고 있었다.
문제는 디자인이 바뀌면서 프론트 단에서 우측 사진의 24~25일에 그려져있는 연속된 일정의 컴포넌트를 만들기 위해 프론트에서 과도한 자원 사용이 이루어지는 것 같다는 의견이 제시되면서 시작되었다.
변화가 적고, get 요청이 대부분 이라는 도메인 특성을 생각해보면 서버에서 프론트가 표현하기 가장 쉬운 형태로 데이터를 조작해서 캐싱을 통해 구현을 하는게 가장 효율적이라고 판단을 하여 여러 방안을 생각해보았다.
아이디어 1
현재 기능을 유지하되, 같은 description 의 경우에는 date에 리스트 형식으로 표현한다.
해당 기능을 담당했던 개발자 분이 연속된 디자인을 구현해내기 위해서 description의 비교를 통해서 리스트를 만들었는데 이를 서버에서 만들면 어떨까 하는 아이디어를 내셨다.
따라서 해당 리스트를 만드는 과정을 서버단에서 처리한다면 더 효율적일것이라고 생각해서 나오게된 아이디어
장점
- DB 마이그레이션이 필요하지 않다.
- 주어진 api 에 충실한 구현
단점
- 리스트를 만드는 과정에서 연속되어있는지, description이 같은지를 검증 해보아야하기에 자원이 많이 낭비된다.
- 또한 만들어진 리스트도 sort 를 통해 head 와 tail 을 인식하는 과정에 맨 앞과 맨 뒤의 index 를 가져오는 접근또한 자원이 소모된다.
서버 측 - O(N)
- N개의 일정을 순회하면서 같은 description을 가진 일정을 그룹화하고, 연속된 날짜를 찾아 리스트를 만든다.
프론트 측 - O(N)
- 프론트에서는 서버에서 받은 리스트를 다시 순회하면서 각 일정의 head, tail을 인식하는 작업을 수행해야 한다. 따라서 프론트에서도 O(N) 복잡도가 발생한다.
→ description 검증과 연속 여부를 검증하는 것보다 차라리 등록시에 리스트 형식으로 출력하기 쉽도록 등록하는건 어떨까?
아이디어 2
db 마이그레이션을 통해 startDate, endDate 속성을 추가한다.
장점
- 직관적인 data
- 서버단에서 추가적인 작업을 할 필요가 없다.
- head, tail 이 주어지기 때문에 프론트단의 자원소모가 적다.
단점
- DB 마이그레이션이 이루어져야한다!
- 또한 쿼리를 발생시키는 과정에서도 전월의 말일 ~ 금월의초반 을 가지고 있는 일정의 경우에는 Between 쿼리를 이용하지 못해서 full scan 이 이루어진다.
서버 측 - O(N)
- startDate와 endDate가 포함된 일정 데이터를 조회하는 과정에서, 특정 범위에 해당하는 일정들을 가져오는 작업은 여전히 O(N) 복잡도를 가진다.
프론트 측 - O(1)
- 일정이 이미 startDate와 endDate로 구분되어 있으므로, 프론트에서 별도로 head와 tail을 인식할 필요가 없다.
→ DB마이그레이션이 이루어지지 않고 효율적인 구현이 어려울까?
아이디어 3
hasNext Boolean 옵션을 추가하여 연결 디자인에 대응한다.
장점
- DB 마이그레이션이 필요하지 않다.
- 단일 일정에 대해서 hasNext 검증을 통해 쉽게 구현 가능하다.
단점
- head, tail 에 대한 구분을 하기 위한 로직이 필요하다.
- head: (prev.hasNext == False and now.hasNext == True)
- tail: (now.hasNext == True and next not null and next.hasNext == False)
서버 측 - O(N)
- 서버에서 일정에 hasNext 값을 추가하거나 기존 값을 업데이트할 때, 연속된 일정을 확인하는 작업이 O(N) 복잡도를 가진다.
프론트 측 - O(N)
- 프론트에서는 hasNext 값을 바탕으로 head와 tail을 인식하기 위해 다시 O(N) 복잡도를 가진다.
→ 아예 head, tail 구분을 백엔드에서 제공해주는게 어떨까?
아이디어 4
결국 우리가 필요한건?
타입을 나누어서 생각해보자.
연속된 일정을 위한 head와 tail 그리고 mid 가 있겠다.
단일 일정을 위한 single 타입이 있고
이 모든게 영업시간 변경/휴일 인지로 크게 나뉘어진다.
필수 구현 알고리즘?
DB 마이그레이션을 진행할것이 아니라면(안하는 편이 DB 조회 측면에서도 효율적이다)
특정 범위 내의 N개의 일정에 대해서
- 연속되는 일정인지 검증
- 같은 type인지 검증
해당 과정이 필수적인데 O(N)
이를 하는 과정에서 어떤 타입인지 서버에서 제공을 해준다면?
아이디어 결론
blockType 이라는 새로운 속성을 만들어서 각 일정에 대해 특정 날짜가 어떤 블록 타입인지(start, middle, end, single) 판단한 후 클라이언트로 이 정보를 포함한 date만 나열된 데이터를 보내는 방식으로 진행해보자!
장점
- DB 마이그레이션이 필요하지 않다.
- 단일 일정에 대해서 blockType 을 통해 쉽게 판별 가능하다.
- head, tail 구분을 위한 로직이 프론트에 구현되지 않아도 된다.
단점
- 서버 사이드에서의 자원소모가 불가피 하지만 DB 쿼리의 효율과 필수적으로 구현해야되는 로직이기 때문에 불가피한 소모라고 생각한다.
서버 측 - O(N)
- 서버에서 각 일정에 대해 blockType을 판단(start, middle, end, single)하고 이를 설정하는 과정이 O(N) 복잡도를 가진다.
프론트 측 - O(1)
- 서버에서 이미 blockType을 제공하므로, 프론트에서 추가적인 연산을 할 필요가 없다.
아이디어 5
애초에 저장할때 blockType 도 같이 저장을 할까? (DB에 속성 추가)
→ 만약 일정에 변화가 생기게 된다면 변화된 일정 앞뒤로의 blockType 수정이 불가피하다.
요청직전에 계산 + 캐싱 기능을 이용하자!
1. 변화가 없다면 요청마다 계산을 하지 않아도 되고
2. 캐싱 전략의 경우에는 GET 요청에 yyyyMM 파라미터를 이용해 저장해두고 yyyyMM 에 생성/업데이트/삭제가 수행될 경우에는 캐시 evict을 수행하는 방식으로 구현해보자
마무리
이번 글에서는 Dayner의 일정 관리 기능의 새로운 디자인 요구사항 추가에 대응하여 다양한 아이디어를 탐색하고, 그 장단점을 비교해보았습니다.
여러 아이디어를 고려한 결과로 api의 효율에는 서버와 프론트 간의 역할 분담이 중요하다는 것을 깨닫게 되었는데 서론에서도 말했듯이, 시스템(도메인)의 특성과 요구 사항에 따라 어디의 자원을 이용할지 어떤 전략을 사용할지가 정해지는지를 배운것 같습니다.
예로 들면, 아이디어 2의 경우에는 프론트 사이드에서 제시한 이슈를 해결하기 위해서 쉽고 귀찮지 않은(?) 방안으로 DB 마이그레이션을 통해 데이터를 구조적으로 변경하는 것이 빠를수도 있었지만, 프론트 사이드의 자원 사용을 같이 고려하여 적절하게 DB의 데이터를 후처리해서 제공을 해주는게 api의 효율을 높이는데 도움이 되었다는것을 알수 있었습니다.
간단하지만 나름의 View를 얻은게 있다하면 백엔드 엔지니어의 입장에서 서버의 효율만을 고려하는 것이 아니라, 프론트엔드와의 상호작용을 고려한 균형 잡힌 솔루션을 고민하는 것이 필요하다는 것을 깨달았던 리팩토링이었던것 같습니다.
여담으로 아이디어의 DB구조로 처음 DB설계를 진행하려했는데 오히려 최소한의 정보만을 담고 있었기에 리팩토링 시에 더 많은 가용성을 가지고 문제 해결에 접근할 수 있었던 것 같다는 생각을 하게 되어서 앞으로의 설계 철학에 이번 경험이 영향을 미칠것 같다는 생각이 들었습니다. ㅎㅎ
'Dayner 프로젝트' 카테고리의 다른 글
Dayner에서 구매 이력을 관리하는 방법 [1] (feat: 개인정보보호법, 원장 데이터) (4) | 2024.08.31 |
---|---|
영업시간 디자인 변경에 대응한 리팩토링 일지 [2] (feat: 버전별 API 캐시 전략) (0) | 2024.08.25 |
Dayner 2차 리팩토링 계획(feat: 멀티 모듈화) (0) | 2024.04.03 |