승인 프로세스 뒤에 숨겨진 일꾼들: 사장님 입점요청 승인툴 고도화 프로젝트 경험담

Sep.26.2024 이형준, 박민규, 박재랑

Backend Web Frontend

사장님이 배민에서 장사를 시작하기 전, 어떤 것들을 준비할까요?
우리가 보는 배민앱에서 가게가 노출되고 주문을 받기위해서, 사장님은 조금 길고 험난한 준비과정을 거쳐야 합니다.

  • 사장님과 가게에 대한 기본 정보
  • 입점 계약을 체결하기 위한 ‘사업자정보’와 ‘영업신고증’ 을 비롯한 각종 인허가 서류 입력
  • 배민앱에 노출될 가게의 영업시간, 휴무정보, 매장 사진
  • 배달팁, 배달시간, 배달지역 정보
  • 군침 싹 도는 메뉴 사진과 메뉴 정보
  • 주문 대금을 정산받기 위한 정산 정보 등등등….

사장님은 이렇게 많은 정보를 배민외식업광장 ‘입점신청’ 에서 온라인 계약서의 형태로 등록하게 되는데요.

배달의민족 내부 승인팀에서 사장님이 제출한 계약서를 승인 정책에 맞춰 검수하고 배달의민족 앱에 노출하고 있어요.

이 과정을 요청-승인 프로세스 라고 칭하며, 오늘은 이 프로세스가 운영되는 ‘SUPER2 승인툴’ 어드민을 고도화한 세일즈서비스팀의 여정을 소개하려고 합니다.

사장님의 장사를 돕는 보이지 않는 손 “SUPER2”

승인을 담당하는 어드민 SUPER2 의 가장 중요한 목표 중 하나는 바로 ‘사장님의 요청을 빠르게 승인하는 것’ 입니다. 그래야 사장님이 빠르게 배달의 민족에 가게를 노출하고, 주문을 받을 수 있기 때문이에요.

사장님이 등록하는 정보의 종류가 다양하기 때문에, 승인팀에서는 각 정보의 성격별로 파트가 나뉘어져 업무를 하고 있어요. 그리고 승인팀이 사용하는 SUPER2 승인툴은 각 담당자의 속성에 맞는 요청을 자동으로 분배해주는 ‘자동할당 시스템’ 을 기반으로 운영되고 있습니다.

승인팀의 업무 효율성을 높인다는 것은 곧 사장님의 빠른 입점을 보장하는 것이기에, 어떻게 하면 승인 업무의 효율성을 높여 사장님의 빠른 장사를 도울지를 고민하면서 이 과제가 시작되었습니다.

super2 승인툴이 안고 있던 어려움

✅ 첫째, 승인 작업자들이 자신의 업무 현황을 파악할 수 없다

기존 SUPER2승인툴은 유입된 요청을 하나의 리스트화면에서 일괄적으로 조회하고, 각 작업자들이 알아서 자신의 업무를 검색하여 가져가는 형태였어요.



이 과정에서 실수로 누락하는 업무도 생기고, 스스로 오늘 어느 정도의 일을 처리했는지 알기 어려웠습니다. 또한 매번 새로 요청이 들어왔는지 확인하기 위해 리스트화면을 리프레시해야하는 불편도 있었고요.

✅ 둘째, 승인 업무를 총괄하는 관리자들이 전체 현황을 파악할 수 없다.

승인팀의 관리자는 업무 전반의 추이를 파악하고 병목이 발생하는 구간을 해결하거나 지연을 야기하는 이슈를 해결해야 하는데요.

기존에는 관리자들이 이러한 정보를 볼 수 있는 지면이 없어 매번 메신저로 각 담당자에게 현황을 물어보며 커뮤니케이션 하거나, 뒤늦게야 병목구간이나 에러를 발견하는 등 비효율적으로 관리업무를 수행하고 있었어요.

작업자와 관리자 측면 각각에서 발생하는 이러한 비효율은
결과적으로 빠른 승인을 저해하는 요인이 되어 SUPER 승인툴의 가장 중요한 방향성 "빠른 승인을 통해 사장님의 빠른 장사를 돕는다" 를 해치고 있었습니다.

그래서, 크게 2개의 방향으로 개선을 해보기로 했어요.

👉🏻 승인작업자가 개인의 업무를 빠르게 처리할 수 있게! 개인 업무관리 고도화

👉🏻 관리자가 전체 현황을 효율적으로 트래킹할 수 있게! 업무현황보드 개발


개인 업무 관리


개인 업무 관리 화면은 승인 담당자들이 가장 많은 시간을 보내는 핵심적인 공간이에요.
이곳에서 업무를 시작하고 종료하도록 하고, 할당받은 요청과 관련된 대부분의 작업을 수행할 수 있도록 했어요.

이런 만큼, 이 화면의 사용성은 승인 담당자들의 업무 생산성에 큰 영향을 미치게 되는데요.
그래서 화면을 사용자가 직관적으로 쉽게 사용할 수 있도록 설계하는 것이 매우 중요합니다.
하지만 현재 세일즈서비스팀의 시스템 구조상 디자이너의 도움을 받기가 어려운 상황인데요.

업무 관리 화면에서 어떤 부분들이 개선되었는지를 함께 말씀드리고, 디자이너의 지원 없이도 화면을 더 나은 방향으로 개선할 수 있는 몇 가지 방법을 공유하겠습니다.

👉🏻 개인 업무 관리에서 개선한 것들


우선, 개인의 업무현황을 확인할 수 있도록 업무 관리 화면에 상단 보드를 제공했어요.

전체 리스트 화면으로 요청을 조회하는 기존 방식은 각 작업자가 매번 필터를 설정해 조회해야 하는 점이 번거로웠는데요.
새로 제공된 상단 보드에서는 작업자 개인별로 총 할당된 업무, 보류된 작업, 그리고 중요한 정보들을 한눈에 확인할 수 있도록 구성했어요.

상단 보드에서는 별도의 화면 전환 없이 할당되거나 보류된 작업을 바로 확인할 수 있도록 기존 ‘요청 관리’ 화면의 일부를 레이어 팝업으로 제공하고 있어요. 덕분에 필요한 정보를 더 빠르고 편리하게 확인할 수 있게 되었습니다.
한 번의 클릭으로 업무 목록을 바로 볼 수 있는 기본 필터 기능이 포함되어 있으며, 필요에 따라 추가 필터도 설정할 수 있습니다.

또한, 할당받은 업무의 속성에 맞게 업무그룹이 자동변경되도록 했어요.



작업자들은 각자 지정받은 업무 그룹에 속해 요청을 처리하며, 할당받는 요청의 속성에 맞는 그룹에서 업무를 처리해야 해요. 그래야 업무 속성별로 생산성이나 처리율을 정확하게 집계할 수 있죠.
하지만 이전에는 수동으로만 업무 그룹을 변경할 수 있어 불편함이 많았습니다.

이번 개선을 통해 작업 시작 시 자동으로 적절한 그룹에 배정되도록 시스템을 개선했고,
작업자들이 혼란을 겪지 않도록 그룹 변경 전후의 정보를 모두 제공하는 기능도 추가하였습니다 🙂


화면 설계는 사용자가 시스템을 어떻게 경험하고, 얼마나 효율적으로 작업을 수행할 수 있는지에 큰 영향을 미칩니다.

특히 비즈니스 환경에서 사용되는 업무 관리 화면은 직관적이고 사용하기 쉬워야 하며, 다양한 상황에서도 모든 사용자가 편리하게 접근할 수 있어야 해요.

이러한 이유로, 사용자 경험을 더욱 향상시키기 위해 적용할 수 있는 몇 가지 핵심적인 개선 방법을 소개하고자 합니다.

화면을 더 나은 방향으로 개선할 수 있는 몇 가지 방법


📍1) 색상대비와 사용성 향상
디지털 환경에서 모든 사용자가 웹 콘텐츠에 쉽게 접근할 수 있도록 하는 것은 매우 중요해요.
특히 저시력자들도 무리 없이 콘텐츠를 확인할 수 있도록 하기 위해, 웹 콘텐츠 접근성 가이드라인(WCAG)에서는 색상 대비에 대한 명확한 기준을 제시하고 있습니다

ℹ️ 색상 대비란?
색상 대비는 텍스트와 배경색 간의 밝기 차이를 의미합니다. 이 대비가 충분하지 않으면, 텍스트를 읽기 어려워져 사용자의 접근성이 크게 떨어집니다. WCAG에서는 AA 등급 기준으로 다음과 같은 대비 비율을 권장하고 있습니다.

  • WCAG AA 등급
  • 작은 글자: 최소 4.5:1
  • 큰 글자(18pt 이상의 텍스트 또는 굵은 텍스트): 최소 3:1

이 기준은 특히 저시력자들을 위한 것이지만, 일반 사용자에게도 도움이 됩니다. 색상 대비가 잘 유지된 콘텐츠는 더 선명하게 보이며, 가독성도 크게 향상돼요.
콘텐츠가 WCAG의 색상 대비 기준을 준수하는지 확인하는 것은 매우 간단합니다. WebAIM의 색상 대비 확인 도구를 사용하면, 웹사이트의 텍스트와 배경색 간의 대비를 쉽게 확인할 수 있습니다.


↑ 색상대비가 불충분한 경우와 충분한 경우 비교

특히 버튼에 있는 텍스트는 색상 대비를 충분히 확보하는 것이 더 중요해요. 버튼은 사용자 인터페이스에서 중요한 요소로, 클릭이나 터치와 같은 상호작용을 유도하기 때문에 사용자가 쉽게 인식하고 읽을 수 있어야 합니다.

하지만 버튼의 색상이 동적으로 변경되는 경우(예: 마우스 오버, 활성화 상태 등), 이때 텍스트와 배경 간의 색상 대비가 떨어질 수 있어요. 이러한 부분도 놓치지 않는 것이 중요해요.

📍2) 특정 상태를 컬러로만 표현하지 않기

색상은 시각적으로 중요한 요소입니다. 색상을 통해 사용자에게 직관적으로 정보를 전달할 수 있기 때문이죠.

하지만 모든 사용자가 색을 동일하게 인식하는 것은 아니에요. 특히 색맹을 가진 사용자들에게는 색상만으로 정보를 전달하는 것이 어려울 수 있습니다. 그렇기 때문에 특정 상태를 컬러로만 표현하는 것은 피해야 해요.


↑ 색맹을 가진 사용자에게 불친절한 UI와 그렇지 않은 UI (출처)

색맹 사용자들이 색상만으로 상태를 판단하기 어려우므로, 추가적인 정보나 표시를 제공하여 누구나 쉽게 상태를 이해할 수 있도록 해야 합니다.

📍 3) 사용자의 특정 액션을 취소하거나 수행을 어렵게 만들어 리스크를 줄이기
디지털 작업 환경에서는 버튼 하나로 중요한 데이터를 삭제하거나 잘못된 데이터를 생성할 수 있습니다.
취소 기능을 제공함으로써 실수로 인한 리스크를 줄이고 사용자 경험을 개선할 수 있어요.

예를 들어, Gmail은 이메일 삭제 후 이를 되돌리는 기능을 제공하죠.

Gmail과 같은 되돌리기 기능을 구현하는 데는 여러 개발적 고려 사항이 따를 수 있어요.

따라서 중요한 액션을 수행하기 전에 확인 창을 띄워 사용자가 정말 해당 액션을 실행할지 묻는 것도 유용한 방법입니다.

↓ GitLab의 프로젝트 제거 시 문구입력


📍 4) 페이지네이션 사용하기
무한 스크롤 방식은 피드처럼 지속적으로 탐색할 콘텐츠를 제공할 때 적합하지만, 업무 목적의 사용자에게는 페이지네이션이 더 적합할 수 있어요.
페이지네이션을 통해 사용자는 콘텐츠의 끝을 쉽게 파악할 수 있으며, 콘텐츠가 많을 경우 필터를 조정해 탐색할 콘텐츠의 수를 줄일 수 있죠.

페이지네이션을 구현할 때 전체 콘텐츠 수를 계산해야 하므로 데이터베이스에 부담이 될 수 있지만, 사용자 경험 측면에서 페이지네이션이 더 나을 수 있다는 점을 고려하면 좋습니다.

↓ 처음, 마지막 페이지로 이동할 수 있는 기능을 제공하는 페이지네이션

또한, 페이지네이션을 제공할 때 현재 페이지의 앞뒤 몇 개 페이지뿐만 아니라, 처음과 마지막 페이지로도 쉽게 이동할 수 있는 기능을 제공하면 더욱 유용해요.

위에 기술한 내용들은 ‘업무 관리’뿐만 아니라 ‘업무 현황 보드’에도 적용되어 관리자가 전체 현황을 효율적으로 파악할 수 있도록 도와주고 있습니다.

업무 현황 보드


업무 현황 보드는 승인 작업자들의 업무 상태와 요청 처리 현황을 관리자들이 실시간으로 파악할 수 있게하는 지면입니다.
요청의 처리현황을 준실시간으로 확인할 수 있고, 관리자가 f/u해야하는 긴급 이슈나 시스템 에러, 장기 처리 지연 건에 대해 리포팅 받을 수 있어요.


이를 통해 관리자는 당일 처리해야 할 요청의 누락을 방지하고 긴급 요청을 우선적으로 배정하며, 지연된 요청에 빠르게 대응할 수 있습니다.

또한, 작업 인원 현황과 처리량을 확인해 인력 부족 시 유휴 인력을 활용하거나 추가 지원을 결정하여 업무 효율성을 높일 수 있습니다.

관리자는 실시간으로 데이터를 확인하기 때문에 신속한 의사결정을 내리기 위한 준실시간 데이터 조회는 중요합니다.

하지만 실시간 데이터 조회는 데이터베이스에 큰 부하를 줄 수 있는 작업이므로, 시스템의 성능 저하를 최소화하기 위해서는 최적화된 조회 방식이 필요했어요.

👉🏻 시스템 성능을 고려한 실시간 데이터 조회 방식

업무 현황 보드에서 실시간 조회는 시스템 성능에 심각한 부하를 줄 수 있으므로 최적화된 접근 방식이 필요합니다.

데이터베이스 조회가 오래 걸릴 수 있는 데이터를 안정적으로 전달하기 위한 주요 방식으로 캐시(Cache), 배치(Batch), 스케줄러(Scheduler)를 고려하게 되었는데요. 각 방식의 특징을 비교해보겠습니다.

📍 1) Cache
캐시 방식은 데이터 조회 시 반복적으로 발생하는 동일한 요청에 대해 빠르게 응답하기 위해 사용됩니다.
데이터를 처음 조회한 후, 이를 인메모리 DB에 저장하고, 이후에는 인메모리 DB에 저장된 데이터를 활용하여 데이터베이스 조회 부담을 줄일 수 있습니다. 하지만 캐시 방식은 다음과 같은 단점이 있습니다.


출처 : https://javascript.plainenglish.io/thundering-herd-problem-solution-with-node-js-and-promise-e8bc55dc5105

첫 번째, 초기 조회 성능 문제입니다.
데이터베이스에서 처음 데이터를 가져와 메모리에 저장하기 전까지는 실제 데이터베이스 조회가 필요하므로, 첫 조회는 시간이 오래 걸릴 수 있습니다.
이 시점에 여러 개의 요청이 동시에 들어올 경우, 데이터베이스에 과도한 부하가 걸릴 수 있습니다. 예를 들어, 초기 데이터 조회 시 10개의 동시 요청이 발생하면 데이터베이스에 10번의 조회 요청이 들어가게 되는데(Cache Stampede) 이는 성능 저하를 초래할 수 있습니다.
물론 이러한 문제를 해결하기 위해 분산 락(Distributed Lock) 메커니즘을 활용할 수 있지만 구현 복잡성 증가, 운영 및 유지 보수 부담 등의 새로운 문제를 야기할 수 있습니다.

두 번째, 저장 데이터의 크기 문제입니다.
캐시로 저장해야 할 업무 현황과 관련한 데이터가 10MB가 넘는다는 점입니다. 메모리 사용량이 급격히 증가하여 전체 시스템 성능에 부정적인 영향을 미칠 수 있습니다.
특히 RDB의 인덱스를 활용할 수 없고 캐시에 저장된 데이터를 애플리케이션에서 매번 필터링해야 한다는 점에서 큰 비효율성이 발생합니다.

📍 2) Batch

배치 방식은 주기적으로 데이터를 조회 및 처리하여 별도의 통계 테이블에 저장한 후, 업무 현황 조회 시 통계 테이블에서 데이터를 가져오는 방식입니다.
데이터베이스에 실시간 부하를 최소화하는 방식으로서 유용합니다. 특히, RDB의 인덱스를 활용할 수 있는 점에서 대용량 데이터의 효율적인 조회가 가능하다는 장점이 있습니다.
통계 테이블의 데이터가 정기적으로 업데이트되고 빠르게 필터링 된 데이터를 조회할 수 있어 성능이 뛰어납니다.

하지만 배치 방식은 추가적인 오버헤드가 발생하는 문제가 있습니다. 현재 팀의 배치 수행 방식은 애플리케이션을 독립적으로 실행하는 방식입니다. 배치 작업을 실행하기 위해 애플리케이션 컨텍스트를 로드하고 서버를 시작하는 과정에서 추가적인 오버헤드가 발생합니다.

이러한 초기화 작업은 시간이 수 분 단위로 소요되기 때문에 처리 속도가 느려지게 됩니다. 따라서 적재된 통계 데이터는 실시간성이 떨어지게 됩니다.

📍 3) Scheduler
스케줄러 방식은 배치 방식과 비슷하게 일정한 주기로 데이터를 처리하고 저장하는 방법이지만, 더 자주 실행되어 실시간에 가까운 데이터를 제공할 수 있습니다.

통계 데이터를 별도의 테이블에 저장하기 때문에, 데이터베이스에 실시간으로 부하를 주지 않으면서도 비교적 실시간에 준하는 데이터를 제공할 수 있습니다. 이는 캐시의 속도와 배치의 대규모 데이터 처리의 이점을 모두 결합한 방식이라 생각합니다.

스케줄러 방식의 가장 큰 장점은 부하를 효율적으로 분산할 수 있다는 점입니다. 현재 저희 팀은 API 서버와 스케줄러 작업을 처리하는 워커 서버를 분리하여 운영하고 있어, 스케줄러 방식을 활용하기에 최적의 환경을 갖추고 있습니다.

이 구조를 통해 통계 데이터 갱신 작업을 워커 서버가 전담 처리하므로, 스케줄러 작업 주기를 유동적으로 조절하더라도 API 서버의 성능에 직접적인 영향을 주지 않아, 비즈니스 상황에 맞게 주기를 유연하게 조정할 수 있습니다.

캐시 방식은 빠른 응답 속도와 효율적인 메모리 활용이 장점이지만, 첫 조회 시 발생하는 성능 저하와 대용량 데이터 처리에서의 한계가 있습니다.
배치 방식은 대규모 데이터 처리에 효과적이지만 준실시간성을 보장하지 못합니다.
스케줄러 방식은 준실시간성과 시스템 부하 간의 균형을 유지하며 유연한 처리가 가능합니다.

따라서 업무 현황 보드와 같은 준실시간 데이터 조회가 필요한 시스템에서는 시스템 성능 저하를 최소화하고, 대용량 데이터를 효율적으로 처리할 수 있는 스케줄러 방식이 가장 적합하다고 생각하여 채택하였습니다.


마지막으로,
업무현황보드에서 중요하게 사용되는 항목인 ‘정시 미처리 요청 집계‘ 를 구현하면서 고민한 점을 이야기 해보려고 하는데요.

사장님의 빠른 입점을 돕기 위해 승인 작업자에게 특정 시간까지 반드시 처리하도록 가이드하는 기준이 있는데, ‘정시 미처리 요청’은 정해진 기준시간 내에 처리되지 않은 요청이 몇 건인지 집계하여 보여줌으로써 관리자가 추적하여 지연 요소를 제거할 수 있게 도와주고 있는 항목이에요.

↓ 예시

요청종류 기준시간 공통조건
A 전일 17시 ~ 오늘 17시 오늘 기준시간 이전에 생성된 요청
전날 기준시간 이후에 생성된 요청
요청의 상태가 ‘완료(ACCEPTED)’가 아닌 요청
B 전일 12시 ~ 오늘 12시
C 전일 00시 ~ 오늘 00시

조금 더 이해를 돕기 위해 현재 요청을 관리하는 테이블 구조를 설명드리겠습니다.

id(PK) type (요청 종류) status (요청 상태) createdAt (생성 시간)
1 A ACCEPTED (완료) 2024-09-14 21:17:24
2 B PENDING (대기) 2024-09-15 13:49:21
3 C PENDING (대기) 2024-09-15 18:21:51

그럼 A 종류의 정시 미처리를 조회하려면 아래와 같은 쿼리가 발생합니다.

SELECT count(*)
FROM request # 요청 테이블
WHERE request.created < '2024-09-13 17:00:00' # 어제 기준시간(2일전 17시) 이후에 생성된 요청
  AND request.status != 'ACCEPTED' # 완료(ACCEPTED) 상태가 아닌 요청
  AND request.type = 'A'; # 요청 종류 A

하지만 15가지의 요청 타입별로 서로 다른 기준의 createdAt을 적용해 카운트해야 하는 어려움이 있었는데요.
이를 해결하기 위해서는 가장 넓은 범위의 createdAt을 사용하여 쿼리한 후,
애플리케이션 레벨에서 필터링하여 카운팅 하는 방식으로 아래와 같이 처리하였습니다.

fun findNotAcceptedOnTimeRequestCounts(today: LocalDate): Map<RequestType, Int> {
    return requestQueryService.findNotAcceptedOnTime(today)
        .filter { isAcceptedOnTime(it.type, it.createdAt, today) }
        .groupingBy { it.type }
        .eachCount()
}

private fun isAcceptedOnTime(requestType: RequestType, createdAt: LocalDateTime, today: LocalDate): Boolean {
    return when (requestType) {
        // 전일 0시~오늘 0시
        RequestType.C, RequestType.D, RequestType.E ->
            createdAt.isAfter(today.minusDays(1).atStartOfDay()) && createdAt.isBefore(today.atStartOfDay())

        // 전일 12시~오늘 12시
        RequestType.B, RequestType.F ->
            createdAt.isAfter(today.minusDays(1).atTime(12, 0)) && createdAt.isBefore(today.atTime(12, 0))

        // 전일 17시~오늘 17시
        else ->
            createdAt.isAfter(today.minusDays(1).atTime(17, 0)) && createdAt.isBefore(today.atTime(17, 0))
    }
}

그러나, 추후 정시 미처리된 요청들을 카운트 숫자만 보여주는 것에서 나아가, 해당 요청들의 상세 목록을 페이지네이션 형태로 보여줘야 하는 기능이 추가되었습니다.
이때, 기존 방식으로 요청 타입별로 서로 다른 조건을 적용해 필터링하고 리스트로 반환하는 데 어려움이 있었습니다.

이 문제를 해결하기 위해, QueryDSL을 이용해 쿼리 내에 모든 조건을 OR 절로 추가하여 각 요청 타입별 기준 시간에 따라 데이터를 추출할 수 있도록 변경하였습니다.

일반적으로 OR 절을 많이 사용하면 성능 저하가 발생할 수 있지만, 저는 이 작업을 스케줄러를 통해 주기를 유동적으로 변경할 수 있었기 때문에 성능에 미치는 영향은 최소화할 수 있었습니다.

fun findOnGoingNotAcceptedWithPagination(
    requestTypes: List<RequestType>,
    page: Long,
    size: Long,
    today: LocalDate,
): PaginationRequestResult<SimpleRequest> {
    val results = jpaQueryFactory
        .selectFrom(request)
        .where(onGoingNotAcceptedCondition(requestTypes, today))
        .offset(((page - 1) * size))
        .limit(size)
        .orderBy(request.createdAt.asc())
        .fetchResults()
    return PaginationRequestResult(results.results, page, size, results.total)
}

private fun onGoingNotAcceptedCondition(
    requestTypes: List<RequestType>,
    today: LocalDate
): BooleanBuilder {
    val booleanBuilder = BooleanBuilder()
    val createdAtCondition = BooleanBuilder()
    requestTypes.forEach {
        val condition = when (it) {
            // 전일 0시~오늘 0시
            RequestType.C, RequestType.D, RequestType.E ->
                request.createdAt.between(today.minusDays(1).atStartOfDay(), today.atStartOfDay())

            // 전일 12시~오늘 12시
            RequestType.B, RequestType.F ->
                request.createdAt.between(today.minusDays(1).atStartOfDay(), today.atTime(12, 0))

            // 전일 17시~오늘 17시
            else ->
                request.createdAt.between(today.minusDays(1).atTime(17, 0), today.atTime(17, 0))
        }
        createdAtCondition.or(request.type.eq(it).and(condition))
    }
    booleanBuilder.and(request.status.ne(RequestStatus.ACCEPTED))
    booleanBuilder.and(createdAtCondition)
    return booleanBuilder
}

위 내용을 SQL 쿼리로 변환하면 아래와 같습니다.

SELECT request.id,
       request.type,
       request.sort_time
FROM request
WHERE request.status != 'ACCEPTED'
  AND (
    ((request.type = 'C' OR request.type = 'D' OR request.type = 'E') AND request.created_at BETWEEN '2024-09-14 00:00:00' AND '2024-09-15 00:00:00')
        OR
    ((request.type = 'B' OR request.type = 'F') AND request.created_at BETWEEN '2024-09-14 00:00:00' AND '2024-09-15 12:00:00')
        OR
    ((request.type NOT IN ('B', 'C', 'D', 'E', 'F')) AND request.created_at BETWEEN '2024-09-14 17:00:00' AND '2024-09-15 17:00:00')
  )
ORDER BY request.created_at ASC
LIMIT 20;

업무현황 보드의 정시 미처리를 개선하는 과정에서 이러한 접근을 통해
각 요청 타입별로 기준 시간에 따라 데이터를 조회하고 페이지네이션 형태로 상세 목록을 제공할 수 있게 되었습니다.

마치며


이번 과제가 무엇보다 뿌듯했던 점은, 과제를 마친 직후 곧바로 좋은 피드백을 받을 수 있었기 때문이에요.

이 과제가 진행되기 전에는, 위에 언급한 많은 기능들이 사실상 수기로 이루어지고 있었는데요.
예를 들면 작업자가 매일 자신의 근무시간과 처리한 요청 건수를 기재하거나, 관리자가 전체적인 생산성을 가안으로 집계하여 작성하는 방식이었습니다.

그렇다보니 승인 작업자의 인력운용을 전적으로 수기 기입에 의존하고 있어 신뢰도가 매우 낮았고,
전체적으로 어느 정도의 인적 리소스가 투입되어야 맞는지에 대한 판단도 주관적으로 이루어지면서 효율적인 인력 운용이 어려웠어요.

하지만 과제 배포 후 이러한 관리가 시스템 내에서 자동으로 이루어짐에 따라
관리자 기준 일 190분, 작업자 기준 일 900분의 불필요한 업무시간이 감소된 것으로 측정되었습니다.

또한 관리자와 작업자 모두 업무 현황을 효과적으로 트래킹할 수 있게 되면서 요청의 처리율도 크게 개선되었는데요. 광고 신청 요청을 기준으로, 개선 전에는 66.6%였던 요청의 당일 처리율이 개선 후 꾸준히 상승하며 최고 82.0%까지 올라가게 되었어요.

이러한 과정이 모여 사장님이 배달의민족에 좀 더 빠르게 입점할 수 있게 되고,
나아가 많은 사람들이 좋은 가게를 배달의민족에서 만나볼 수 있는 시작점을 세일즈서비스팀에서 마련하고 있다는 점에서 보람있는 과제였습니다 🙂