본문 바로가기
IT & 비즈니스

리팩토링을 미루다 배포가 하루 밀린 날

by notes9107 2026. 8. 23.

2024년 3월, 제가 운영하던 파이썬 기반 자동매매 서버는 평소보다 주문량이 조금 늘어난 날 멈췄다. 기능 자체는 정상 작동했지만 오전 9시부터 10시 사이 메모리 사용량이 1.8GB에서 7.6GB까지 치솟았고, 결국 주문 확인 작업이 11분 동안 밀렸다. 처음에는 거래소 API 문제라고 생각했지만 로그를 따라가 보니 주문 처리 함수 하나가 응답 객체를 리스트에 계속 쌓고 있었다. 그날 이후 저는 ‘실행되는 코드’와 ‘운영 가능한 코드’가 전혀 다르다는 사실을 몸으로 배웠다.

1. 작동만 하던 코드가 만든 장애

처음 작성한 시스템은 기능을 빨리 붙이는 데 초점을 맞췄다. 시세 수집, 매수 판단, 손절 주문, 텔레그램 알림을 420줄짜리 trading.py 파일에 몰아넣었고, 변수 이름도 temp1, result2처럼 급하게 붙였다. 2024년 4월 손절 기준을 3%에서 2.5%로 바꾸던 날에는 조건문 하나를 잘못 건드려 매수 주문에도 손절 기준이 적용됐다. 테스트 계좌에서 다행히 발견했지만, 그 뒤로 저는 코드를 읽는 사람이 미래의 저라는 점을 전제로 이름과 함수 구조를 다시 보기 시작했다.

2. 함수 분리보다 먼저 한 일

처음부터 대대적인 리팩터링을 시도하지는 않았다. 과거에 파일 전체를 한 번에 바꾸다가 새 버그를 만든 경험이 있었기 때문이다. 대신 2024년 5월 6일부터 매일 배포 전 30분을 정해 가장 위험한 함수 하나만 골랐다. 주문 가능 금액을 계산하던 78줄짜리 함수를 잔액 조회, 수수료 계산, 위험 한도 확인으로 나눴고, 각 함수의 입력과 출력 형태를 고정했다. 함수가 짧아진 것보다 더 큰 변화는 실패 지점을 로그에서 바로 찾게 됐다는 점이었다. 예전에는 한 번의 오류를 추적하는 데 40분이 걸렸지만, 이후에는 대체로 10분 안에 원인을 좁혔다.

3. 아키텍처를 나눈 뒤 생긴 뜻밖의 비용

저는 비즈니스 로직과 거래소 API 호출을 분리하면 모든 문제가 사라질 거라고 생각했다. 하지만 처음 만든 인터페이스는 메서드가 19개나 됐고, 실제 전략에서는 그중 5개만 사용했다. 구현체를 바꿀 때마다 불필요한 메서드까지 수정해야 해서 오히려 개발 속도가 느려졌다. 2024년 6월에는 인터페이스를 주문 제출, 주문 조회, 잔액 조회 세 묶음으로 다시 나누고, 전략 코드에는 ExchangeClient가 아니라 내가 정의한 작은 포트만 전달했다. 그 결과 테스트에서 실제 거래소 대신 30줄짜리 가짜 객체를 주입할 수 있었고, 네트워크가 끊긴 상황도 로컬에서 반복 재현하게 됐다.

4. 리소스 최적화에서 먼저 버린 착각

저는 초기에 비동기 처리를 넣으면 서버가 무조건 빨라진다고 믿었다. 그래서 시세 수집 작업 12개를 asyncio로 동시에 실행했는데, 2024년 7월 테스트에서는 API 제한에 걸려 429 응답이 오히려 세 배 늘었다. 문제는 동시성 자체가 아니라 거래소별 호출 간격과 재시도 정책을 고려하지 않은 설계에 있었다. 이후 세마포어로 동시 요청을 4개로 제한하고, 응답 캐시의 유효 시간을 2초로 설정했다. CPU 사용률은 68%에서 41%로 내려갔고, 분당 API 호출량도 약 1,200회에서 470회로 줄었다. 빠른 기술을 덧붙이기 전에 실제 병목을 측정해야 한다는 교훈을 얻은 장면이다.

5. 메모리 누수는 코드보다 운영 습관에서 드러났다

메모리 문제를 고칠 때도 처음에는 가비지 컬렉터 설정부터 만졌다. 그러나 프로파일러를 붙여 확인해 보니 1초마다 저장하던 시세 스냅샷을 24시간 동안 메모리에 보관하는 구조가 원인이었다. 2024년 8월 12일, 최근 15분 자료만 deque에 남기고 오래된 값은 PostgreSQL로 넘기도록 바꿨다. 동시에 작업 큐가 비정상적으로 커질 때 경고를 보내도록 했다. 변경 뒤 12시간 동안 메모리 사용량은 6.9GB에서 1.3GB로 낮아졌고, 재시작 없이 9일간 서버를 운영했다. 여기서 제가 배운 점은 메모리 최적화가 특정 문법 하나의 문제가 아니라 보관 기간과 처리 흐름을 정하는 운영 규칙의 문제라는 사실이었다.

리팩터링 과정에서 가장 자주 실패한 부분은 테스트를 나중에 붙이려 했던 태도였다. 2024년 9월에 주문 모듈을 먼저 분리하고 테스트를 마지막에 작성했더니, 기존 동작을 그대로 옮긴 것인지 확인할 기준이 없었다. 그래서 테스트 계좌의 과거 주문 300건을 고정 입력으로 만들고, 수수료와 잔액 변화가 이전 버전과 같은지 비교했다. 이 작업에서 반올림 위치가 달라 소수점 이하 금액이 어긋나는 문제를 발견했다. 이후 저는 작은 변경이라도 실패 사례 하나와 정상 사례 하나를 먼저 기록한 뒤 코드를 고칩니다. 테스트가 개발을 방해하는 절차가 아니라 기억의 빈틈을 메우는 기록장처럼 느껴지기 시작했다.

클린 코드를 적용하면서 생산성이 늘기만 한 것은 아닙니다. 처음 두 달은 파일 수가 6개에서 24개로 늘었고, 이동해야 할 위치가 많아져 간단한 수정에도 시간이 더 들었다. 팀원 한 명은 구조가 지나치게 복잡해졌다고 지적했고, 저도 그 의견에 동의했다. 그래서 모듈을 기능별로 무조건 나누지 않고 변경 이유가 같은 코드만 묶었다. 한 달 뒤에는 주문 정책과 알림 문구가 서로 다른 배포 주기를 갖는다는 점을 반영해 다시 경계를 조정했다. 제가 얻은 배움은 아키텍처 원칙을 교과서처럼 적용하는 일이 아니라, 실제 변경 빈도와 장애 지점을 관찰하며 경계를 계속 다듬는 과정에 가깝다는 점이다.

지금도 제 코드가 늘 깔끔한 상태로 유지되는 것은 아닙니다. 다만 배포 전에는 함수 길이보다 변경 범위, API 호출 수, 메모리 증가량, 실패 시 복구 경로를 먼저 확인한다. 독자께서 비슷한 시스템을 손보고 있다면 전체 구조를 한꺼번에 갈아엎기보다 최근 장애 하나를 골라 재현하고, 그 장애를 만든 책임만 작은 단위로 떼어내 기록해 보시길 권한다. 숫자로 측정할 항목을 정하고 한 번에 한 가지씩 바꾸면, 리팩터링이 취향 논쟁이 아니라 다음 장애를 줄이는 실험으로 바뀝니다. 저는 그 방식으로 매일 조금씩 고쳤고, 지금도 커밋 하나를 남길 때마다 미래의 제가 읽을 설명을 코드에 남기고 있다.

실전에서 겪은 시행착오

2024년 3월 파이썬 자동매매 서버에서 주문 처리 응답 객체를 리스트에 계속 쌓아 메모리가 1.8GB에서 7.6GB로 늘었고, 주문 확인이 11분 밀린 뒤 프로파일러로 원인을 확인했습니다.

2024년 5월 6일부터 배포 전 30분 동안 위험한 함수 하나만 골라 잔액 조회·수수료 계산·위험 한도 확인으로 나누고, 테스트 계좌 주문 300건을 고정 입력해 반올림 차이까지 검증했습니다.

적용 전 꼭 확인할 주의사항

  • asyncio로 시세 수집 작업 12개를 동시에 실행하기 전에 거래소 API 제한을 확인하지 않으면 429 응답이 세 배 늘어날 수 있습니다.
  • 24시간 시세 스냅샷을 메모리에 보관하면 1초 단위 데이터가 쌓여 메모리가 6.9GB까지 증가할 수 있으므로 deque 보관 기간과 PostgreSQL 저장 시점을 정해야 합니다.
  • 인터페이스를 처음부터 19개 메서드로 나누면 실제 전략에서 5개만 사용해도 구현체 수정 범위가 커질 수 있으므로 30줄짜리 가짜 객체로 테스트할 경계를 먼저 확인해야 합니다.