채널톡 동사무소 이야기

개발자가 고객 문의를 직접 응대하며 느낀 것

Dugi 🎈

  • 테크 인사이트

안녕하세요, 채널톡 백엔드 개발자 두기입니다

저는 최근 출시된 도큐먼트 제품을 개발하고 있어요. 직접 서비스를 책임지고 운영하는 것은 이번이 첫 경험이에요. 6월 27일에 공식 릴리즈 된 이후 두 달 정도 지났는데, 운영 단계의 에피소드와 느낀 점을 정리하는 글입니다.

도큐먼트를 포함한 채널톡이라는 서비스는 다른 기업이 사용하는 B2B 제품이기 때문에 사용자의 요청이나 문의, 클레임이 B2C 제품에 비해 복잡하고, 사용자의 문제를 잘 파악해서 해결해야 하는 부분이 많다고 생각해요.

특히 채널톡에서는 고객 문의에 개발자나 제품팀 멤버들이 초대되어 문의를 직접 해결하기도 합니다. 그래서 이번 편은 사용자의 직접 문의(민원)를 해결하는 동사무소의 일상 컨셉이에요.

꼭 도큐먼트 제품에서 발생한 상황은 아니고, 이전에 다른 사례의 예시를 참고하기도 했으며, 적당한 각색을 통해 재구성했으니 참고해주세요!

이번 동사무소 편의 이전 시리즈인 "채널톡 탐정사무소" 글이 궁금하다면?

모든 사용자 데이터 변경에는 흔적이 남아야 한다

어느 날 굉장히 다급해 보이는 사용자 한 명이 찾아왔어요.

"제가 이것저것 만지다가 아티클(글)이 다 날아간 것 같아요 ㅠㅠ 혹시 어떻게 복구할 수 있는 방법이 없을까요? "

정말 영구 삭제를 했다면 복구가 어려울텐데.. 라고 생각하며 두기는 이 때를 위해 준비한 audit log(데이터 변경 이력)을 조회해보았어요. 그런데 이 사용자에게는 아티클을 영구 삭제한 이력이 없었어요.

이상함을 느낀 두기는 사용자에게 몇 가지 물어보았고, 알고 보니 이 사용자는 글을 삭제한 것이 아니라, 그냥 메인 화면에서 노출이 안되게 숨겨놓았던 것이었어요(관련 설정값을 바꾼 이력도 있었구요). 이렇게 저렇게 하면 다시 숨김이 해제된다고 알려드렸고, 결과는 해피 엔딩!

데이터 변경 이력을 남기지 않았다면 정확하게 어떤 상황인지 파악을 하기 어려웠을거고, 그냥 영구 삭제한 글은 복원이 안돼요 하고 돌려보낼 수밖에 없었을 거에요. 다른 문의가 들어왔을 때도 탐정처럼 주변 엔티티 정보를 관찰하면서 추측만 하는 게 아니라 간단하게 이력 조회로 쉽게 해결이 가능하겠죠?

  • 문의가 들어왔을 때 어떤 상황에서 문제가 발생했는지 주변 맥락을 파악해야 하는데, 데이터 변경 이력은 큰 도움이 되었어요.

  • 사용자는 자신이 어떤 행동을 했는지 정확하게 설명할 수 있는 경우가 드물기 때문에 맥락을 재구성하려면 꼭 필요한 정보에요.

  • 문제가 사용자의 액션 때문인지, 우리 코드의 버그 때문인지 구분하는 데에도 유용한 근거로 사용할 수 있어요.

데이터 변경 이력 도입기

사실 데이터 변경 이력은 서비스 운영 초반부에 넣을 생각은 아니었어요. (갈 길이 급한데...)

도큐먼트는 6월 말 공식 릴리즈 전 4월에 저희 팀 내부 가이드에 먼저 릴리즈 되었어요. 그런데 릴리즈 된 지 일주일도 안 되어서 가이드 문서에 접속이 안 된다는 리포트가 들어왔어요!

빠르게 확인해보니, 저희 가이드 문서 스페이스의 웹사이트 발행 상태가 OFF로 변경되어 있었어요. 누가 테스트하거나, 데모를 보여주다가 OFF로 돌리고 까먹고 가버린 거죠. 원래 운영용 채널에서는 테스트나 데모를 하면 안 되고 이걸 위한 공간이 따로 준비되어 있지만, 항상 지켜지기를 기대하기는 어려운 일이죠.

이런 일을 다시 겪기 싫었던 두기는 결국 데이터 변경 이력 (audit log) 기능과 권한 세분화 기능을 빠르게 구현해두었어요. 다행히 코드상으로는 아래와 같이 비즈니스 로직 인터페이스를 감싸는 enhancer 패턴을 사용해서 깔끔하게 들어갈 수 있었네요!

Go

관련 사례

앞선 경우는 저희 팀에서 겪었던 사례지만, 비슷하게 팀원 간 커뮤니케이션 미스나 실수로 문제가 발생하는 경우가 많아요.

  • 영구 삭제를 직접 했는데, 다시 문의해서 복원해달라고 요청하는 경우 (안 되는 것이 원칙이지만, 가끔 가능할 때 해결해주면 굉장히 고마워함)

  • 고객사의 수천 개 데이터가 갑자기 사라졌는데 범인을 몰라요. 사실 범인은 고객사의 누군가가 퇴사하면서 지우고 나간 것이었어요. 심지어 "한 번에 삭제" 기능도 아니고 수천 건을 하나하나 삭제하고 나가서 눈치채기 어려웠어요.

  • 고객사의 한 팀원이 어떤 설정값을 변경했는데, 그 옆자리 팀원이 문의해서 누가 변경했는지 물어보네요. (누가 바꿨는지 알려드리고 원만히 합의 보시라고 안내) 서로 대화 좀 하세요..

백오피스 도구를 준비하자

또 다른 사용자가 문의를 주었어요. 이번에는 "아티클을 공개 했는데, 메인 화면에 노출이 안 된다"고 하네요. 다행히 아주아주 간단한 문의에요. 사실 이 정도는 CX팀에서 익숙해지면 해결하실 수 있는데, 아직 신규 제품이다 보니 직접 만든 저희 팀만 제품 기능에 대해 정확하게 알고 있는 것이 많아요. 그래서 메인 화면에 노출 여부를 결정하는 설정값들을 확인하려고 하는데... 어떻게 보죠?

사용자 문의가 들어오거나 버그인지 확인하는 과정은 사용자의 설정값을 살펴보고 어떤 데이터가 있는지 확인하는 것으로부터 시작해요. 이 때마다 데이터베이스에 직접 연결해서 쿼리를 날리는 것은 번거롭기도 하고, 권한이나 보안 관리가 잘 안 된다면 큰 문제가 생길 수도 있어요.

이런 상황을 위해 두기는 미리 운영 데이터베이스의 read replica를 준비해두고, 쿼리 도구인 Redash와 연결해두었어요. 이제 웹에 접속해서 간단하게 쿼리를 통해 원하는 데이터를 확인할 수 있고, SAML(구글 계정 로그인)로 계정과 권한 관리가 되기 때문에 걱정 없어요!

그런데 문제가 있어요. 팀에서 SQL을 짤 수 있는 사람이 백엔드 개발자(a.k.a. 바로 나)밖에 없어요. 자연스럽게 모든 고객 문의가 저로 라우팅 되어버렸어요.

이러한 상황에서 또 할 수 있는 것들이 있어요.

  • 쿼리를 작성해야만 데이터를 확인할 수 있지 않도록, GUI와 검색 기능을 갖춘 백오피스 웹 클라이언트를 직접 만들어두는 것이에요. 물론 손이 많이 가는 작업이기 때문에 천천히 준비하고 있어요.

  • 다른 팀원에게 SQL을 공부하라고 시키면 돼요.

보고서 쓰기

도큐먼트는 신규 제품이라서 할 수 있는 것들이 많아요. 무엇을 하고 하지 않을지 고르고, 우리가 잘 하고 있는지 확인해야 하니 우리가 중요하게 생각하는 숫자를 정리하고, 매주 보고서를 작성하고 있어요. 또 보고서는 외부 홍보용으로 쓰이기도 하고, 팀에서 우리 잘하고 있다고 자랑하기 위해 쓰기도 해요.

이런 리포트 쓰는 일은 소프트웨어 개발이 아니니까 다른 사람이 해주나요? 당연히 그럴 리가 없어요. 채널 동사무소 스타트업에 다니는 개발자는 어쨌든 필요하고 내가 할 수 있는 일이면 해야 해요. 다행히 Redash를 이미 사용하고 있기 때문에 쿼리를 돌리고 결과를 시각화하는 것은 아주 쉬워요. (그냥 Redash에서 제공하는 visualization 기능을 활용하면 돼요.)

금방 다채로운 대시보드 리포트가 완성되었어요!

다른 팀의 요청으로 데이터 추출이 필요한 경우에도 CSV export 기능을 유용하게 쓸 수 있어요. 쿼리 결과를 다른 사람들이 Excel이나 구글 스프레드시트에서 확인할 수 있게 하기 위해 코드 몇 줄 더 작성할 필요도 없어요.

서비스 개발과 비즈니스 로직 구현은 내가 가장 익숙한 환경에서 제일 잘 사용할 수 있는 프레임워크와 도구를 사용하기 때문에 빠르게 무언가를 만들어내는 것은 능숙해지면 어렵지 않습니다. 오히려 사용자의 문의 확인과 해결, 서비스 운영에 필요한 지표 확인, 다른 팀과의 소통에서 도구가 익숙하지 않아 시간을 많이 사용하는 경우가 많다는 것을 느꼈어요.

데이터베이스 백업과 복구

이건 다행히 실제 운영단계는 아니고 개발 스테이징 서버의 에피소드에요.

같이 도큐먼트를 개발하는 클라이언트 팀원이 갑자기 개발 서버에 테스트 중이던 데이터가 다 사라졌다고 멘션을 줬어요! 화들짝 놀란 두기는 빠르게 확인해봤는데, 진짜 데이터가 날아갔고 심지어 그 팀원이 보던 데이터 뿐만이 아니라 전체 데이터베이스가 날아간 것이었어요!

나중에 쿼리 실행 로그를 보고 원인은 찾으면 되고, 그 전에 데이터베이스 복구를 해야겠죠. 다행히 저희 데이터베이스는 Amazon RDS 상에서 운영되고 있고, 스테이징 환경에서는 매일 자동으로 PITR 백업이 기록돼요. PITR 백업을 그대로 쓸 수는 없었어요

(그 인스턴스의 모든 데이터베이스가 PITR 시점으로 리셋되는건데, 저희 데이터베이스만 날아간 것이었거든요).

하지만 PITR로부터 새 인스턴스를 띄운 후 pg_dump로 데이터를 덤프 뜨고, 원래 인스턴스에 데이터를 밀어넣으니 복구 완료!

사실 원인은 개발 환경에 붙어서 테스트 코드를 돌리다가 통합 테스트 코드의 truncate (TRUNCATE TABLE {table_name} CASCADE) 로직이 스테이징 데이터베이스에서 돌아간 것이었어요. 통합 테스트는 매번 테스트 데이터베이스를 초기화하고 실행하기 때문이죠. 코드상으로 실수가 발생할 수 있어보여 통합 테스트는 로컬 호스트에 대해서만 돌릴 수 있도록 방어 로직을 추가했어요.

(대문짝한 경고 문구)

  • PITR로 복구가 가능한 건 머리로 알고 있었는데, 직접 복구하는 과정을 경험해보았어요! (개발자 업적 달성 )

  • 실제 서비스였다면 PITR만으로는 부분적인 데이터 손실이 있어 추가적인 작업도 진행해야 할 거에요. 그리고 다른 마이크로서비스와의 정합성 오류도 체크 해야겠죠.

  • 로컬 데이터베이스를 매번 초기화하면서 통합 테스트를 실행하는 것은 다른 문제도 있어요. 테스트 케이스 간 간섭이 일어나면 안 되기 때문에 테스트 케이스를 동시에 하나만 돌릴 수 있어 테스트 실행 속도가 느립니다. 테스트를 많이 작성할수록 점점 불편해져서, testcontainers 를 사용해서 테스트 케이스마다 별도의 데이터베이스 container를 사용하는 방법으로 바꿨어요.

오퍼레이션

개발자는 새로운 기능을 만들고 버그를 고치면서 코드를 작성하는 것이 주된 일이라고 생각할 수 있지만, 조금만 정신을 놓고 있으면 다른 일을 하는데 시간을 많이 쓰게 되어요. 바로 오퍼레이션인데, 사용자가 요청한 내용을 해결하거나 내부 팀에서 협력 요청을 처리하는 것을 말해요. 오퍼레이션을 효율적으로 처리할 수 없으면 주객전도로 하루 종일 이런 일에 잡혀있다가 정작 기능 개발은 야근으로 해야 할 거에요

특히 채널톡은 B2B 서비스이기 때문에, 신규 고객사를 유치하거나 기존 고객사를 관리하는 팀이 있어요. Account manager 분들이 고객사에서 주신 요청을 전달해주시거나 이런 저런 도움 줄 수 있는지 여쭤보시는 경우가 많아요.

왜 오퍼레이션에 시간을 많이 사용하게 되는 걸까요?

제가 참여했던 여러 프로젝트에서 가장 중요한 기능을 만드는 것은 오히려 가장 쉬운 일 중 하나였어요. 가장 중요하기 떄문에, 가장 먼저 계획하고, 제일 좋은 구조로 설계하고, 깔끔하게 만들어지기 때문이에요.

하지만 오퍼레이션은 긴급하게 들어온 요청을 처리하는 등 예상 못한 단발성 사건이 많아서 사용하기 불편하거나, 코드가 좋은 구조로 설계되지 못하는 경우가 많아요. 팀 메신저 스레드 어딘가나 팀 리더의 컴퓨터 폴더 깊숙한 곳에서만 필요한 스크립트를 찾을 수 있는 경우는 개발자들에게 이미 익숙한 상황일 거에요.

어떻게 오퍼레이션 비용을 줄일까?

서비스가 막 출시된 직후에는 이런 고민을 하지 않았기 때문에 모든 오퍼레이션은 수동으로 하나하나 SQL 쿼리를 짜거나 스크립트를 작성해서 해결했어요. 그러다 보니 하나를 처리하는 데 몇 시간이 걸릴 때도 있고, 일주일에 서너 개 끝내는 데 시간의 절반을 쓰겠더라구요.

특히 도큐먼트의 경우 이미 고객이 사용하고 있던 외부의 문서 서비스나 웹사이트로부터 데이터를 불러오는 것이 관건이었습니다. 고객마다 서로 다른 문서 파일이나 구조를 사용하고 있다 보니 수동으로 처리해야 하는 부분이 많았습니다. 어떻게 오퍼레이션 요청을 빠르게 쳐낼까 이것저것 해보다 보니 아래와 같은 구조가 되었어요.

  • 모든 엔티티에 대한 관리자용 읽기와 쓰기 API를 만들어둡니다.

    이것은 반복 작업이고 코드에 패턴이 명확하기 떄문에, 이미 이런 기능을 제공하는 프레임워크나 code generation을 활용해도 좋을 것 같아요!

    예를 들면 사이드프로젝트 할 때 유용한 Supabase의 경우 데이터베이스 schema로부터 REST API를 유도해주는 기능이 있습니다.

    "모든 사용자 데이터 변경에는 흔적이 남아야 한다" 부분에서 언급한 것처럼, 관리자용 쓰기 API로 인한 데이터 변경에도 로그를 충실히 남기면 혹시라도 실수했을 때 바로잡기 더 쉬워집니다!

  • 메타인지를 잘 합시다! 같은 요청을 해결하는 데 반복해서 시간을 쓰고 있다면, 바로 그 작업을 편리하게 수행할 수 있는 관리자용 API를 만들어야 한다는 뜻입니다.

    3회 반복이 좋은 기준입니다. 세 번째로 어떤 일을 하게 된다면 그 작업을 관리자용 API로 만드는 것을 높은 우선순위로 두고 바로 작업하는 저만의 규칙을 만들었습니다.

    약간 다른 이야기지만 이 규칙은 코드 리팩터링이나 코드 리뷰에 저만의 규칙으로 적용하기도 합니다. 만약 반복되는 코드나 패턴을 3회 이상 사용하게 된다면, 그 코드를 공통 컴포넌트로 분리해야 한다는 뜻으로 받아들이고 바로 작업합니다.

    만약 코드 리뷰에서 어떤 피드백이 3회 이상 등장한다면, 그것은 그 주제에 대해 팀에서 서로 생각이 다른 지점이 있는지 리마인드 하고 지나갑니다.

  • 관리자용 읽기와 쓰기 API를 HTTP/REST 로 뚫어둔 후, 스크립트에서 매번 http request를 만들어서 보내고 response를 해석하는 것이 귀찮게 느껴졌습니다. 그래서 관리자용 API에 대응되는 관리자용 클라이언트 SDK도 만들었습니다.

    SDK라고 해서 대단한 것은 아닌데요, REST API를 감싸는 계층을 하나 더 두고 요청을 보내고 받는 것을 담당하는 컴포넌트입니다. 스크립트에서 함수 호출하는 것처럼 관리자용 API를 사용할 수 있어 스크립트를 작성하는 것이 훨씬 빨라졌습니다.

    스크립트를 메인 애플리케이션과 함께 monorepo로 구성하여 비즈니스 로직을 재활용하는 것도 가능해졌습니다.

더 유용한 스크립팅을 위해

결과적으로, 관리자용 API와 모노레포 구조를 통해 복잡한 로직을 담은 스크립트를 금방 작성할 수 있게 되었답니다. 다음과 같은 오퍼레이션 요청을 편리하게 처리하고 있어요.

  • 데이터 스캔하면서 통계값 뽑기

  • 데이터 정합성 맞추는 마이그레이션 스크립트

  • 외부 데이터를 불러오거나, 서비스 내부의 데이터를 외부로 export

  • Secondary database로의 전체 혹은 부분 동기화

메인 애플리케이션 외에 스크립트를 따로 작성하면서 지나칠 수 있는 자그마한 팁이에요.

  • 스크립트의 실행 로그는 잘 보관하기.

    셸에서 직접 명령어를 입력해서 스크립트를 실행한다면 tee 를 이용하여 콘솔과 파일 둘 모두로 결과를 forwarding 할 수 있습니다. 통합 개발 환경에서 실행되는 스크립트라면 이미 별도의 로깅 시스템이 갖춰서 있으니 더 좋겠지요!

    그리고 로그에는 당연히 충분한 정보를 담아야 만약 에러가 발생하더라도 언제 어디에서 발생했는지 알 수 있겠죠? (에러 메시지만 출력하고 어떤 데이터를 처리하다가 발생한 에러인지 표시하지 않으면 난감)

  • 배치 데이터 처리 등 많은 데이터를 처리한다면.. progress를 출력하세요.

    스크립트 프로그램이 문제가 있어서 멈춘 건지, 계속 돌고있는 건지 구별이 되어야 하니깐요. tqdm 처럼 예쁜 progress bar도 나오고 ETA도 표시되면 편리하겠네요.

  • 가능하다면 dry-run 옵션을 두세요.

    Dry-run 이란 스크립트를 실행하지만 엔티티 쓰기 등 실제로 부수효과가 있는 동작은 생략하고, 어떤 처리를 할거야~ 라고 로그만 남기는 것을 의미합니다. 실제로 실행하기 전 리허설을 해본다고 비유할 수 있겠네요.

    개발 환경에서는 고려하지 못한 엣지케이스가 운영 환경의 데이터에서 등장할 수 있기 때문에 dry-run을 해볼 가치가 충분히 있습니다. 특히 오래 전 만들어진 데이터의 경우 그 이후 적용된 정합성 정책에 맞지 않아 스크립트 오류가 발생하는 경우가 에전부터 있었습니다.

    스크립트는 한 번 돌기 시작하면 잘못되었을 때 중간에 멈추고 고쳐서 대응할 수 있는 기능을 넣는 경우가 별로 없죠. Dry-run 옵션을 두고 충실하게 구현하면 나중에 실패한 데이터를 따로 모아 재처리하는 번거로움이 줄어듭니다.

  • Documentation과 스크립트 실행 옵션에 대한 설명을 상세히 달아두면, 내가 휴가 갔을 때 다른 팀원이 돌릴 수 있는 스크립트가 됩니다.

공습 경보!!

개발자는 버그를 만들고 버그를 고쳐요.

누구나 실수를 할 수 있고 작은 버그로 정리되는 해프닝도 있지만 서비스 전체가 먹통이 되는 큰 장애도 발생하는 법이에요. 하지만 모든 실수를 개발과 테스트 단계에서 잡고 갈 수는 없습니다. 그러려면 코드 리뷰에 시간을 쓰고, QA 시나리오 작성하고 며칠 테스트 하고, 기존 기능에 문제가 없는지 regression 테스트도 계속 하면서 새로운 기능을 릴리즈 해야 해요. 새롭게 만들어지고 있는 제품을 개발하면서 이렇게 방향성을 잡는 것은 맞지 않아요.

다만 저는 실수해도 빠르게 바로잡을 수 있는 문화를 가지면 좋겠어요.

  1. 실수 한 것을 최대한 빨리 인지. (기술적으로는 알람과 로그 시스템, 팀에서는 핫라인과 온콜 담당자 지정)

  2. 고객에게 장애 발생을 잘 전파. 저희 팀에서는 Status 페이지를 운영하고 있어요. 그리고 CX팀에서 여러 사용자 커뮤니티에 참여하고 있는데, 제품팀에서 장애가 발생했다고 전달받으면 고객에게 전파하기도 해요. 먼저 제보를 받기도 하구요.

  3. 이유와 복구 상황 설명. 장애를 해결하는 개발자 본인은 원인 파악과 대처에 바빠서 다른 사람들에게 설명할 시간까지는 없는 경우가 많아요. 팀원이 같이 붙어서 상황을 중계해주고 다른 팀이나 고객 커뮤니티에 연결해주는 역할이 필요해요.

  4. 실수를 잘 수습하기. 데이터 정합성이 깨졌으면 다시 맞추고, 손실되었으면 어떤게 얼마나 손실되었는지 파악하고, 사용자에게 보상이 필요하면 영향받은 고객 목록과 보상 방안을 고민하는 것도 해야해요. 실수를 수습하려면 할 일이 많아요. 개발자가 모두 중요한 역할을 해줘야 해요.

일단 1번이 제일 중요한 것 같아요. 같은 원인과 현상의 장애라도 5분만에 상황 종료되느냐, 1시간이 넘어가느냐에 따라 가벼운 해프닝으로 끝날 일이 수많은 사람들의 민원과 보상을 해줘야 하는 문제로 바뀔 수도 있네요.

사고일지

릴리즈 이후 총 3번 장애가 있었습니다. 두 번째 incident를 겪은 후 사고 리포트를 쓰기 시작했는데, 이 글을 쓰는 데에도 큰 도움이 되었어요. 리포트에는 장애 당일 대응한 내용을 기록해두고, 원인을 정리한 후 단기적으로 고쳐져야 하는 부분을 까먹지 않기 위해서, 그리고 중장기적으로 고쳐져야 할 점을 적어두었어요.

추가로 개발자 머피의 법칙 글을 추천하고 싶은데, 사고를 겪는 과정에서 이 글 내용 생각이 계속 났기 때문입니다. 신중해야 할 곳에서 충분히 생각 안 하고 만들었을 때 어떻게 약한 부분이 터지는지 재미있는 에피소드로 설명해주는 글이었어요.

사고일지 I

이 문제는 사용자의 입력을 충분히 검증하지 않아서 발생한 문제에요. 이 장애를 겪고 난 뒤로 기획적으로 validation이 없는 입력 필드에도 무조건 최대 길이를 정해두어야 한다고 생각하게 되었어요.

도큐먼트 기능이 출시된 지 3주밖에 지나지 않았던 때였는데, 신규 기능이었기 때문에 아주 작고 귀여운 서버를 할당해두었어요. RAM 할당이 500MiB밖에 되지 않았거든요.

그런데 사용자가 너무 큰 입력을 보내고 저장에 성공해버리면서, 그 entity를 조회할 때마다 메모리에 올려두고 있어야 해서 서버 프로세스에서 OOM이 발생했어요. 프로세스 하나 정도는 종료될 수 없겠지만, 그 사용자가 어 왜 안되지? 하면서 계속 요청을 보낼때마다 프로세스가 하나하나씩 암살당하면서 모든 프로세스가 unhealthy 상태로...

수습은 일단 리소스 할당을 늘리면서 5분만에 상황은 끝났지만, 뒤처리가 약간 고생이었습니다. 너무 큰 입력이 저장되어버린 엔티티를 정상화 해야겠는데, 또 사용자의 데이터니까 손실이 되지 않게 처리해야 했네요. 또 나중에 확인해보니 정상 크기가 아닌 엔티티가 100개 넘게 있어서 하나하나 스크립트 돌려가며 정리했어요.

사고일지 II

이번에는 서버 프로세스 자체는 healthy 였어요. 그래서 healthcheck 요청에는 계속 응답 하니까 알람이 안 울렸어요.

그런데 도큐먼트로 만든 웹사이트 접속이 안 된다고 제보가 들어온거에요! 확인해보니 프로세스 중 하나에서 데이터베이스로의 연결이 모두 timeout 나면서 사용자 요청이 처리가 안 되고 있었어요. 그 프로세스를 종료하고 새로 띄우니 문제가 해결되었습니다.

알람이 울리지 않아서 문제가 생긴 후 30분이 지나서야 인지를 했고, 그 후 5분만에 복구 했어요. 하지만 30분이나 먹통이었던 것이 아쉬워서 다음에는 healthcheck에서 애플리케이션이 의존하는 다른 외부 서비스(RDS, Redis, 메시지 큐, 등등)의 health도 체크하도록 해야겠다고 생각했어요.

사고일지 III

마지막 사건은 코드 실수로 데이터 처리 작업 일부가 손실되는 문제였어요. 비동기 처리로 돌린 작업이 실행 도중 취소될 수 있는 문제였는데, 사용자의 요청을 직접 처리하는 데에는 문제가 없었기 때문에 사용자로부터나 내부 제보가 있지 않아서 늦게 발견했어요. 다행히 secondary database에서 조회하는 쿼리 결과가 이상하다는 제보의 원인을 파보다 보니 코드 실수가 배포된 지 하루가 지나기 전에 문제를 찾을 수 있었죠.

이제 정합성을 맞춰야 하는데요... 문제가 있는 코드가 배포되었던 시간 동안 동기화가 깨진 데이터를 찾고, 다시 손실된 작업분을 보충해주는 스크립트를 수동으로 돌렸어요. (다른 팀에 전달해주는 데이터도 다시 replay 해주어야 했구요) 서비스 출시 초기라서 데이터 수가 수백개 밖에(?) 안 되었기 때문에 괜찮았지만, 더 늦게 발견했거나 데이터 수가 많았다면 훨씬 손이 많이 가는 작업이었을 거에요.

장기적으로는 Apache Kafka나 Amazon Kinesis Data Stream처럼 이벤트를 replay할 수 있는 스트림 컴포넌트를 쓰는것이 좋을거에요. 하지만 역시 고민은 이런 것들을 언제 도입하는 것이 적절하냐의 문제입니다. 더 강력한 도구는 더 비싸기도 하고, 필요한 인프라가 늘어나거나 데이터를 주고받는 구조가 복잡해져 이후 변경이 더 어려워지기 때문입니다. (닭잡칼 vs 소잡칼 의 딜레마)

마무리

이번 글은 토이 프로젝트나 MVP로 서비스의 핵심 기능을 만드는 것을 넘어서서, 운영 단계로 넘어가게 되면 어떤 일이 일어나는지에 초점을 맞췄어요. 도큐먼트라는 제품이 이런 것들이 필요없는 아주 초기의 단계거나 이 프로세스가 완성된 성숙한 제품이 아니라, 이 문제를 능동적으로 해결해야 하는 단계에 있었기 때문에 이 경험을 할 수 있었다고 생각해요.

정리해보면 이렇게 요약할 수 있겠네요!

  • 사용자 데이터 변경에 흔적을 남겨서 사용자 요청에 잘 대응하자

  • 백오피스 도구를 준비해서 개발자가 아니더라도 사용자 요청을 해결할 수 있게 하자

  • 서비스 지표를 볼 수 있는 환경을 준비하자

  • 데이터베이스 백업 잘하자

  • 복잡한 오퍼레이션을 처리할 수 있는 스크립팅 환경 잘 만들어두자

  • 서비스 장애 잘 인지, 전파하고, 빠르고 완전하게 복구하고 수습하자

끝으로 제 의견을 약간 덧붙이면, 운영 단계에서 찾아오는 도전을 해결하는 것이 개발자의 경험을 특별하게 만들어준다고 생각합니다. 제품을 출시해서 사용자들이 가지고 오는 문제는 실제의 구체적인 상황에서 출발하기 때문에 문제를 발견하고 해결하는 과정을 듣는 것이 재미있습니다.

제품이 비슷한 니즈나 비전을 가지고 있더라도 각각의 경험이 서로 아주 다른 경우도 많습니다. 그래서 개발자로서 의미있는 경험을 하고 싶다면, 사용자가 있는 제품 개발에 참여하거나, 토이 프로젝트라면 운영 단계로 이어가는 것이 좋은 선택이라고 추천하겠습니다!

We Make a Future Classic Product

채널팀과 함께 성장하고 싶은 분을 기다립니다

사이트에 무료로 채널톡을 붙이세요.

써보면서 이해하는게 가장 빠릅니다

회사 이메일을 입력해주세요