한꺼번에 몰아치던 배치를 떠나 개별 흐름 워크플로로 전환한 개발 이야기

대형 주문 관리 프로그램의 내부를 살펴보면 당장 처리하는 일보다 시간이 흘러야 비로소 실행되는 작업이 놀라울 정도로 많습니다. 손님이 주문을 넣었는데 정해진 시간 동안 가게에서 받지 않으면 알림을 울려야 하고 응답이 없으면 취소해야 하며 음식이 나온 뒤 일정 시간이 지나면 끝맺음을 지어야 합니다. 이처럼 오랜 시간을 거치며 상태가 바뀌는 구조를 과거에는 정해진 주기에 따라 일괄적으로 훑어보는 방식으로 해결하곤 했습니다. 일괄 처리 방식은 주기마다 조건에 맞는 대상을 한 무더기로 가져와 다루면 되기 때문에 언뜻 보기에 매우 편리합니다. 하지만 손님이 몰려드는 바쁜 시간대가 찾아오면 한 번에 살펴봐야 할 대상이 폭발적으로 늘어나면서 전체 흐름에 큰 부담을 주게 됩니다. 바로 이 지점이 모든 것을 한꺼번에 짊어지던 기존 방식의 한계이며 이를 극복하기 위한 새로운 흐름 제어 도구 개발에 착수했습니다. 이 글에서는 주문 하나를 각각 독립된 흐름으로 분리하여 바쁜 시간대에도 부드럽게 소화할 수 있도록 만든 독자적인 작업 제어 도구의 탄생 과정을 공유하려 합니다.

=

한꺼번에 몰아치던 배치를 떠나 개별 흐름 워크플로로 전환한 개발 이야기

한꺼번에 몰아치던 배치를 떠나 개별 흐름 워크플로로 전환한 개발 이야기

1. 배치 방식의 한계와 새로운 흐름의 필요성

주문 접수와 배달 과정을 관리하는 프로그램에서 시간 경과에 따른 처리는 피할 수 없는 핵심 기능 중 하나입니다. 주문이 들어온 시점부터 가게의 접수 여부를 확인하고 일정 시간이 지났을 때 경고를 보내는 일은 서비스의 품질을 좌우합니다. 전통적인 방식은 일정 주기로 데이터베이스를 조회하여 조건에 맞는 주문들을 한꺼번에 불러와 처리하는 구조를 택했습니다. 이 방식은 구현이 단순하고 초기 개발 속도가 빠르다는 장점이 있어 수많은 현장에서 오랫동안 표준처럼 쓰여왔습니다. 그러나 주문량이 급증하는 피크 시간대가 되면 이 일괄 처리 방식은 치명적인 약점을 드러내기 시작합니다. 한 번에 처리해야 할 데이터의 양이 늘어나면서 서버가 감당해야 할 부하가 선형 이상으로 치솟고 전체 처리 속도가 급격히 떨어집니다.

특정 주문 하나에 문제가 생기거나 연산이 지연되면 뒤따라오는 전체 주문들의 처리까지 줄줄이 늦어지는 현상이 발생합니다. 이러한 문제를 근본적으로 해결하기 위해서는 모든 주문을 커다란 덩어리로 묶어 처리하던 방식에서 벗어나야 했습니다. 주문 하나하나가 각자의 생명 주기를 가지고 독립적으로 흘러갈 수 있다면 전체 시스템의 안정성은 크게 높아질 것입니다. 마치 거대한 컨베이어 벨트 위에 모든 물건을 올려두고 한 번에 움직이는 대신 개별 바구니에 담아 각각의 경로로 이동시키는 것과 같습니다. 이러한 고민 끝에 우리는 거대한 일괄 작업을 내려놓고 각각의 작업이 독립적인 흐름을 가지는 새로운 구조를 구상하게 되었습니다.

💡 핵심 포인트
일괄 처리는 바쁜 시간대에 거대한 부하를 유발하므로 개별 흐름으로 쪼개는 전환이 필요합니다.

2. 유명 워크플로 엔진 검토와 마주한 현실적인 장벽

오랫동안 유지되는 작업을 안정적으로 다루기 위해 가장 먼저 살펴본 대안은 업계에서 널리 쓰이는 유명 워크플로 엔진이었습니다. 해당 엔진은 복잡하고 긴 시간을 요구하는 작업을 코드 수준에서 안전하게 다룰 수 있도록 훌륭한 추상화를 제공하는 도구입니다. 개발자는 흐름을 정의하는 작업과 외부 세계를 호출하는 동작을 평범한 함수처럼 작성하기만 하면 되므로 매우 매력적으로 다가왔습니다. 프로세스가 도중에 죽거나 재배포되어도 서버에 남은 실행 기록을 바탕으로 중단된 지점부터 정확히 이어갈 수 있는 신뢰성을 보장했습니다. 재시도와 시간 제한, 신호 대기 같은 복잡한 안정성 처리를 엔진이 대신 맡아주기 때문에 개발자는 핵심 비즈니스 로직에만 집중할 수 있었습니다.

하지만 실제 현업에 도입하려고 면밀히 검토해 보니 몇 가지 넘기 힘든 커다란 장벽과 부담감이 존재한다는 사실을 깨달았습니다. 가장 큰 문제는 팀 전체가 새로운 개념을 익혀야 하는 높은 학습 곡선이었으며 실행 기록을 복원하는 과정에서 지켜야 할 엄격한 규칙이 있었습니다. 코드 안에서 현재 시각이나 난수를 마음대로 쓸 수 없는 제약이 존재하여 팀원 모두가 이 방식을 숙지하는 데 적지 않은 시간과 노력이 필요했습니다. 또한 관리형 서비스를 이용하지 않고 직접 내부 구조를 구축하려면 여러 개의 하부 서비스와 데이터베이스를 조합해야 하는 복잡함이 따랐습니다. 사용할 수 있는 데이터베이스 종류가 제한적이어서 기존에 능숙하게 다루던 도구 외에 새로운 저장소를 추가로 운영해야 하는 부담도 무시할 수 없었습니다.

💡 핵심 포인트
기존 엔진은 강력하지만 높은 학습 비용과 별도 인프라 운영 부담으로 인해 직접 도입하기에 주저함이 있었습니다.

3. 자체 제작 도구의 탄생과 단순한 구조의 설계

기존 엔진을 그대로 도입하기에는 우리 환경에 너무 과분하고 복잡하다는 판단 아래 우리만의 가벼운 도구를 직접 만들기로 결정했습니다. 이름하여 자체적인 작업 제어 라이브러리를 설계하기 시작했으며 가장 중요하게 여긴 가치는 단순함과 직관성이었습니다. 거대한 외부 도구에 종속되어 나중에 걷어내기 힘들게 되는 위험을 피하고 우리 시스템에 딱 맞는 옷을 입히고 싶었습니다. 가장 먼저 적용해 본 대상은 복잡한 다단계 업무가 아니라 가장 단순한 형태인 주문 접수 지연 알림 기능이었습니다. 이 작은 기능부터 시작하여 점차 범위를 넓혀간다면 충분히 안정적인 시스템을 구축할 수 있을 것이라는 확신이 있었습니다.

새롭게 만든 라이브러리는 거대한 일괄 처리 루프를 완전히 제거하고 개별 주문 건수가 하나의 독립된 작업 흐름으로 동작하도록 설계되었습니다. 주문이 생성되는 순간 고유한 흐름이 시작되고 정해진 시간 동안 대기하면서 가게의 응답을 기다리게 구현되었습니다. 대기 시간이 끝났을 때 조건이 충족되지 않았다면 미리 정의된 다음 단계로 자연스럽게 넘어가 경고 알림을 보내거나 취소를 수행합니다. 이 모든 과정이 거대한 일괄 작업에 묶이지 않고 개별 주문의 식별자를 기반으로 독립적으로 움직이기 때문에 특정 주문에 문제가 생겨도 다른 주문에는 전혀 영향을 주지 않습니다. 복잡한 외부 인프라를 추가로 구축할 필요 없이 기존에 사용하던 친숙한 데이터베이스 환경 위에서 그대로 동작하도록 만들어 운영의 부담을 최소화했습니다.

💡 핵심 포인트
복잡성을 덜어내고 기존 데이터베이스 위에서 가볍게 동작하는 자체 제어 도구를 설계했습니다.

4. 실제 서비스 적용 과정에서 마주한 도전과 해결책

새로운 제어 도구를 실제 운영 환경에 적용하는 과정은 생각보다 섬세한 주의와 철저한 검증을 요구하는 작업이었습니다. 가장 먼저 마주한 과제는 서버가 예기치 않게 재시작되거나 배포가 일어날 때 진행 중이던 작업들이 유실되지 않도록 보장하는 것이었습니다. 일괄 처리 방식에서는 서버가 죽었다 살아나면 다음 주기 대상을 다시 조회하면 그만이었지만 개별 흐름 방식에서는 현재 상태를 정확히 기록해야 했습니다. 이를 위해 각 흐름이 진행되는 단계마다 데이터베이스에 현재 상태를 안전하게 저장하고 다음 단계로 넘어갈 때 원자성을 유지하도록 꼼꼼하게 코드를 다듬었습니다.

또한 동시에 수많은 주문이 몰리는 피크 시간대에도 데이터베이스가 과부하에 걸리지 않도록 흐름의 실행 주기를 분산시키는 전략을 도입했습니다. 작업이 한꺼번에 몰리지 않고 조금씩 시차를 두고 실행되도록 지연 시간을 조절하는 장치를 마련하여 시스템 전체의 안정성을 높였습니다. 개발 과정에서 발생할 수 있는 다양한 예외 상황을 가정하여 수많은 단위 시험과 통합 시험을 거치며 코드의 완성도를 차근차근 끌어올렸습니다. 동료 개발자들과 함께 코드를 검토하며 혹시라도 놓친 예외 처리가 없는지 거듭 확인하고 실제 운영 환경과 유사한 조건에서 성능을 시험했습니다. 이러한 철저한 검증 과정을 거친 덕분에 실제 서비스에 반영된 이후에도 큰 장애 없이 안정적으로 동작하며 우수한 성능을 보여주었습니다.

💡 핵심 포인트
철저한 상태 기록과 부하 분산 전략을 통해 실제 운영 환경에서의 안정성을 성공적으로 확보했습니다.

5. 개별 흐름 구조로 전환한 뒤 얻어낸 극적인 변화

일괄 처리 방식을 내려놓고 개별 흐름 구조로 전환한 이후 우리 개발팀과 서비스 환경에는 눈에 띄는 긍정적인 변화들이 찾아왔습니다. 무엇보다 바쁜 시간대에 서버가 겪던 커다란 압박감이 거짓말처럼 사라졌으며 전체적인 시스템의 반응 속도가 눈에 띄게 빨라졌습니다. 과거에는 문제가 생기면 전체 주문 흐름이 멈추거나 지연되는 통에 발을 동동 구르곤 했지만 이제는 문제가 발생한 특정 주문만 안전하게 격리되어 처리됩니다. 디버깅 과정 또한 훨씬 수월해졌는데 특정 주문의 고유 식별자만 알면 그 주문이 어떤 경로를 거쳐 현재 상태에 이르렀는지 한눈에 추적할 수 있게 되었습니다.

개발자들의 업무 만족도와 생산성 역시 크게 향상되었는데 복잡하고 거대한 외부 엔진의 규칙에 얽매이지 않고 우리 손으로 만든 직관적인 코드를 다루기 때문입니다. 새로운 기능을 추가하거나 기존 로직을 수정할 때 거대한 일괄 처리의 눈치를 볼 필요 없이 독립된 흐름 파일만 가볍게 고치면 되었습니다. 다른 비즈니스 영역에서도 이러한 개별 흐름 구조를 적용해 달라는 요청이 이어지고 있으며 점차 적용 범위를 넓혀가는 중입니다. 거대한 일괄 처리라는 익숙하지만 무거운 짐을 내려놓고 하나씩 부드럽게 흘려보내는 방식을 택한 것은 개발 인생에서 손꼽히는 현명한 선택이 되었습니다.

💡 핵심 포인트
개별 흐름 전환 후 부하 감소와 쉬운 추적 덕분에 개발 생산성과 시스템 안정성이 동시에 높아졌습니다.

6. 성장하는 시스템을 위한 유연한 설계의 전망

복잡한 문제를 해결할 때 세상에 이미 잘 알려진 거대하고 화려한 기술을 무조건 도입하는 것이 능사는 아니라는 교훈을 얻었습니다. 우리 팀의 규모와 역량, 그리고 실제 마주한 문제의 본질을 정확히 파악하고 그에 딱 맞는 가장 단순한 형태의 해결책을 찾는 것이 훨씬 중요합니다. 이번에 만든 자체 제어 라이브러리는 거창한 외부 엔진에 비해 기능이 소박할지 모르지만 우리 시스템에 가장 알맞은 맞춤옷처럼 훌륭하게 제 역할을 해내고 있습니다. 앞으로도 기술적인 도전 과제를 만났을 때 맹목적으로 유행을 좇기보다는 우리 팀의 상황에 가장 합리적인 길을 고민하는 태도를 유지할 것입니다.

이 글을 읽고 계신 독자 여러분 중에서도 혹시 매일 밤 거대한 일괄 처리 작업 때문에 가슴 졸이고 계신 분이 있다면 과감히 방식을 쪼개는 시도를 권해드립니다. 모든 것을 한꺼번에 짊어지려 애쓰기보다 하나씩 가볍게 흘려보내는 구조로 시선을 바꾸는 순간 생각보다 훨씬 큰 여유와 안정감을 만나게 될 것입니다. 앞으로도 현장에서 겪은 소중한 경험과 고민들을 꾸준히 나누며 함께 성장하는 개발 문화를 만들어 나가는 데 힘을 보태겠습니다. 작고 단단한 코드 조각들이 모여 거대한 서비스를 지탱하듯 우리의 작은 시도들이 모여 더 나은 기술 환경을 완성해 갈 것을 믿어 의심치 않습니다. 새로운 기술적 도전을 망설이지 마시고 오늘 당장 여러분의 시스템에서 가벼운 혁신을 시작해 보시기를 바랍니다.

💡 핵심 포인트
맹목적인 유행 도입보다 팀 상황에 맞는 단순하고 유연한 설계를 선택하는 태도가 중요합니다.

자주 묻는 질문

일괄 처리 방식에서 개별 흐름으로 바꾼 가장 큰 이유는 무엇인가요?
바쁜 시간대에 모든 작업을 한꺼번에 훑어 처리하면서 발생하는 심한 부하와 장애 전파 현상을 근본적으로 해결하기 위해서입니다.
유명한 워크플로 엔진을 도입하지 않고 직접 만든 이유는 무엇인가요?
높은 학습 비용과 엄격한 제약 조건 그리고 별도의 인프라를 직접 운영해야 하는 현실적인 부담이 컸기 때문입니다.
개별 흐름 방식으로 바꾼 뒤 어떤 점이 가장 좋아졌나요?
특정 주문에 문제가 생겨도 다른 주문에 영향을 주지 않고 독립적으로 격리되며 문제 추적이 훨씬 수월해졌습니다.
이 방식을 다른 업무 영역에도 확대할 계획인가요?
네, 이미 검증을 마쳤으므로 시간이 흐르며 상태가 변하는 다른 여러 비즈니스 영역으로 점차 적용 범위를 넓혀가고 있습니다.

=

🔗 함께 읽으면 좋은 글: 신용대출 한도조회 신용점수 하락 진실과 금리 낮추는 방법 총정리