← 모든 글
Engineering· 5

달리는 기차의 바퀴를 갈아 끼우기: 15년 된 시스템을 멈추지 않고 현대화하기

박비봉

우리 시스템은 15년 됐다. 그리고 매일 100만 명이 그 위에서 출석을 찍고, 강의를 듣고, 성적을 확인한다.

이 두 문장은 함께 놓이는 순간 엔지니어링에서 가장 까다로운 문제 하나를 만든다. 15년 된 코드는 바꿔야 한다. 그런데 100만 명이 의지하는 시스템은 멈출 수 없다. 우리는 달리는 기차의 바퀴를 갈아 끼우고 있다.

멈출 수 없는 시스템의 역설

200개가 넘는 대학과 기관이 우리 LMS 위에서 돌아간다. 학생은 아침에 강의실 앞에서 출석을 찍고, 닫혀 있던 문은 우리 시스템과 연동되어 열린다. 학기 내내 성적을 확인하고, 교수에게 질문을 남기고, 과제를 제출하고, 동료와 이야기를 나눈다. 거창한 순간이 아니라, 매일 반복되는 이 평범한 일들이 단 한 번도 어긋나면 안 된다. 화려하지 않아서 오히려 무겁다. 누군가의 하루가 매일 그 위에 얹혀 있기 때문이다.

이런 시스템은 보수적일 수밖에 없다. "잘 돌아가는 건 건드리지 마라"는 격언이 가장 강하게 작동하는 곳이다. 그런데 동시에, 15년 전의 가정 위에 세워진 코드는 오늘의 요구를 감당하지 못한다. AI 워크로드, 실시간 처리, 새로운 보안 기준 — 어느 것도 2011년에는 고려 대상이 아니었다.

멈추면 안 되는데, 안 바꾸면 천천히 죽는다. 이 역설이 우리가 매일 다루는 문제다.

전면 재작성이라는 유혹

오래된 시스템을 마주한 모든 팀이 한 번쯤 꾸는 꿈이 있다. "처음부터 다시 짜자."

깨끗한 코드베이스, 최신 아키텍처, 15년치 부채가 없는 출발선. 매력적이다. 우리도 여러 번 이 유혹 앞에 섰다.

그러나 전면 재작성은 거의 항상 실패한다. 이유는 단순하다. 15년 된 코드에는 15년치 현실이 박혀 있다. 이상하게 보이는 if 문 하나가, 사실은 어느 대학의 특수한 학사 일정을 처리하기 위한 것이다. 누구도 기억하지 못하는 예외 처리가, 10년 전 어느 학기 초 출결 장애를 막기 위해 들어간 것이다. 그 코드를 버리는 순간, 그 안에 녹아 있던 도메인 지식도 함께 버려진다.

그리고 결정적으로, 재작성하는 동안에도 기차는 달린다. 새 시스템을 2년에 걸쳐 만드는 사이, 기존 시스템은 계속 고객의 요구에 응답해야 한다. 결국 두 개의 시스템을 동시에 유지하다가, 새 시스템은 영원히 "곧 출시"에 머문다.

우리는 이 길을 가지 않기로 했다.

우리가 택한 방법: 조금씩, 그러나 멈추지 않고

대신 우리는 시스템을 한 부분씩 떼어내 교체한다. 오래된 거목을 무화과나무가 천천히 감싸 결국 대체하는 것에 빗대 흔히 strangler fig 패턴이라 부르는 방식이다.

원리는 이렇다. 거대한 모놀리스를 한 번에 들어내는 대신, 경계가 분명한 기능 하나를 골라 새로 만든다. 그리고 트래픽을 조금씩 새 코드로 흘려보낸다. 처음에는 1%, 문제가 없으면 10%, 그리고 100%. 이상이 감지되면 즉시 옛 코드로 되돌린다. 사용자는 그 사이에서 아무것도 느끼지 못한다.

이 방식이 작동하려면 몇 가지가 받쳐줘야 했다.

기능 플래그. 모든 변경은 스위치 뒤에 숨어 있다. 새 코드를 배포하는 것과 새 코드를 켜는 것은 분리된다. 배포는 조용히, 활성화는 신중하게.

양립 운영(dual-run). 한동안 옛 코드와 새 코드를 동시에 돌리며 결과를 비교한다. 새 코드가 옛 코드와 같은 답을 내는지 실제 트래픽으로 검증한 뒤에야 전환한다. 성적 처리처럼 틀리면 안 되는 영역일수록 이 단계가 길어진다.

기관 단위 카나리. 우리는 멀티테넌트다. 그래서 위험한 변경은 한두 기관에서 먼저 켠다. 200곳에 한꺼번에 푸는 일은 없다.

데이터베이스도 같은 원칙으로

가장 손대기 무서운 곳은 데이터다. 15년치 학습 데이터가 쌓여 있고, 그 위에서 수많은 쿼리가 돈다.

우리는 여기서도 "버리고 새로 짓기"를 택하지 않았다. 15년간 축적된 학습 데이터는 여전히 MySQL 위에 있다. 검증됐고, 안정적이고, 우리가 가장 깊이 이해하는 곳이다. 대신 새로운 종류의 문제 — AI 워크로드의 벡터 검색 — 에는 PostgreSQL을 새로 들였다. 옛것을 갈아엎는 게 아니라, 새 문제에 맞는 도구를 곁에 두는 방식이다.

이건 우리의 일관된 태도이기도 하다. 우리는 도구에 충성하지 않는다. 문제에 충성한다. MySQL이 잘하는 일은 MySQL에게, 벡터 검색이 필요한 일은 PostgreSQL에게 맡긴다.

AI가 바꾼 것

최근 1~2년, 레거시 현대화의 풍경이 바뀌었다. AI 에이전트 때문이다.

오래된 시스템을 현대화할 때 가장 큰 비용은 "이 코드가 무엇을 하는지 알아내는 일"이다. 주석은 낡았고, 작성자는 떠났고, 맥락은 사라졌다. 예전에는 사람이 며칠씩 코드를 거슬러 읽으며 그 의도를 복원했다.

지금은 AI 에이전트에게 오래된 모듈을 통째로 읽히고, 동작을 설명하게 하고, 빠져 있던 테스트를 먼저 생성하게 한다. 그 테스트가 통과하는 것을 안전망 삼아 내부를 바꾼다. 코드의 겉보기 동작을 유지한 채 속을 갈아 끼우는 작업 — 리팩터링 — 의 속도가 달라졌다.

물론 AI가 만든 이해도, AI가 만든 테스트도 사람이 검증한다. 특히 레거시에서는 "그럴듯한데 틀린" 설명이 가장 위험하다. AI는 속도를 주지만, 무엇을 건드려도 되고 무엇은 절대 건드리면 안 되는지에 대한 판단은 여전히 사람의 몫이다.

무엇을 남기고 무엇을 버릴까

15년 된 코드를 마주하는 엔지니어가 가장 먼저 배워야 하는 건 겸손이다.

이상해 보이는 코드의 상당수는, 우리가 모르는 이유 때문에 그렇게 생겼다. 그걸 "옛날 사람이 못 짠 코드"로 단정하고 지우는 순간, 그 안에 담긴 현실이 어느 날 갑자기 장애로 돌아온다. 오래된 코드를 읽을 때 우리는 먼저 묻는다. 이건 왜 이렇게 됐을까.

동시에, 진짜 부채도 있다. 더 이상 누구도 쓰지 않는 기능, 잘못된 가정 위에 세워진 구조, 지금이라면 절대 그렇게 짜지 않을 것들. 이런 건 과감히 들어낸다.

남길 것과 버릴 것을 가르는 기준은 결국 도메인 이해다. 코드를 잘 읽는 능력만으로는 부족하다. 대학의 학사가 어떻게 돌아가는지, 학기의 트래픽이 어떤 리듬으로 움직이는지 아는 사람만이 그 둘을 구분할 수 있다.

마치며

레거시 현대화는 화려한 일이 아니다. 새 제품 출시처럼 눈에 띄지도 않는다. 잘하면 아무도 모른다 — 사용자는 어제와 똑같이 출석을 찍고, 강의를 듣고, 성적을 확인한다. 그 "아무 일도 없음"이 우리가 만들어내는 성과다.

15년 된 시스템을 멈추지 않고 바꾼다는 건, 결국 두 가지를 동시에 존중하는 일이다. 지금 그 위에서 살아가는 100만 명에 대한 책임과, 다음 15년을 감당할 수 있어야 한다는 미래에 대한 책임. 기차는 멈출 수 없고, 바퀴는 갈아야 한다. 우리는 매일 그 사이에서 일한다.