[EasyShift] 근무 스케줄 관리 서비스ㅤ

노출 페이지
인덱스
5 more properties

1. 배경(Problem)

Target Audience
근무 일정이 매월 변경되는 근로자 및 이를 관리하는 관리자
Pain Points
관리자는 매월 근로자의 휴무일을 반영하여, 수작업으로 스케줄을 생성하고 조정하는 과정이 번거롭고 비효율적이다.
근로자는 근무 일정을 쉽게 확인하거나 이를 공유하기 어렵고, 휴무 변경 시 관리자에게 직접 요청하거나 대체 근무자를 직접 찾아야하는 불편함이 있다.
이에 EasyShift는 다음과 같은 솔루션을 제공합니다.
1.
근무 스케줄링 자동화
관리자는 직원들의 휴무 신청을 반영하여 최적의 근무 스케줄을 자동으로 생성하고 조회할 수 있다.
2.
근로자의 편리한 일정 관리
근무 일정 확인, 변경 요청, 대체 근무 신청 등을 편리하게 할 수 있어 근로자의 커뮤니케이션 부담을 줄인다.
3.
매장 운영의 디지털 전환
매장과 근무 스케줄과 관련된 모든 프로세스를 온라인화 하여, 관리자와 근로자의 매월 반복되는 번거로움을 최소화한다.

2. 서비스 소개(Solution)

1) 회원가입
Administrator / Worker 두 가지 role 중 하나를 선택하여 회원가입이 가능합니다.
2) 매장 생성
유저가 관리할 매장을 생성할 수 있습니다.
3) 스케줄 템플릿 추가
* 스케줄 템플릿: 매장의 매달 반복되는 고정된 스케줄을 찍어낼 ‘도장’과 같은 개념 (e.g. 홀 - 오픈, 마감 / 주방 - 오픈, 미들, 마감)
4) 근로자 초대
관리자는 매장 코드를 근로자에게 전달하여 매장에 초대할 수 있습니다.
5) 스케줄 생성
스케줄 템플릿을 바탕으로 스케줄을 생성할 수 있습니다. 생성할 스케줄의 이름, 날짜, 설명, 각 근무(e.g. 오픈)에 필요한 인원 수 등을 입력합니다. 이렇게 생성한 스케줄은 “Pending” 상태, 근로자의 희망 휴무일을 제출 받는 상태가 됩니다.
6) 희망 휴무일 제출
근로자는 Pending 상태의 스케줄에 희망 휴무일을 제출합니다.
7) 스케줄 자동 생성
EasyShift만의 최적의 근무 일정 자동 배치 알고리즘을 이용하여 근로자들의 희망 휴무일을 반영한 스케줄이 생성됩니다. (알고리즘 시간복잡도: O(N)O(N))
8) 스케줄 수동 조정
자동 생성된 스케줄을 확인하고 수동으로 조정할 수 있습니다. 우측 사이드바에는 해당 매장의 근로자 정보가 제공됩니다.
원하는 날짜의 근무 블록을 눌러, 교체될 근무자를 입력합니다.
9) 간편한 근무 확인
메인 페이지에서는 무한 스크롤 형태의 주간 캘린더로 생성된 스케줄을 확인할 수 있습니다. 우측 상단의 드롭다운으로 특정 포지션(e.g. 홀, 주방)의 스케줄을 선택할 수 있습니다.
토글로 본인의 근무만 확인할 수 있습니다.
관리자는 근로자 블록을 눌러 해당 근로자의 월간 스케줄 정보를 한눈에 확인할 수 있습니다.

3. 활용 라이브러리 및 개발 환경

프론트엔드는 Next.js를 기반으로 구축되었습니다.
대량의 데이터를 효율적으로 처리하기 위해 서버 사이드에서 데이터를 가져오는 방식을 적용하였으며,
이를 통해 캘린더 이동 시에도 부드럽고 원활한 사용자 경험을 제공할 수 있도록 설계했습니다.
안정적인 개발 환경을 위해 TypeScript를 도입하여 코드의 안정성을 높였고, 예측 가능한 타입 기반 개발을 통해 유지보수성과 협업 효율성을 강화했습니다.
스타일링에는 Tailwind CSS를 활용하여 간결하면서도 일관된 UI를 구현했고, 데이터 관리에는 TanStack Query를 적용하여 데이터 캐싱을 최적화했습니다. 이를 통해 불필요한 API 요청을 최소화하여 로딩 속도를 개선하고 사용자 경험을 향상시켰습니다.
또한 개발 생산성을 높이기 위해 MSW(Mock Service Worker)를 도입하여 API 요청을 가상화함으로써, 프론트엔드와 백엔드 개발을 동시에 진행할 수 있는 환경을 구축했습니다.
백엔드는 Spring Boot 3.4.2JDK 21을 기반으로 구축되었습니다.
최신 LTS 버전인 JDK 21을 사용해 성능과 보안이 강화된 환경을 구축하였고, Spring Boot를 활용해 효율적인 개발과 편리한 의존성 관리가 가능하도록 했습니다.
MySQL을 기반으로 안정적인 데이터 관리를 수행하였고, Hibernate(JPA)를 활용해 데이터베이스 연동을 간소화하여 유지보수성을 높였습니다.
또한 Swagger를 통한 API 문서 자동화를 통해 프론트엔드와의 협업을 더욱 원활하게 진행할 수 있도록 했습니다.

4. 트러블 슈팅

FE
첫 번째로 복잡한 비즈니스 로직을 가진 컴포넌트 구조를 개선했습니다.
캘린더와 같이 복잡한 비즈니스 로직이 얽힌 컴포넌트에서 상태값이 많아지면서 불필요한 리렌더링이 발생하고, UI 로직과 데이터 로직이 한 곳에 섞이면서 단일 책임 원칙(SRP)이 지켜지지 않는 상황이었습니다. 또한, Props Drilling 문제로 중복 데이터 관리가 비효율적인 점도 개선이 필요했습니다.
이 문제를 해결하기 위해,
UI와 데이터 로직을 분리하여 캘린더 컴포넌트는 UI만 담당하도록 했고,
useSuspenseQuery를 적용해 데이터가 보장된 상태에서 UI를 렌더링하도록 개선했으며,
Context API를 활용해 전역 상태를 관리하면서 Props Drilling 문제를 해결했습니다.
이렇게 구조를 개선한 결과, 코드의 가독성이 높아지고 유지보수도 훨씬 쉬워졌으며, 성능 최적화까지 가능해졌습니다.
두 번째로 API 요청 최적화와 사용자 경험을 동시에 개선했습니다.
저희 서비스에서는 주간 캘린더에 불러오는 데이터가 많아 API 요청을 일주일 단위로 수행했습니다.
하지만, 사용자가 내비게이터를 빠르게 연타하면 과도한 API 요청이 발생성능 저하가 우려되었고,
이를 방지하기 위해 디바운싱을 적용하면 API 요청은 최적화되지만, UI 업데이트도 함께 지연되는 문제가 발생했습니다.
이를 해결하기 위해 UI 상태(currentDates)와 API 요청 상태(debouncedDates)를 분리하는 방식을 적용했습니다.
UI 상태는 즉시 업데이트하여 유저가 변경 사항을 바로 확인할 수 있도록 하고,
API 요청 상태는 useDebounce를 적용해 일정 시간 동안 변경이 없을 때만 실행하도록 최적화했습니다.
이렇게 구현한 결과, 불필요한 API 요청을 줄이면서도 유저가 즉각적인 피드백을 받을 수 있어 성능과 사용자 경험을 모두 개선할 수 있었습니다.
BE
백엔드에서는 체계적인 코드 검토를 통해 버그를 사전에 방지하고, 유지보수성을 높이는 것을 중요한 목표로 삼았습니다.
일례로 기존에는 ScheduleTemplate에서 ShiftTemplate 리스트를 직접 관리하고 있었습니다.
이렇게 되면서 관리 주체가 불분명해지고, setter 메서드가 많아지면서 코드가 점점 복잡해지는 문제가 발생했습니다.
이 문제를 해결하기 위해, ShiftTemplates라는 별도 클래스를 분리해서 역할을 명확히 했습니다.
먼저, 코드의 응집도를 높이기 위해 ShiftTemplates가 데이터 관리를 담당하도록 했고,
불필요한 setter 메서드를 제거해서 객체의 불변성을 유지할 수 있도록 했습니다.
또, ScheduleTemplate이 리스트를 직접 조작하는 것이 아니라, ShiftTemplates를 통해 관리하도록 개선하여 가독성과 유지보수성이 훨씬 좋아졌습니다.
이렇게 구조를 개선한 결과, 코드가 더 단순하고 명확해졌고, 변경이 발생해도 예측 가능하고 안정적인 시스템을 유지할 수 있게 되었습니다.
또한 코드가 변경될 때 예상치 못한 오류를 방지하고, 안정적인 개발 환경을 유지하기 위해 단위 테스트를 적극적으로 도입했고, 기존 기능이 정상적으로 동작하는지 보장하면서 안전하게 코드 개선이 가능하도록 설계했습니다.
기존에는 테스트 코드가 한 번에 모든 로직을 검증하면서,
가독성이 떨어지고,
유지보수가 어려운 문제가 있었습니다.
이를 해결하기 위해 Given-When-Then 패턴을 적용했습니다.
테스트 목적을 명확하게 구분하여, 테스트 코드가 훨씬 직관적으로 구성되었고,
각 단계가 분리되어 로직을 쉽게 이해할 수 있게 되면서,
변경 사항이 생겨도 유연하게 대응할 수 있도록 개선했습니다.
결과적으로, 테스트의 흐름이 명확해지고 유지보수성이 크게 향상되었으며, 코드 변경이 발생해도 쉽게 이해하고 수정할 수 있는 테스트 환경을 구축할 수 있었습니다.
저희 서비스의 핵심인 스케줄링 알고리즘에 대해 말씀드리겠습니다.
효율적인 근무 배정을 위해 무조건적으로 고려해야 할 두 가지 핵심 요소가 있었습니다.
첫째, 근무가 균등하게 배정될 것
둘째, 동일한 시간대에 중복 배정이 발생하지 않을 것
이와 함께, 근로자의 휴무 신청과 근무 로테이션 순서도 최대한 반영하여,
공정하면서도 유연한 스케줄링이 가능하도록 알고리즘을 최적화했습니다
알고리즘의 시간 복잡도는 N을 근무 수, M을 유저 수로 두었을 때 가장 최악의 경우 모든 근무에 대해 모든 유저를 검사해야 하는 경우가 발생합니다.
이때의 시간 복잡도는 O(N * M log M)이며,
이는 모든 유저가 휴무일 경우에 해당됩니다.
하지만, 일반적인 경우에는 몇 개의 후보만 검사하면 배정이 완료되기 때문에,
실제 소요 시간은 O(N log M) 수준으로 최적화됩니다.
테스트 데이터는
900개의 근무 배정, 20명의 유저이고 각 유저는 3일의 휴무를 포함하고 있습니다.
실행 방식은
JVM 워밍업 3회, 측정 반복 10회입니다.
테스트 결과
평균 실행 시간은 0.114ms이며
총 실행 시간은 워밍업 포함 2분 10초입니다.
결론적으로
순수 알고리즘 성능은 실행 시간이 0.1ms 내외로 최적화되어 매우 빠릅니다.
테스트 결과 또한 오차 범위 ±0.003ms 내외로 안정적임을 확인했습니다.
마지막으로, 스케줄링 API의 성능 테스트 결과입니다.
부하 테스트 도구는 nGrinder를 사용했고,
서버 스펙은 CPU 2코어, RAM 2GB이며
1대의 컨트롤러 + 2대의 에이전트 구성으로 테스트를 진행했습니다.
테스트 대상은 근무를 배정하는 API입니다.
테스트 결과는
초당 처리량 평균 93.6, 최고 112이며
평균 응답 시간은 1,004.14ms로 약 1초에 해당됩니다.
총 요청 수는 24,708건, 성공률 100%였습니다.
즉, 현재 서버 스펙(2코어, 2GB)에서도 충분한 성능을 제공하며,
100TPS 수준에서도 1초 내 응답이 가능한 안정적인 성능을 보였습니다.
또한, 성공률 100%로 높은 안정성을 확인할 수 있었습니다.
이를 통해, 실제 서비스 운영 환경에서도 충분한 성능을 보장할 수 있음을 검증했습니다.
INFRA
EasyShift의 인프라는 DigitalOcean이라는 클라우드 서비스를 이용해 배포했습니다.
아마 생소할 수도 있는데요. 사실 원래 AWS를 사용하려 했지만,
배포 담당 팀원의 AWS 프리 티어가 만료되는 바람에
무료 크레딧을 제공하는 DigitalOcean을 선택하게 되었습니다.
하지만, 향후 AWS로 이전할 가능성도 고려해야 했기 때문에
서버 구성과 관리를 코드로 자동화하는 방식으로 구축했습니다.
이를 위해 IaC(Infrastructure as Code) 도구를 활용했으며,
특히 Terraform과 Ansible을 사용했습니다.
Terraform
클라우드 리소스를 코드로 정의하고 생성하기 위해 사용했습니다.
DigitalOcean의 가상 서버(Droplet), 고정 IP, 네트워크 환경 등을 자동으로 구성하여 언제든지 동일한 환경을 빠르게 다시 만들 수 있도록 했습니다.
Ansible
서버 내부 환경을 자동화하는 데 활용했습니다.
사용자 계정 생성, Docker 설치, 데이터베이스 초기 설정 같은 작업을스크립트 형태로 관리하여 휴먼 에러를 최소화했습니다.
향후 AWS로 이전하더라도 동일한 환경을 빠르게 세팅할 수 있는 기반을 마련했습니다.
이렇게 인프라를 코드로 관리하면서 여러 가지 이점을 얻을 수 있었습니다.
버전 관리가 가능해 변경 사항을 추적하고 필요할 때 롤백할 수 있으며,
새로운 환경을 구축할 때도 일관성을 유지할 수 있습니다.
또한, 다른 클라우드로 이전하더라도 안정적이고 효율적으로 옮길 수 있습니다.
결과적으로, 유연하고 확장성 있는 인프라 환경을 구축할 수 있었습니다.
저희 서비스는 코드 변경 사항이 자동으로 서버에 반영되도록
CI/CD 파이프라인을 구축하여 운영하고 있습니다.
덕분에, 개발자는 별도의 배포 과정 없이 코드 푸시만으로 자동 배포가 가능하도록 만들었습니다.
개발자가 코드를 GitHub에 푸시하면, GitHub Actions가 자동으로 실행됩니다.
저희는 main 브랜치와 develop 브랜치를 기반으로 운영하고 있는데,
main 브랜치는 실제 서비스가 운영되는 배포 환경
develop 브랜치는 새로운 기능을 테스트하는 개발 환경
따라서, 어떤 브랜치에 푸시하느냐에 따라 배포 대상이 자동으로 결정됩니다.
CI/CD 파이프라인 상세 과정을 살펴 보자면,
Docker로 이미지를 빌드하고
DockerHub에 저장합니다.
이후 GitHub Actions가 SSH로 서버 접속하여
기존 컨테이너를 중지 후 새로운 컨테이너로 교체합니다.
OAuth 설정 및 환경변수도 적용해줍니다.
배포 속도 최적화
운영을 해보니, 간단한 코드 수정도 빌드 시간이 오래 걸리는 문제가 있었습니다.
이를 개선하기 위해,
PR을 main이나 develop 브랜치에 보낼 때 미리 도커를 빌드하고
빌드 캐시를 활용해 기존에 8분 걸리던 배포 시간을 1분으로 약 88% 단축했습니다.
결과적으로
코드 푸시 한 번으로 모든 배포 과정이 자동화되어,
개발자는 배포 부담 없이 개발에만 집중할 수 있었습니다.
또한 배포 속도가 빨라지고, 안정적인 서비스 운영할 수 있게 되었습니다.
마지막으로, EasyShift의 서버는 컨테이너 기반으로 구성되어 있습니다.
이렇게 각 컴포넌트가 독립적으로 운영해서 관리와 확장하기 쉽게 만들었습니다.
우선은 첫 번째 계층은 주황색으로 표시한 Nginx 리버스 프록시로, HTTPS 암호화를 제공하고 사용자 요청을 적절한 백엔드 서비스로 라우팅하는 것입니다.
HTTPS는 Let's Encrypt에서 제공하는 무료 SSL 인증서를 사용했습니다.
그리고 운영 환경과 개발 환경을 구분해서, 운영 환경 요청은 로컬호스트의 81번 포트, 개발 환경 요청은 로컬호스트의 8081번 포트로 전달되도록 설정했습니다.
그 다음은 서버 애플리케이션 컨테이너들입니다. 여기에는 실제 서비스가 돌아가는 운영 환경 컨테이너 ‘easyshift_prod’와, 새로운 기능을 테스트하는 개발 환경 컨테이너 ‘easyshift_dev’가 있습니다.
두 환경이 서로 완전히 분리된 상태로 실행되도록 구성했습니다.
MySQL 데이터베이스 컨테이너입니다. 컨테이너가 꺼지거나 재시작 되더라도 데이터가 유지되도록 Docker 볼륨을 활용했고 운영과 개발 환경에서 각각 별도의 데이터베이스를 사용해서 완전히 분리해 두었습니다.
이렇게 구축해두니 개발자들이 운영 로그를 보기 힘들었습니다. 그래서 마지막으로 모든 컨테이너의 상태와 로그를 실시간으로 확인할 수 있도록 Dozzle이라는 모니터링 시스템을 적용했습니다. 덕분에 문제가 발생하면 굳이 서버에 접속하지 않아도 바로 로그를 확인하고 빠르게 대응할 수 있었습니다.
이런 구조 덕분에 운영이 안정적이고, 개발 속도도 훨씬 빨라졌습니다.

5. 팀 소개

조윤해 FE
ISFP
2000. 08. 29.
susu12356@gmail.com
고주형 INFRA
INTP
1998. 10. 03.
dury.ko@gmail.com
김찬호 BE
ISTJ
1997. 09. 03.
nh0903@pusan.ac.kr
이영재 BE
ISTJ
2000. 01. 14.
zerojae175@gmail.com
조장호 BE
INTJ
1997. 05. 19.
26dev@naver.com