"캘린더 이벤트 생성 버그, 겪었던 고민과 해결 방법"

gimseonjin616·2023년 3월 11일
0

회고

목록 보기
2/2

안녕하세요! 신입 개발자 Kerry입니다!

오늘은 지난 프로젝트하면서 겪은 문제를 간단하게 회고하는 글을 써보려고 합니다.

개인적으로 회고하는 내용이라 많은 내용이 누락되고 틀릴 수 있으니 문제가 있으면 알려주시면 감사하겠습니다!

문제 발생

어 Kerry, 캘린더에 이벤트를 생성할 때, 이벤트가 두 개 만들어지내요???

제가 개발하고 있는 서비스는 스케쥴링 서비스입니다.

사용자의 캘린더를 보고 적절한 일정을 제안하고 확정까지 할 수 있는 서비스 입니다.

따라서 캘린더에 이벤트(일정)을 확정 짓는 서비스는 매우 중요한 기능입니다.

그런데 가끔씩 캘린더에 이벤트가 두 개씩 만들어지는 버그가 발생했습니다.

이런 상황은 UX에 좋지 않기 때문에 빠르게 해결해야 했습니다.

원인 파악

저희 서비스는 GCP의 Cloud Run을 사용합니다.

그래서 해당 요청의 로그를 살펴보니 "동일한 요청이 약 1초 간격으로 발생하면, 두 개의 이벤트가 생성" 됩니다.

Hotjar라는 유저 행동을 트래킹해주는 서비스로 파악해보니 빠르게 더블 클릭하는 경우, 흔히 얘기하는 "따닥"을 하는 경우에 자주 발생했고 Front에서 더블 클릭을 막아버리는 것으로 문제를 해결할 수 있었습니다.

그러나 네크워크 이슈나 새로 고침 등오로 여전히 동일 문제가 발생을 했고 근본적인 문제를 해결하기로 했습니다.

기 서비스 분석

현재 저희 서비스의 이벤트 생성 워크플로우를 간략하게 예시를 들면 아래와 같습니다.

1. 이벤트 생성을 요청한다.
2. 이미 생성된 이벤트인지 확인한다.
3. 캘린더 api를 호출한다.
4. 이벤트 생성 알림 메일을 보낸다.
5. 결과를 DB에 저장한다.

이렇게만 봤을 때, 어떻게 해결해야할 지 감이 잡히지 않았습니다.

그래서 저는 "타임라인 다이어그램"을 사용해서 분석해봤습니다.

타임라인은 시간에 따른 액션의 순서를 표현한 것이며 동시에 여러 개가 만들어질 수 있습니다.

액션은 부수효과가 있는 함수로 쉽게 얘기해서 외부 서비스에 영향을 끼치고 상황에 따라 결과가 바뀌는 것입니다.

한번의 요청이 들어온 경우 아래와 같이 타임라인이 그려집니다.

그리고 동일한 요청을 처리할 수 있는 가장 이상적인 타임라인은 아래와 같습니다.

그러나 문제가 발생한 타임라인은 아래처럼, 이벤트 생성 결과가 DB에 저장되기 전에 새로운 요청이 들어온 경우입니다.

물론 unique key를 사용해서 DB에는 저장이 되지 않지만 외부 API의 경우에는 따로 Lock이 걸려있지 않아 두 개의 이벤트가 생성된 것입니다.

결국 이 문제를 해결하기 위해선 "캘린더 API를 호출하기 전에 중복 여부를 판단" 할 수 있어야 합니다.

해결 방법 1

제가 맨 처음 생각한 방식은 "서버 메모리에 이벤트 생성 요청 정보를 저장하고 중복 여부를 판단하자" 입니다.

외부 API를 호출하기 전에 가지고 있는 요청 정보에서 동일한 요청이 있으면 해당 타임라인을 끝내버리는 것입니다.

그러나 이 방법을 사용할 수 없었습니다.

저희가 사용하는 Cloud Run은 Stateless한 서비스 입니다.

그렇기 때문에 서버 내부에 값을 저장할 수 없고, 설사 저장하더라도 제대로 동작할 지 장담할 수 없습니다.

외부 In Memory DB인 Redis나 Memcached를 사용할 수 있지만 단지 이 문제를 해결하기 위해서 도입하는 것은 오버 엔지니어링으로 판단됐습니다.

해결 방법 2

이 서비스는 현재 운영되고 있는 서비스기 때문에 최대한 우리가 가지고 있는 자원에서 이 문제를 해결해야하는 것 뿐 아니라 기존 유저플로우를 해쳐선 안됐습니다.

그래서 생각한 방법은 "액션의 순서를 바꾸는 것"입니다.

현재 저희 로직에서 중복 체크를 하고 있는 부분은 크게 두 군데 입니다!

바로 DB 조회 & DB 저장입니다.

그리고 이 문제의 핵심 원인입니다. DB 조회와 DB 저장 사이에 캘린더 API 호출과 이메일 보내는 로직이 있어서 그 와중에 새로운 요청이 오면 중복 검증이 안되는 것입니다.

그렇다면 이 API 호출과 이메일 로직을 DB 저장 이후로 빼면 어떻게 될까요?

아래 타임라인처럼 API 중복 호출이 발생되지 않습니다.

다만 API 호출 결과를 DB에 저장해야하기 때문에 마지막에 DB Update를 추가해줘야만 완벽한 타임라인이 만들어집니다.

이 방법을 사용할 경우, 액션의 순서만 바꾸면 되기 때문에 interface나 소스 코드의 변화가 거의 없으며 문제 해결에 필요한 비용도 매우 적습니다.

다만 기존과 비교했을 때, Update Query가 한번 더 발생하는 단점이 존재하지만 저희 서비스는 초기이고 하나의 쿼리가 문제를 발생시킬 만큼 트래픽이 많지 않기 때문에 심각한 문제는 아니였습니다.

마무리

저희는 2번째 방법을 통해 문제를 해결했습니다.

우선 개발 초기 단계라 구현해야할 기능이 산더미이고 소요공수와 투입자원 및 소요기간 모두 넉넉치 않아 "가지고 있는 자원 내에서 빠르게 해결할 수 있는" 2번을 채택했습니다.

하나의 이벤트만 만들기 위해 해당 이벤트를 생성하는 다른 요청를 막는다는 점과 각각의 타임라인의 제어는 DB의 Lock과 트랜잭션 격리와 비슷하다는 느낌을 받았습니다.

따라서 기회가 된다면 다음 블로그 글에서는 비즈니스 로직 레벨에서 ACID와 동시성을 어떻게하면 잘 제어할 수 있는가에 대해 살펴보려고 합니다.

부족한 글이지만 재미있으셨다면, 또 편하게 애기를 나누고 싶으시다면 아래의 커피챗 링크로 편하게 연락주세요!


Kerry Kim

Software Developer

📧 gimseoniin616@gmail.com
📱 010-8835-2870
Kerry와의 커피챗
🏠 https://github.com/gimseonjin

profile
to be data engineer

0개의 댓글