제품 운영 잘하기

Nov.23.2021 김정환

Web Frontend

저는 초보 아빠입니다. 일과를 마친 뒤 잠자는 자녀를 보면 "좋은 아빠가 될 거야"라고 다짐하게 됩니다. 건강하게 낳는 것뿐만 아니라 바르게 기르는 것도 부모의 역할입니다.

아이 발을 받치는 손
아이 발을 받치는 손, 출처: Omar Lopez on Unsplash

그 동안 소프트웨어 제품을 여러 개 만들었는데요, 신규 프로젝트를 출시하는 일은 재밌고 설레는 일입니다. 반면 출시한 제품을 오랫동안 운영해 보진 못했는데 개발자로서 부족한 점으로 여깁니다.

올해 초 부서 이동을 하면서 3년 이상 된 제품의 운영 업무를 맡았습니다. 부족한 부분을 채울 수 있는 기회라고 여기고 이런저런 일을 했는데요. 제품 운영 노하우를 개발자/운영자 관점에서 정리해 보겠습니다.

쾌적한 개발 환경 만들기: ts-loader, fork-ts-checker-webpack-plugin

이 제품은 타입스크립트와 리액트를 사용합니다. 타입스크립트 코드를 컴파일하고 나면 이를 웹팩이 하나의 파일로 합치는 빌드 과정이 있는데요. 두 개 프로세스를 따로 실행하는 방식을 사용하고 있었습니다.

이런 방식에는 몇 가지 문제가 보였는데요. 컴파일 오류를 발견하더라도 뒤이어 실행되는 웹팩 메시지에 가려져 오류 메시지를 제대로 볼 수 없었습니다. 매번 프로세스를 두 개씩 실행해야 하는 것도 약간 번거로웠습니다.

$ tsc & webpack

이런 환경을 구성했던 프로젝트 세팅 초기와 지금의 개발 환경은 많이 달라졌습니다. 타입스크립트 컴파일 단계를 웹팩 안으로 통합할 수 있기 때문이죠. 웹팩 로더 중 ts-loader를 추가하면 웹팩만 실행하더라도 두 가지 일을 동시에 처리할 수 있습니다.

// webpack.config.js

module: {
  rules: [{
    test: /.tsx?$/,
    // tsc를 직접 실행하지 않고 로더를 추가
    loader: 'ts-loader'

tsc를 실행하면 컴파일과 타입검사를 동시에 실행합니다. 코드가 많다면 타입 검사 속도도 무척 느려질 수 있는데요. 상당 기간 유지 보수해 온 이 제품은 많은 코드 때문에 타입 검사 속도가 불편할 정도로 느렸습니다.

fork-ts-checker-webpack-plugin 은 타입스크립트 타입 검사를 별도로 실행하는 웹팩 플러그인입니다. 웹팩을 실행하면 먼저 컴파일과 번들링만 빨리 실행하고 타입 체크는 좀 늦게 따로 실행할 수 있습니다.

// webpack.config.js

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

plugins: [
  // 플러그인을 추가
  new ForkTsCheckerWebpackPlugin()
]

module: {
  rules: [{
    test: /.tsx?$/,
    loader: 'ts-loader',
      options: {
        // 타입체크는 fork-ts-checker-webpack-plugin이 수행
        transpileOnly: true,
     },
  }],
},

쉬운 디버깅 환경 만들기: source-map

여러분은 제품에 문제가 있을 때 어떻게 디버깅하시나요? 많은 분들이 console.log() 함수를 이용해 원하는 위치에 값을 기록하는 방식으로 문제 원인에 접근합니다. 직관적이고 쉽기 때문에 많이 사용하는데요. 브라우저의 디버깅 기능을 사용하면 좀 더 편하고 많은 정보를 볼 수 있습니다.

그러려면 브라우저가 빌드된 코드를 이해할 수 있게 소스맵을 만들어야 합니다. 그렇지 않으면 브라우저는 빌드 된 코드만 로딩하기 때문에 코드 읽기가 어렵기 때문이죠.

소스맵은 웹팩 설정에 따라 여러 가지 방식으로 만들 수 있습니다. 웹팩 가이드라인에 따라 개발 환경에서는 아래 설정으로 소스맵을 만들었습니다.

// webpack.config.js

devtool: 'eval-source-map'

다시 빌드하고 보면 브라우저가 빌드 된 코드와 소스맵을 참고해서 사람이 읽을 수 있는 코드로 보여줍니다.

간편한 배포 확인: banner-plugin

이 제품은 정기적으로 작업물을 배포합니다. 개발자는 잘 배포되었는지 확인하고 관련 채널에 알리는데요. 저는 젠킨스에서 배포 완료를 확인한 뒤 배포된 제품도 바로 확인합니다. 직접 눈으로 보아야 배포가 잘 되었다는 걸 확신할 수 있기 때문입니다.

문구 수정 같은 간단한 기능은 금방 확인할 수 있습니다만, 특정한 조건에서 발생하는 작업은 확인하는데 좀 까다롭습니다. 그만큼 배포 소식도 늦어져 애를 태우기도 합니다.

좀 더 직관적으로 배포 여부를 확인하는 방법이 있을까요? 프론트엔드 코드를 배포하는 것은 결국 서버 어딘가에 정적파일을 업로드하는 것인데요. 이 정적파일에 배포일자를 기록해 두면 좋겠습니다. 이 정보만 확인하면 쉽게 배포 여부를 알 수 있을 것 같습니다.

웹팩 기본 플러그인 중 banner-plugin은 빌드 결과물에 주석을 추가하는 녀석입니다. 빌드 날짜를 주석으로 기록해 둔다면 배포 여부를 이것으로 갈음할 수 있겠습니다.

// webpack.config.js

const Webpack = require('webpack')

// 코드 상단에 추가할 주석 조각
const banner = 빌드 날짜: ${new Date().toLocaleString()};

plugins: [
  // 배너 플러그인으로 번들 결과물에 주석 기록
  new Webpack.BannerPlugin({ banner })
],

만약 배너가 보이지 않는다면 웹팩의 TerserPlugin 옵션을 확인해 보세요. 이 플러그인은 코드를 압축하는데 이 과정에서 배너를 삭제할 수도 있기 때문입니다.

// webpack.config.js

const TerserPlugin = require('terser-plugin');

optimization: {
  minimizer: [
    // 기본 플러그인을 커스터마이징
    new TerserPlugin({
      terserOptions: {
        output: {
          // 주석을 제거한다.
          comments: false,
          // 아래 주석은 유지한다.
          preamble: /* ${banner} */,

preamble(서두, 서문) 옵션에 압축 대상에서 제외할 문구를 지정할 수 있습니다. 여기에 배너 문구를 추가해두면 플러그인은 이를 건너뛰고 코드를 압축합니다. 결과물에 배너를 유지할 수 있겠죠.

이제 배포하고 나면 직접 기능을 확인하기 전에 정적 파일을 열어 봅니다. 코드 상단에 빌드 정보를 확인하고 배포여부를 알 수 있기 때문입니다.


여기까지 개발 환경을 개선한 것을 말씀드렸습니다. 개발자 뿐만 아니라 기획자, 테스터, 운영자 등 다양한 직무의 동료들이 제품을 손질하고 보살핍니다. 운영 환경을 제대로 갖추어야 제품도 무럭무럭 자랄 텐데요. 이번에는 운영 환경 개선 이야기를 해보겠습니다.

반복 작업 줄이기: 폼 입력 자동화

이 제품에는 사용자가 입력해야 할 폼이 무척 많습니다.

가령 가게를 등록하는 화면에는 10개 이상의 필수 입력 필드가 있는데요. 새로운 가게를 만들고 나서 후속 업무를 해야 할 상황이 빈번합니다. 필드를 많이 입력해야 하고 시간도 오래 걸려서 사람을 지치게 하는 일입니다.

크롬 브라우저를 사용하고 있어서 크롬 확장도구를 개발해 불편한 점을 해결했습니다. 자주 사용하는 폼 입력을 자동화하는데 확장도구의 역할입니다. 모두가 공감하는 요구사항을 신속히 정리했고 대략 일주일 만에 만들어 사내에 배포했습니다.

가게 만드는데 1분 이상 걸리던 것이 확장 도구를 사용하면 단 10여 초 만에 만들 수 있습니다. 상황에 따라 다양한 조건의 가게를 한 번의 클릭으로 만들 수도 있습니다. 이제는 가게 생성이 아니라 그 이후에 해야 할 본래의 일에 집중할 수 있습니다.

잦은 실수 방지하기: 약관 관리

이 제품은 약관도 제공하는 서비스입니다. 서비스가 성장하고 상품 개수가 늘어날 때마다 약관을 수시로 변경하는데요. 일하는 방식은 이렇습니다.

  1. 운영자는 약관을 수정합니다.
  2. 운영자는 수정한 워드 파일을 개발팀에 전달합니다.
  3. 개발팀은 해당 약관 HTML을 수정합니다.
  4. 테스터는 워드파일의 수정사항과 결과물이 일치하는지 검사한 뒤
  5. 배포합니다.

이러한 방식은 실수하기 쉬워 보였습니다. 실제로 QA 과정에서 나온 대부분의 버그는 오타입니다. 오타를 수정하는 것은 간단하지만 버그를 리포팅하고 해결하는 과정은 무척 지루한 일입니다. 몇 번 오타 수정을 하고 나면 업무에 대한 만족감도 떨어지는 것 같습니다.

마크다운(Markdown)은 개발할 때 즐겨 사용하는 문서 형식입니다. 점점 일반 에디터 서비스에도 마크다운 형식을 사용하는 분위기 같습니다. 약관을 마크다운으로 작성한다면 HTML을 사용했던 문제를 해결할 수 있지 않을까요? 코드와 텍스트가 섞여 있는 HTML보다 텍스트만 있는 마크다운을 다루는 것이 더 단순하니까요.

// ageement.md

# 광고주 가입

배달의민족 광고서비스를

이렇게 만든 약관 마크다운을 서비스에 적용하려면 HTML로 변환해야 합니다. react-markdown 은 마크다운과 리액트 컴포넌트를 결합해 HTML 문서를 만들어 내는 라이브러리입니다.

// Agreement.tsx

import ReactMarkdown from 'react-markdown';
import agreementMd from './agreement.md'

const Agreement = () => (
  <ReactMarkdown childrend={agreementMd} />
)

이제 약관 수정하는 업무 방식이 좀 달라졌습니다.

  1. 개발팀에서 관리하는 약관 파일(마크다운)이 원본이 됩니다.
  2. 운영팀은 이 파일의 최신본을 요청하고 수정한 뒤 개발팀으로 전달합니다.
  3. 개발팀은 이 파일을 기존것과 바꾸기만하면 됩니다.

사람이 직접 코드를 수정하고 눈으로 비교하는 일이 줄어들기 때문에 실수도 잦아들 것이라고 기대합니다.

결론

출시한 제품을 잘 보살피려면 꽤나 부지런해야 하는 것 같습니다. 잦은 기술 업데이트에 따라 합리적인 버전을 선택하고 적용해야 합니다. 유지 보수 할수록 비대해지는 코드 구역이 있다면 전체적인 균형도 맞추어야 합니다. 방치된 코드는 과감하게 삭제할 줄도 알아야 하고요. 오늘도 오래된 레거시 코드와 씨름하며 제품 성장을 위해 일하시는 개발자들을 응원합니다.

제가 속한 부서는 배달의민족 서비스를 잘 운영할 수 있는 몇 가지 제품을 담당합니다. 10명 미만의 프론트엔드 개발자도 같이 일하고 있는데요. 아직 한 두 분은 더 필요한 것 같습니다. 운영 노하우가 있으시다면 부디 이곳에도 관심 부탁드립니다.