Backend

AI와 함께 OMS의 Writer connection을 15.9% 줄인 방법

AI의 작업 단위를 사람이 한 번에 검수할 수 있는 크기로 제한했습니다.

Kyungyeon Byun (경연)Kyungyeon Byun (경연)
·
# Refactoring# SOLID# Software Design
AI와 함께 OMS의 Writer connection을 15.9% 줄인 방법
date
slug
author
status
tags(최대 3개)
summary
type
thumbnail
category
updatedAt
 
한 줄 요약
AI의 작업 단위를 사람의 검수 범위로 제한하고 작은 배포를 반복해, 멈출 수 없는 OMS의 구조와 성능을 안전하게 개선했습니다.

잘 동작하는 시스템도 계속 개선해야 합니다

OMS(Order Management System)는 이미 주문을 정상적으로 처리하고 있었습니다. 장애를 복구하거나 당장 새 기능을 만들어야 하는 상황도 아니었습니다.
다만 지금 문제없는 것과 앞으로 안전하고 빠르게 변경할 수 있는 것은 다른 문제이므로 코드를 다시 살펴봤습니다.
주문 한 건에는 상품, 회원, 혜택, 배송, 구독, 재고, 외부 판매 채널 등 여러 도메인이 관여합니다. 흐름을 추적해 보니 같은 정보를 여러 단계에서 반복 조회했고 상품 수가 늘수록 검증 쿼리도 증가했습니다. 주문 준비부터 저장, 재고 반영, 후처리까지 여러 책임이 한 흐름에 모여 있었습니다.
책임이 한 흐름에 몰린 이 구조는 트래픽이 몰릴 때 DB 부하를 키웁니다. 정책 하나를 바꿀 때도 수정·리뷰·테스트 범위가 넓고 한 영역의 변경이 주문 전체로 번질 가능성이 큽니다.
이번 작업의 목표는 성능 개선만이 아니었습니다.
  • 반복 조회와 병목 줄이기
  • 책임과 경계 분명히 하기
  • 기존 동작을 지키며 안전하게 변경
  • 운영 지표로 개선 효과 검증
하나의 주문 흐름에 조회·검증·저장·후처리 책임이 얽혀 있던 구조
하나의 주문 흐름에 조회·검증·저장·후처리 책임이 얽혀 있던 구조

책임을 분리해 변경 영향을 통제했습니다

기존 주문 생성 흐름에는 정보 조회, 정책 검증, 주문 데이터 조립, 저장, 재고 반영, 외부 연동과 후처리가 한곳에 모여 있었습니다. 코드가 한곳에 있다는 사실보다 더 큰 문제는 서로 다른 이유로 바뀌는 로직이 같은 클래스에 묶여 있었습니다.
notion image

변경 범위를 예측할 수 있는 SOLID 원칙

SOLID를 적용하는 목적은 클래스와 파일의 수를 늘리는 데 있지 않습니다. 변경이 발생했을 때 수정 범위와 영향을 예측할 수 있도록 경계를 만드는 데 있습니다. 이번 작업에서는 단일 책임 원칙(SRP), 개방-폐쇄 원칙(OCP), 의존 역전 원칙(DIP)을 중심으로 구조를 정리했습니다.

SRP: 클래스의 변경 이유를 하나로 제한

하나의 클래스가 배송 정책, 회원 혜택, 저장, 외부 연동을 모두 담당하면 정책이 바뀔 때마다 같은 클래스를 수정해야 합니다. 변경 이유가 많을수록 회귀 범위가 넓어지고 다시 확인해야 할 테스트도 불분명해집니다.
조회 컨텍스트 구성, 정책 검증, 주문 저장, 후처리를 분리하면 각 구성 요소가 하나의 업무 변화에 집중합니다. 배송 정책 변경은 배송 검증 영역에서 끝나고 재고 반영 방식의 변경은 주문 데이터 조립 코드로 번지지 않습니다.

OCP: 기존 흐름을 흔들지 않고 정책을 확장

새로운 주문 유형이나 검증 정책을 추가할 때마다 주문 생성 전체를 수정하면 기존 주문 경로도 영향을 받습니다. 정책별 경계를 나누면 구현을 추가하거나 교체하는 방식으로 확장할 수 있습니다. 이미 검증한 오케스트레이션과 저장 흐름은 그대로 유지합니다.

DIP: 구체적인 구현보다 계약에 의존

주문 코드가 상품, 회원, 재고 영역의 데이터 구조와 조회 방식까지 알면 다른 영역의 내부 변경이 주문 코드로 전파됩니다. 주문은 구체적인 저장소나 조회 구현 대신 필요한 입력과 결과를 정의한 인터페이스에 의존하도록 바꿨습니다.
계약에 의존하는 이 경계 덕분에 실제 DB와 외부 시스템을 모두 실행하지 않고도 계약 단위로 동작을 검증할 수 있습니다. 책임 분리는 코드를 정리하는 일이 아니라 변경 영향과 테스트 범위를 통제하는 일입니다.
업무 의미에 따라 책임과 테스트 경계를 분리한 구조
업무 의미에 따라 책임과 테스트 경계를 분리한 구조

읽기 좋은 코드는 테스트 범위도 분명

책임이 작아지면 입력과 출력이 분명해지고 테스트 범위도 줄어듭니다. 주문 생성 전체를 실행해야 확인할 수 있던 정책을 작은 단위로 검증할 수 있습니다.
테스트하기 좋은 코드는 우연히 만들어지지 않습니다.
  • 외부 의존성을 경계 밖으로 밀어내기
  • 함수와 클래스는 하나의 판단에 집중
  • 같은 입력에 같은 결과를 반환
  • 부수 효과가 발생하는 지점을 명시
  • 실패 조건과 예외를 코드에 드러내기
입출력이 분명한 코드는 사람의 리뷰 속도를 높이고 AI가 변경 의도를 파악하는 데도 유리합니다. 읽기 좋은 코드가 있어야 사람과 AI가 같은 의도를 읽고 협업합니다.

같은 고민하는 동료를 위한 실험을 합니다

아임웹은 비즈니스 조직인 스쿼드의 목표에는 속하지 않지만, 제품의 성장에 임팩트를 낼 수 있다고 판단되면 길드를 구성해 자유롭게 실험할 수 있습니다.
이 작업은 스쿼드의 약속된 일정을 중단할 만큼 긴급하지 않았습니다. 정상 운영 중인 시스템의 기반을 다지는 일은 기능 개발에 밀려 뒤로 가기 쉽고 미룰수록 다음 변경 비용이 커집니다.
OMS는 아임웹에 합류했을 당시부터 기여한 제품입니다. 다른 제품에 집중한 기간에도 쌓여가는 문제와 병목을 계속 살폈습니다. 제품의 맥락과 구조를 오래 이해한 만큼 문제를 깊이 파고들 수 있다고 판단했습니다.
그래서 스쿼드 일정에 영향이 없도록 작은 실험을 위한 길드를 개설했습니다.. 조사 내용, 변경 이유, 검증 결과와 다음 개선 후보를 공개해 동료가 검토하고 이어갈 수 있도록 기록했습니다.

사람의 검수를 기준으로 AI 작업 환경을 설계했습니다

AI는 반복되는 코드 패턴을 찾고 책임 분리 후보를 정리하고 테스트 사례를 보완했습니다. 다만 OMS는 주문·결제·재고의 일관성을 다뤄 한 번 틀어지면 되돌리기 어렵습니다. AI가 만든 변경을 그대로 신뢰할 수는 없습니다.
AI는 작업 속도를 높이는 도구이며 변경의 책임은 개발자에게 있습니다.

작은 변경을 배포하고 다음 범위를 다시 계획

큰 범위를 한 번에 수정하지 않았습니다. 현재 코드와 운영 지표를 먼저 확인하고 이번 사이클에서 안전하게 바꿀 수 있는 범위를 정했습니다.
책임이 섞인 지점, 반복 조회, 과도한 동시 실행, 다른 도메인 DB에 직접 접근하는 지점 등 개선 후보는 많았습니다. 위험도와 의존관계를 기준으로 매번 하나의 범위만 선택했습니다.
처음 세운 계획을 끝까지 한 번에 실행하지도 않았습니다. 작은 변경을 배포한 뒤 운영 지표와 회귀 여부를 확인하고 그 결과를 바탕으로 다음 범위와 순서를 다시 계획했습니다.
수정 범위 계획 → 작은 단위의 PR → 코드 검수 → 테스트 → 배포 → 다음 범위 재계획
 
각 변경은 다음 과정을 반복합니다.
  1. 이번에 수정할 범위와 영향을 계획
  1. AI가 수정안을 사람이 한 번에 검수할 수 있는 작은 PR로 분리
  1. 사람이 변경 이유, 코드와 영향 범위를 직접 검수
  1. 단위 테스트와 기존 회귀 테스트를 실행
  1. 배포 후 운영 지표와 이상 여부를 확인
  1. 확인한 결과를 바탕으로 다음 수정 범위를 다시 계획

AI의 작업 단위는 사람이 검수할 수 있는 크기로 제한

작업 책임자는 한 명이었기 때문에 여러 AI가 만든 결과를 한꺼번에 검수할 수 없었습니다. AI가 많은 코드를 빠르게 만드는 것보다 변경 이유와 영향을 이해하고 안전성을 확인하는 일이 먼저였습니다.
사람이 한 번에 검수할 수 있는 크기가 AI의 작업 단위가 되도록 작업 환경에 규칙을 적용했습니다.
  • 하나의 PR은 하나의 목적만 가지기 변경 의도와 영향을 한 번에 파악할 수 있도록 수정 범위를 최소화합니다.
  • 기능 수정과 리팩터링을 같은 PR에 포함하지 않기 동작 변경과 구조 변경을 분리해 회귀 원인을 분명히 합니다.
  • 작업 범위의 모든 함수에 테스트 작성 정상 흐름과 함께 실패 조건과 경계값을 검증합니다.
  • 모든 테스트가 통과한 뒤에만 PR을 생성 실패한 결과물은 사람에게 넘기지 않습니다.
  • 사람이 PR을 검수하고 병합을 마치면 다음 작업을 계획 앞선 변경이 실제 코드에 반영된 상태에서 다음 범위를 조사합니다.
 
사람이 PR 검수하고 머지하는 과정은 단순한 승인 과정이 아닌 다음 AI 작업을 위한 신호입니다. AI는 PR 머지를 확인하고 다음 계획을 시작합니다.
사람의 검수와 병합을 게이트로 둔 AI 작업 환경
사람의 검수와 병합을 게이트로 둔 AI 작업 환경
이 사이클을 반복하면 문제의 원인을 좁히기 쉽고 되돌릴 범위도 작아집니다. 앞선 변경의 실제 효과를 확인한 뒤 다음 결정을 내릴 수 있어 핵심 시스템을 전면 재작성하는 방식보다 안전하게 개선을 이어갈 수 있었습니다.
이 작업 환경에서는 하루 평균 20~30개의 PR을 만들었습니다. 각 PR의 변경은 50줄 이내로 제한해 변경 의도와 영향을 빠르게 파악할 수 있었습니다.

반복 조회를 줄이고 읽기·쓰기 경계를 정리

조회 결과를 컨텍스트로 전달해 재사용

여러 단계에서 배송 설정, 쇼핑몰 정책, 상품 정보와 회원 혜택을 반복 조회하고 있었습니다. 요청 초기에 필요한 정보를 준비해 컨텍스트로 전달하고 이후 단계에서는 같은 결과를 재사용하도록 바꿨습니다.

상품 수만큼 늘어나던 검증을 배치화

상품마다 같은 검증을 반복하면 상품 수에 비례해 쿼리 수도 증가합니다. 필요한 상품을 모아 한 번에 조회하고 검증하도록 바꿨습니다.
쿠폰, 적립금, 배송 설정을 N번 조회하던 구간은 한 번의 조회로 줄였습니다. 구매 수량 처리에서는 주문 상품 수만큼 발생하던 Writer DB의 SELECT를 제거하고 Reader 조회도 하나의 배치 쿼리로 통합했습니다.

조회는 Reader로, Writer에는 쓰기만 남기기

재고명이나 만료 쿠폰 로그처럼 조회 성격의 쿼리를 Reader로 옮겼습니다. Writer 트랜잭션에는 필요한 UPDATEINSERT만 남겨 저장 중 connection과 lock을 불필요하게 오래 점유하지 않도록 했습니다.

성능을 살피며 정확성 문제도 함께 개선

성능을 살피는 과정에서 다른 사이트의 주문이 잘못 연결될 수 있는 조건을 발견했습니다. 요청한 상품 일부가 조회되지 않아도 검증을 통과할 수 있는 조건도 확인했습니다. 성능보다 올바른 결과를 만드는 일을 우선했습니다.

동일 요청의 중복 실행을 캐시와 single-flight로 줄이기

짧은 시간에 같은 요청이 몰리는 구간에는 짧은 TTL의 캐시와 single-flight를 적용했습니다. 동일한 계산을 동시에 여러 번 실행하지 않고 하나의 결과를 함께 사용합니다.

검수와 테스트를 통과한 변경만 배포

변경마다 기존 계산 결과와 처리 순서를 유지했습니다. 단위 테스트와 함께 실제 주문 시나리오와 기존 회귀 테스트를 확인했습니다. 배포 후에는 connection, slow-query, 응답 시간과 실패율을 살폈습니다.
최소 단위의 변경을 반복 배포하면 문제가 생겼을 때 원인을 좁히기 쉽고 되돌릴 범위도 작아집니다.

Writer connection은 15.9%, slow-query 알림은 69.2% 줄었습니다

구조가 좋아 보인다는 판단만으로 개선을 증명할 수는 없습니다. Writer DB connection과 slow-query 알림을 개선 전후로 비교했습니다.

Writer DB connection 사용 구간

Datadog에서 동일 시간대의 30분 안정 운영 구간을 비교했습니다.
지표
개선 전
개선 후
변화
평균
286.5
240.9
15.9% 개선
p50
286
243
15.0% 개선
p95
290
249
14.1% 개선
최대
312
252
19.2% 개선
관측 범위
284~312
224~252
전체 사용 구간 하향
정상 운영 구간의 Writer connection 사용량은 약 15~16% 감소했습니다. 개선 전후 관측 범위의 폭은 모두 28로 같아, 변동성이 줄었다기보다 전체 connection 사용 구간이 낮아졌다고 보는 편이 정확합니다. 7월 13일 동일 시간대와 비교해도 평균이 약 17.6% 감소해 같은 방향을 확인했습니다.

쿼리 제거·배치화·Reader 전환·캐시가 함께 기여

  • 구매 수량 처리에서 불필요한 Writer SELECT 제거. Reader 조회는 하나의 배치 쿼리로 통합하고 조회 목적의 SELECT는 Reader로 이동.
  • 쿠폰, 적립금, 배송 설정의 반복 조회를 N회에서 1회로 축소.
  • 회원·쇼핑몰·구독 컨텍스트와 상품 조회 결과 재사용, 디지털·선물 검증 배치화.
  • 반복 호출이 많은 조회 경로에는 캐시와 single-flight 적용.
Writer pool 상한은 낮추지 않았습니다. 이번 감소는 pool 제한의 결과가 아니라 쿼리 제거·배치화·Reader 전환·캐시가 합쳐진 실제 점유 감소입니다.

slow-query 빈도와 극단적인 지연이 감소

slow-query 알림 중 실행 시간이 포함된 메시지를 기준으로 두 비교일의 24시간을 확인했습니다. 긴 SQL의 후속 분할 메시지는 건수에서 제외했습니다. 두 기간에는 같은 운영 환경과 9초 임계값을 적용했습니다.
개선 전 값을 100으로 환산한 주요 지표 변화
개선 전 값을 100으로 환산한 주요 지표 변화
지표
개선 전
개선 후
변화
전체 알림 건수
1,897건
585건
69.2% 개선
시간당 알림
79.0건
24.4건
69.2% 개선
평균 실행시간
54.2초
20.1초
63.0% 개선
p95
406.0초
46.0초
88.7% 개선
최대
451.6초
195.1초
56.8% 개선
300초 이상
186건
0건
100% 개선
동일 30분 구간
12건
0건
100% 개선

두 조회 경로에서 눈에 띄는 감소 폭

첫 번째는 주문과 마이그레이션 정보를 결합하는 조회입니다. 개선 전에는 평균 400초대의 알림이 184건 발생했지만 개선 후 같은 형태의 알림이 사라졌습니다. 조인 조건 보강과 쿼리 구조 변경이 가장 직접적인 개선 요인으로 추정됩니다.
두 번째는 주문 상태별 개수를 집계하는 조회입니다. 알림은 893건에서 221건으로 약 75.3% 감소했습니다. 캐시·single-flight 적용, 쿼리 단순화와 상관 조건 보강이 직접 연결된 변경입니다.
  • Read 알림: 1,753 → 563건 (67.9%)
  • Write 알림: 144 → 22건 (84.7%)
앞서 설명한 주문 생성 최적화는 Writer connection 감소에 직접 기여했습니다. 반면 이번 slow-query 개선의 중심은 주문 목록과 상태별 개수 집계 조회입니다. 주문 생성 최적화는 Reader slow-query 감소에는 간접 효과로 분류했습니다.

작은 변경을 반복할수록 다음 변경이 안전해 집니다

오래된 시스템일수록 큰 결심보다 작은 근거가 앞선다는 것을 다시 배웠습니다.
이미 동작하는 시스템을 존중하면서 변경 이유를 기록하고 한 번에 하나씩 수정하고 테스트하고 운영 지표로 확인해야 합니다. AI는 이 과정을 빠르게 만들지만 판단과 검수까지 대신하지는 않습니다.
AI 시대에는 코드의 책임과 경계, 이름과 테스트가 더 중요합니다. 사람이 오해하기 쉬운 코드는 AI도 오해합니다. 반대로 의도가 분명한 코드는 사람이 빠르게 리뷰할 수 있고 AI도 더 정확하게 변경을 보조합니다.
이 길드는 혼자 시작했지만 혼자 끝내기 위한 작업이 아니었습니다. 정규 로드맵을 흔들지 않고 가능성을 확인하고 결과와 근거를 남겨 동료가 다음 개선을 이어갈 수 있게 하는 것이 목적이었습니다.
OMS 개선은 아직 완벽하지 않습니다. 다만 AI와 함께 어떻게 작업을 이어나갈 수 있을지 확인할 수 있는 좋은 개선 프로젝트였습니다.
좋은 시스템은 한 번의 거대한 재작성으로 완성되지 않습니다. 오늘보다 내일 더 안전하게 바꿀 수 있도록 만드는 작은 개선의 연속이 좋은 시스템을 만듭니다.
 
변경연 Backend Chapter Lead 지속 가능한 시스템을 만드는 방법을 고민합니다. 아키텍처, 성능, 개발 생산성에 대한 경험을 기록합니다.
 

댓글