레거시 코드 및 시스템 리팩토링 - ABARA

PHP로 구성된 거대한 레거시 API 서버를 Ruby On Rails로 컨버팅하는 머나먼 여정

지준호
2021.02.15

“음…. 어디서부터 이야기를 해야 할까요?”

CTO 님으로부터 레거시 시스템 컨버팅 과정에 관한 기술 블로그를 작성해보는 것은 어떠냐, 하는 제안을 받았습니다. 순간 1년이 꼭 10년 같았던 고난과 좌절, 그리고 환희의 순간들이 주마등처럼 머리를 스쳐갔습니다.

그 대장정의 이야기 보따리를 하나씩 짚어가며 풀어나가 보겠습니다.

1. 레거시 시스템 현황 파악

2019년 6월 4일 화요일, 딸의 생일 다음 날, 동대문을 시스템화 한다는 거대한 꿈을 가지고 있는 회사에 부푼 마음을 안고 힘차게 첫 출근을 했습니다.

“무슨 일부터 해야 할까요?”

“일단 소스 보면서 구조부터 파악하시면 될 것 같습니다.”

“네! 알겠습니다. 여러 모듈이 있던데 git 주소 좀 알려주세요~.”

“네. XX와 XX의 git 주소는 OO 이고요, XX와 XX의 FTP 주소는 OO 입니다.“

“!!”

FTP…. 마지막으로 이 단어를 들었던 순간이 언제였을까 한참 생각해보았습니다.

조금 과장을 보태면 아마도 구한말 순종 8년 때 마지막으로 듣고 못 들어본 단어였습니다. 형상 관리부터 심상치 않음을 느낀 저는 떨리는 마음으로 한가지 질문을 추가했습니다.

“그…. 그럼 모바일이랑 웹에서 사용하는 API 소스는 그 중에 어느 것일까요?”

“모바일 API 주소는 XX, 웹 API 주소는 OO, 관리자 API 주소는 KK 등등… 블라블라 입니다.”

“!!!!”

‘뭐…. 뭐지. 왜 API 주소가 모조리 나뉘어 있는 거지…. 좀 구식이지만 MSA(Micro Service Architecture)로 구현한 형상관리인가’

이런 저런 의문과 두려움을 품은 채 소스를 하나씩 분석해 나가기 시작했는데, 결과는…. 절망이었습니다.

그 이유를 한가지 예를 들어 설명해보겠습니다.

어떤 상품의 상세 정보를 반환 하는 API가 있다고 가정하면, 일반적으로 그 API 하나를 작성하고 나면 그것을 사용하는 매체는 모바일이나 웹, 플랫폼 상관없이 호출할 수 있고 인증 부분만 다른 구조를 가질 것입니다. 그러나 이 레거시 시스템은 모든 매체에 따라 API 서버, 소스가 모두 나뉘어져 있었습니다. DB에 어떤 테이블에 컬럼이 하나 추가되었다고 가정하면 관련된 API만 수정하면 되는 게 일반적인데, 이 시스템에서는 해당 컬럼과 관계된 모든 부분을 같은 코드로 수정해야 하는 안타까움이 존재하고 있던 것입니다.

(실제로 2019년 7월경에 약간 복잡도가 있는 모듈을 개발한 경험이 있었는데, 그 때 자칫 잘못하면 소스 수정하다가 공황 장애가 올뻔했습니다.)

비슷하면서도 살짝 씩 다른 소스를 계속 수정하다 보니 데자뷔 같기도 하고, 8중 if 문을 따라 들어가다가 길을 잃고 림보 상태에 빠졌다가 겨우 현실 세계로 돌아온 경험도 있었습니다. 위대한 수학자이자 철학자 ‘피타고라스’께서는 이런 상황에 대해서 아래와 같은 말을 남겼습니다.

“내가 보기엔 이건 답이 없다.” - 피타고라스 -

빛이 보이지 않는 상황이었습니다.

설상가상으로 서버는 AWS가 아닌 다른 서비스를 사용하고 있어서 오토 스케일링 같은 유연함도 없었고, 어떤 서비스는 상용 서비스임에도 불구하고 서버 1대로 운영하는 깡(?)을 보여주기도 하였습니다.

어쩌다 시스템이 이런 상황에 놓였을까… 회사의 역사를 돌아보니 기존 개발팀이 상당히 소규모였고, 그로 인해 운영과 신규 개발 외에는 아예 시간을 할애할 수가 없는 상황이었습니다.

다행히 회사에서는 위와 같이 상황을 파악하고 개선하기 위해 많은 개발자들을 새롭게 채용하고 있었습니다.

이런 문제점들을 해결하고자 개발자들이 입사한 만큼 시스템의 안정성과 생산성을 위해서 이대로 두고 볼 수는 없었습니다. 여기까지가 서론이고, 지금부터 여러 개발자가 모여서 거대한 실타래를 풀어나간 진짜 대장정을 소개하도록 하겠습니다.

2. 레거시 시스템 분석과 신규 프레임워크 선택

기존 시스템을 분석해보니 많은 문제가 발견되었는데 그 중에서 대표적인 부분을 꼽아보면 아래와 같습니다.

  • 복잡하고 정리되어 있지 않은 코드. (인덴트 포함)
  • API가 파일별로 나누어져 있고, 서버 별로 그 클론이 존재.
  • N+1 쿼리의 페스티벌. (DB 성능의 심각한 저하를 일으킴)
  • 코드의 잠재적 오류와 warning 포인트가 산재. (깨진 유리창의 법칙)
  • 테스트 코드의 부재.

분석 이후 느낀 것은 총체적 난국이었습니다. 프레임워크도 없는 쌩 PHP, 가독성이 떨어지는 소스, 확장성이 고려되지 않아 비슷하면서 다른 역할을 하는 테이블들이 산적한 모델링까지.

개선해야 할 포인트가 너무 많아서 어디서부터 손을 대야 할지도 애매한 상황이었지만,

여러 번의 회의 끝에 일단 DB 모델링은 유지한 채 개발 언어와 프레임워크를 바꾸기로 하였습니다.

어떤 언어와 프레임워크를 선택하면 좋을지 고민에 고민을 거듭했고, 또다시 여러 번의 회의를 통해 아래 3가지 후보군으로 추려낼 수 있었습니다.

  1. 기존 PHP 소스 개선 및 custom 하게 제작한 프레임워크
  2. Laravel
  3. Ruby on Rails

처음에는 첫 번째 안으로 가는 게 좋겠다고 생각을 했습니다. 먼저 어느 정도 정리를 하고 나서 시스템 안정화가 된 이후에 새로운 프레임워크를 적용하는 게 괜찮다고 느껴졌기 때문입니다.

그러나 첫 번째 안은 신규 입사자들이 레거시 시스템 위에서 몇 개의 새로운 모듈을 개발해 본 후 바로 폐기 처분되었습니다. 효율성이 극도로 떨어져서 이대로는 어떤 생산성 향상도 기대할 수 없었기 때문입니다. 기존 경험으로 산정한 공수는 전혀 의미가 없었습니다. 예상한 공수보다 3~5배 정도 오버 되는 건 기본이었습니다.

그리하여, 이제 2개의 후보로 압축되었습니다.

Laravel, 그리고 Ruby on Rails

Laravel이 후보군으로 올라온 이유는 매우 단순했습니다. 기존 언어가 PHP 라는 것! 하지만 여기에도 큰 문제가 있었는데, Laravel 로 개발해 본 경험이 아무도 없었습니다. 기존 시스템에 대한 유지보수, 신규 개발, 그리고 변환 작업이 동시에 이루어져야 하는 상황에서 Laravel까지 공부하면서 상용 시스템에 적용하기엔 무리가 있었습니다.

그래서! Laravel도 최종적으로 드랍되고, Ruby on Rails가 선택되었습니다.

입사 후에 느낀 가장 안타까운 부분이 바로 생산성이었습니다. 복잡한 레거시 시스템과 협업 프로세스의 부재, 규모 있는 프로젝트의 경험 부재 등등 여러 가지 문제로 인해 신규 기능 오픈이 너무나도 더디게 진행되었습니다. 자연스레 기획팀과 클라이언트팀의 불만이 백엔드 파트로 몰렸습니다. 조금 더 신랄하게 비판하자면 백엔드 파트로 인해 여러 업무들이 진행되지 못하고 있는 상황이었습니다!

Ruby on Rails를 선택한 가장 큰 이유는 위에서 말한 부분과 일맥상통합니다. 바로 생산성이 높은 언어라는 것입니다. 다행히 백엔드 개발자 중 몇몇이 이미 Ruby on Rails로 프로젝트를 진행한 경험이 있었고, 그들도 생산성에 대한 칭찬을 아끼지 않았습니다. (최근 스타트업 회사에서 높은 생산성을 이유로 Ruby on Rails를 선택하는 곳이 많은데 더 자세한 내용은 아래 블로그를 참고하시기 바랍니다.) 참고 블로그

3. 레거시 변환 작업 시작

가장 먼저 해야 할 일은 코딩이 아닌 작업 방식의 결정과 Ruby on Rails 스터디였습니다.

기존 업무 프로세스를 예를 들어 설명해보겠습니다. 먼저 1)기획자가 ‘A에서 B라는 기능을 개선’ 이라는 일을 가져왔다고 칩시다. 그럼 2)개발자가 지정되고 3)개발 이후 기획자가 확인하면 오케이 사인이 떨어지고 4)상용서비스에 적용하는 간단한 프로세스였습니다.

여기서 정말 충격적인 것이 있었는데, 거의 모든 일정에 deadline이 없었습니다! 2주 정도에 끝내기로 했던 일을 3주, 4주에 걸쳐 끝낸다 한들 아무도 뭐라고 하지 않는 엄청난 일들이 비일비재하게 일어나고 있었습니다.

위 프로세스는 소규모 프로젝트를 진행할 때는 그래도 적용할 만합니다. 하지만 이번 컨버팅 작업은 백엔드 개발자가 모두 투입되고, 클라이언트 개발자와도 면밀하게 진행되어야 하기에 기존 방식으로는 무리가 있다고 판단했습니다.

코드의 신뢰성 문제는 별개의 문제였습니다. 그래서 아래와 같은 규칙을 도입하기 시작했습니다.

  1. 프로젝트명 정하기: ABARA
  2. 협업툴 제대로 활용하기 : slack, jira, github
  3. 코드 리뷰를 통해 코드 신뢰도 높이기
  4. 클라이언트 개발자가 쉽게 연동, 테스트 할 수 있도록 swagger 제공하기
  5. API json 규격 정하기

프로젝트명 - ABARA

일반적으로 어느 정도 프로세스가 정립된 회사라면 당연히 사용하고 있었을 환경을 하나씩 정립해 나가기 시작했습니다. 프로젝트명은 ABARA 로 정했는데 이유는…. 그냥 제가 가장 좋아하는 음료였기 때문입니다…. (프로젝트 진행 중에 아바라를 얼마나 많이 마셨는지, 작년 겨울에 스타벅스 프리퀀시를 2개나 교환하게 된 훈훈한 일도 있었습니다.)

뒤늦게 입사한 분들은 다른 개발자들이 하도 아바라가 어쩌고, 거기 기능이 어쩌고 말을 하다 보니 무슨 새로운 프레임워크가 나왔나 하면서 구글에 검색했다가 아이스 바닐라 라떼 사진만 엄청나게 봤다고 하는 이야기를 심심치 않게 듣기도 하였습니다.

컨버팅 진행

위의 그림과 같이 백엔드 개발자들이 컨버팅 해야 할 API 를 각각 할당하고 진행 상황을 자세히 보면서 진행하였습니다. 일단 앱과 웹에서 사용하는 API를 컨버팅하기로 했데, 개수가 260개 정도였습니다. (위의 숫자는 하나의 서비스에서만 해당하는 API의 숫자로 전체 변환 개수와는 차이가 있습니다.)
먼저 기본 구조를 잡는 일에 어느 정도 시간이 소요되었고, 곧바로 Ruby on Rails에 익숙하지 않은 주니어 개발자들에게 시니어 개발자들이 코드리뷰를 해주면서 진행했는데 소요된 기간은 총 4개월 이었습니다.

길다면 길다고 할 수 있는 기간이지만 레거시 코드를 새로운 프레임워크로 변환하는 작업을 해본 사람들이라면 이 기간을 보고 프로젝트가 얼마나 빠르게 진행되었는지 알 수 있을 것입니다.

4개월의 기간 동안 단순 컨버팅 작업만 한 것은 아니었습니다. 기존 구조를 도저히 안고 갈 수 없는 부분들은 컨버팅과 함께 리팩토링을 진행했는데 대표적인 부분이 로그인 프로세스와 푸시 시스템이었습니다.

특히 기존 로그인 프로세스에서 앱과의 통신을 할 때 token 방식으로 인증하고 있었는데, 이 때 사용하는 token에 큰 문제가 있었습니다. token은 내부적으로 사용자의 여러 가지 정보(id, password 등)를 조합하여 암호화 과정을 거치는데, 가장 큰 문제는 id 등의 주요 정보가 바뀌기 전까지는 token 또한 바뀌지 않은 채로 계속 사용된다는 부분이었습니다. 즉, 한 번 token이 노출되면 그 토큰으로 거의 모든 정보를 이용할 수 있는 상황이었고 이런 취약점으로 인해 수많은 크롤링 업체들이 신상마켓 서비스의 주요 상품 정보들을 아주 쉽게 퍼갈 수 있었습니다.

그리하여, 컨버팅 프로젝트와 별도로 KAMA 라고 불리는 서브 프로젝트가 탄생하였습니다. (KAMA는 다들 아시다시피(?) 카라멜 마키아또의 약자!)

로그인 프로세스 개선 프로젝트인 KAMA로 인해 사라진 DB 테이블 개수가 무려 21개 였습니다! 오랜만에 속이 뻥 뚫리는 듯한 시원함을 느낄 수 있었고 이런 경험들을 더 많이 하고 싶다는 의욕이 차오르기 시작했습니다.

또한, 단순히 테이블 개수를 줄였을 뿐만 아니라 기존의 사용자의 정보 변화가 없다면 token도 변하지 않는 방식에서 로그인 할 때마다 token이 변화할 수 있게 token, refresh_token을 활용하는 방식으로 보안적인 부분도 한결 개선되었습니다.

푸쉬 시스템도 극적인 변화를 맞이했는데 기존에 별도로 여러 인스턴스와 큐를 사용하여 JAVA로 구축되었던 서비스를 serverless 형태로 재구축했는데…. 이 부분은 추후 별도로 소개하겠습니다.

일단 구조도만 보면 아래와 같은 시스템으로 변경되었습니다.

테스트 코드 도입

위에서 서술했던 바와 같이 기존 시스템의 큰 문제점 중 하나는 테스트 코드의 부재였는데, 테스트 코드가 없는 시스템은 생각보다 많은 취약점이 발생합니다.

쉽게 예를 들어 설명해 보겠습니다.

A라고 하는 공통으로 많이 사용하는 DB 테이블에 어떤 수정이 이루어졌다고 가정했을 때, 그 시스템이 작은 규모라면 이런저런 사이드 이펙트를 고려하면서 개발을 하게 될 것입니다. 그러다 시스템 규모가 커졌다면 어떻게 될까요?

모든 사이드 이펙트를 찾아서 수정한다는 건 잡스 형이 와도 쉽지 않은 일이 될 것입니다. 그러나 테스트 코드가 있다면 어떻게 될까요?

테이블 수정과 관련된 개발을 진행한 이후 전체적으로 테스트 코드를 돌려본다면 개발자가 예상하지 못한 부분의 사이드 이펙트를 쉽게 검출할 수 있게 됩니다! 그리고 만약 테스트 코드에서 안 잡힌 오류가 상용서비스에서 발견된다면 테스트 코드를 지속해서 개선하면서 시스템의 안정성을 높일 수 있게 됩니다.

다행히 Ruby에는 쉽게 테스트 코드를 작성할 수 있는 rspec 이라는 것이 존재했고, 그를 활용하여 이번 프로젝트에서는 모든 API 에 대한 테스트 케이스를 같이 개발하였습니다. 처음에는 테스트 코드 작성에 익숙지 않아서 여러 가지 시행착오를 겪기도 하였는데…. (대표적으로 테스트 DB를 다 날린 일이 있었습니다.) 지금은 노하우가 쌓여서 여러 가지 방범과 장치로 그런 일들은 일어나지 않게 되었습니다!

아래 그림은 새로운 테이블을 추가했을 때 발생한 테스트 케이스 오류 화면입니다. 테스트 코드를 작성하면 이런 식으로 사이드 이펙트에 대한 부분을 배포 전에 먼저 인지하고 처리할 수 있습니다.

협업툴 제대로 활용하기

기존 시스템에서는 API 명세서의 부실한 부분과 swagger의 부재로 클라이언트 개발자와의 체계적인 협업이 어려웠습니다.

물론 회사 규모가 작고 개발자가 몇 명 없을 때는 이런 부분도 큰 문제가 되지 않습니다. 막히면 옆에 가서 물어보면 그만이기 때문입니다. 하지만 현재 상황에서 기존 방식대로 협업하게 되면 아마도 가장 조용해야 할 개발팀 공간이 마치 오일장이 열린 것 마냥 엄청나게 시끄러운 공간이 될 것이 자명했습니다.

그래서 swagger를 통해 클라이언트팀에서 API의 동작을 쉽게 확인할 수 있도록 지원하였고, swagger로 부족한 부분은 상세한 내용을 확인할 수 있도록 Jira 티켓을 제공 하였습니다. 이러한 일들이 쉽게 가능했던 까닭은 일반적인 오픈 소스 프로젝트 방식처럼 모든 작업에 대해 상세히 Jira 티켓을 발급하고 진행했기 때문입니다.

(프로젝트 이름은 ABARA인데, 아래 그림에서 swagger 이름이 macaron인 것은 초기에 프로젝트명이 macaron이었기 때문입니다.)

API json 규격 정하기

기존에는 제대로 된 json 규격 문서가 없었습니다. 그래서 컨버팅 작업을 하면서도 임의로 백엔드 파트에서 json 규격을 정해서 진행하게 되었는데, 이후 클라이언트와 연동을 진행 할 때 규격화 되지 않은 json 으로 인해 클라이언트 개발자들이 혼란을 겪기도 하였습니다.

그래서 클라이언트팀과 협의하여 json 규격을 정하게 되었고, 현재는 정확한 규격 문서로 인해 신규로 입사하시는 개발자분들도 큰 어려움 없이 규격화 된 API 를 개발할 수 있게 되었습니다.

아래 그림은 json 규격 문서의 한 부분입니다.

모니터링 도구 도입

조금 더 나은 무결성의 시스템을 위해 모니터링 도구 newrelic 과 centry 를 적용했습니다. 덕분에 상시로 모니터링 하면서 꼭 운영팀에서 버그로 접수되지 않더라도 오류가 나는 부분들을 선제 대응 할 수 있게 되었습니다. 물론 그렇다고 완벽하게 커버가 가능한 것은 아니지만, 선제 대응을 할 수 있는 것과 아닌 것은 천지 차이가 있습니다.

전체적인 모니터링 부분은 newrelic이 담당하고 있고, application 레벨의 오류 발견은 centry가 담당하고 있습니다.

또한 슬랙과의 연동을 통해서 오류 발생 시 알람을 발생하게 하였는데 이로 인해 백엔드 파트원들의 만성피로가 증가한 부분은 안타깝게 생각합니다….

4. ABARA 오픈!

2019년 11월 말!

드디어 4개월간의 컨버팅 작업이 마무리되었습니다. 물론 이후 작업도 많이 남았지만 일단 모바일과 웹에서 사용할 모든 API 변환이 완료되었고, 테스트 케이스를 돌렸을 때 0개의 오류가 나오게 되었습니다!

웹, 모바일 순서로 새로운 API를 연동한 버전으로 서비스를 리뉴얼하여 오픈하게 되었고 이 모든 과정이 2020년 상반기에 이루어졌습니다.

기능적으로 개선된 것이 아니기에 서비스를 사용하시는 고객분들은 변화를 느낄 수 없었지만, 회사 내부적으로는 크게 느낄 수 있는 변화였습니다.

이제 더는 백엔드 작업으로 인헤 클라이언트 개발 일정 지연이 발생하지 않게 되었고 보안도 많이 개선되었으며, AWS 인프라 구축, 새로운 기능을 위한 오픈 소스 라이브러리 적용 등, 엄청난 이점들을 몸소 느낄 수 있게 되었습니다.

레거시 프로젝트와 비교했을 때 생산성의 향상은 어느 정도 이루어졌냐면, 체감상으로는 거의 5, 10배에 달하는 정도입니다!

컨버팅이 끝난 이후 여러 가지 새로운 모듈을 ABARA 에서 개발하면서 엄청난 생산성을 느낄 수 있었고 모두 정확한 공수 산정을 예상할 수 있게 되었습니다.

백엔드 파트 뿐만 아니라 클라이언트 개발자분들도 이런 부분은 체감할 수 있으리라 생각 됩니다.

5. 맺음말

다시 한 번 언급하지만 레거시 시스템 변환은 생각보다 까다로운 작업입니다.

이미 상용화된 서비스를 중단하지 않고 새로운 시스템으로 연동해야 하는데, 대부분의 회사에서 이런 작업을 진행할 때 예상보다 몇 배의 일정 혹은 비용이 증가한 경험을 했을 것입니다. (심지어 그 실패의 책임을 엉뚱한 사람들에게 뒤집어씌워서 퇴사시키는 사례도 종종 주변에서 볼 수 있었습니다.)

딜리셔스에서 이렇게 성공적으로 변환할 수 있었던 이유는 단순합니다.

실제 작업을 한 개발자는 물론이고, CTO 부터 대표에 이르기까지 프로젝트에 전폭적인 지원을 아끼지 않았고 한마음 한뜻으로 일을 진행한 덕분입니다.

그로 인해 개발자들은 더욱 믿음에 보답하려 노력하였고 그 결과로 성공적인 컨버팅을 이루어 낸 것이라 생각합니다.

역전의 용사들!!

아직 모든 레거시가 변환된 것은 아닙니다. 지금도 관리 시스템에 레거시가 존재하고 열심히 변환을 하고 있습니다. 하지만 이 작업 또한 부분 오픈 되어 실제 상용 서비스에 적용되었고, 내년 1분기 안에는 모든 작업이 종료되지 않을까 예상해 봅니다.

레거시 변환이 완료되면 이제 비로소 여러 모듈의 제대로 된 리팩토링을 진행할 수 있게 됩니다.

물론 변환 작업 중에도 필요한 부분들은 리팩토링을 진행하였지만 워낙에 소스들이 여러 서버에 산발적으로 퍼져 있기 때문에 쉽게 리팩토링을 진행할 수 없었습니다. 하다못해 counter cache 같은 부분도 적용하기 어려운 실정이었습니다. 인제야 좋은 생산성이 나올 수 있는 시스템으로 인해 비슷한 출발선에 서게 되었습니다.

우리가 가려는 시스템의 진화 단계를 그려보자면 그나마 도구를 쓸 수 있는, 드디어 중간 정도 온 듯한 느낌입니다.

ABARA 프로젝트를 통해 많은 자신감을 얻게 되었고, 현재 진행되는 혹은 진행할 프로젝트에서 그 누구도 “이건 못 하겠는데요.”라는 말을 하지 않습니다.

우리는 앞으로 더 많이 발전하고, 더 좋은 시스템을 만들어 낼 것입니다.

그리고 이런 작업들을 통해 우리들은 딜리셔스의 신조를 실현하고자 합니다.

패션 사업을 쉽고 즐겁게~!

지준호

신상마켓 API, Back Office 개발 및 운영

"백조 같은 삶 추구 (겉으로 보기에는 우아하지만, 물속에서는 XX 헤엄치고 있음)"