2018년 7월 10일 화요일

Re: 8년째 같은 제품을 만들고 있습니다

"8년째 같은 제품을 만들고 있습니다"

이래저래 머릿속이 복잡하고 한번쯤 회고를 해야겠다는 생각이 들던 차에, 나 또한 같은 시간을 지나왔기 때문에 그 제목을 따올만한 글이 보여서 내 나름의 지난 일들을 정리해보고자 한다.

1. 해온 일
  • 한 온라인 서비스의 미약하던 시절부터 개발팀이라는 이름의 조직에 속해 그 서비스의 눈에 보이는 것과 눈에 보이지 않는 것 사이의 대부분에 손을 댔다. IE6의 CSS 핵을 열심히도 찾아다니던 때가 있었는가 하면, MySQL의 특정 버전에서 어떤 쿼리를 쓸 수 있고 쓸 수 없는지 실험하기도 했고, nginx와 haproxy, keepalived라는 이름이 어떤 역할을 하는지 뒤적거리기도 했다. AWS니 Azure니 하는 것들을 좇아 몇 가지를 서비스에 도입하기도 했다.
  • 한 때는 2인 팀의 팀원이기도 했고, 또 한 때는 1인 팀처럼 지낸 적도 있었고, 팀장이라는 직함이 붙은 적도 있었다. 팀장과 팀원이라는 역할에 대해 고민해볼 수 있는 시간이었다. 팀장으로서의 나는 이를 테면 '어디선가 누군가에 무슨 일이 생기면 틀림없이 나타난다 홍반장' 정도였다는 평을 들었었다. 내가 원하던 방식과도 결이 같은 평가라고 생각한다.
    • 팀장일 때에 내가 생각한 가장 큰 책임은 제 시간에 팀원들을 퇴근시키는 것이었다. 나름의 소신 때문이기도 했고, 서비스 초반에 뻔질나게 했던 나의 야근들 때문이기도 했다.
  • 회사에 사람이 적을 때는 맞은 편 책상으로 걸려온 전화가 어떤 내용인지에 따라 미리 필요한 자료를 찾아서 던져주기도 했다. 서로 유기적으로 돌아가고, 서로 불필요한 수고를 덜 할수록 좋다고 생각해서 그랬던 거지만, 사람이 적다고 할 수는 없는 상황이 되고 팀제가 강해지고 역할이 나뉘면서 계속 그렇게 하기는 어려워졌다. 대신 자주 손이 가는 작업은 백오피스 도구를 제공하고, 혹시 작은 개선으로 일을 줄일 수 있는지 종종 물어보는 것으로 대신했다.

2. 다뤄본 것
  • CentOS 6.x를 썼다.
    • CentOS 7이 나왔을 때 여러 변화가 있었다고 듣기는 했지만 그 변화들이 장점으로 다가오기엔 변화의 폭이 너무 크게 느껴졌다. ifconfig와 netstat가 구식이 된 것 만큼은 손에 아직 완전히 익지가 않았다.
    • 기왕에 알짜 리눅스 때부터 쓰기도 했고 우분투를 계속 최신 버전으로 올려가며 쓰기 때문에 심각하고 전문적인 수준이 아니면 OS 범위에서 크게 어색한 적은 없었다.
  • 아파치를 썼다.
    • 하지만 아파치를 집요하게 파고들었다고는 할 수 없다. 웹서버 튜닝에 시간을 들여서 얻을 수 있는 소득보다는 다른 걸 하는 게 나은 나날들이 이어졌다.
    • 시간이 좀 지난 뒤에는 PHP CLI를 쓰는 방식을 부분적으로 도입해버렸고, 오히려 PHP-FPM은 제대로 써본 적이 없었다.
  • MySQL은 대체로 흔한 수준의 경험이었다. 여전히 DB에 일가견이 있다고 할 수준은 못 되다 보니 매번 아쉽다.
    • replication을 이런저런 면에서 만지작거려야 하는 시점들이 있었다. 클러스터 같은 구성은 경험해본 바가 없었다. replication도 확장이 쉽다거나 한 건 아니다보니 TiDB 같은 시도가 궁금하긴 하다.
    • PHP에 mysqlnd_ms라는 걸 붙여서 replication 구조 하에서 master/slave를 번거롭지 않게 다루는 방법을 도입했었고, 그 전에 직접 양쪽 연결을 모두 관리하던 것에 비해서는 아주 쓸만한 방식이라고 생각한다. 의외로 다른 언어 다른 프레임워크에서는 잘 다뤄지지 않는 영역인 것 같아서 아쉽다.
    • mysqlnd_ms의 다른 기능으로 slave들의 consistency를 처리해주는 건 여건상 정착시키지 못하고 haproxy로 slave를 하나로 묶는 선에서 끝이 났다. mysqlnd_ms 기본 코드 자체에 버그가 있어서 정상인(=차이가 0인) slave를 무시하는 문제도 있었고, slave의 상태를 확인하기 위해서 SUPER 권한이 필요하기도 했던 것으로 기억한다.
    • SQL 쿼리를 작성하는 방식으로 ORM도 쿼리 빌더도 써봤지만 아무래도 완성된 쿼리를 가지고 있는 게 좋다고 생각한다. DB 포팅을 고려한다고 하지만, 대부분 환경에서 DB 전체를 갈아치우는 일이 얼마나 생긴다고. 그보다는 완성된 쿼리를 가지고 있고 그걸로 성능을 높이고 병목을 없앨 수 있는 가능성을 키우는 게 좋다고 생각하게 됐다.
  • PHP를 쓰면서, 버전마다 새로 도입되는 기능들을 활용하기 위해 노력했다. 본래 설계 목적과는 좀 다를 것 같지만 trait는 초기부터 살이 붙어 비대해진 코드를 쪼갤 때 잘 써먹었다.  generator도 메모리 잠식이 우려되는 부분에서 간간히 쓰곤 한다.
    • PhpStorm을 한동안 썼지만 그리 제대로 썼다고 할 수는 없다.
    • Visual Studio Code를 써봤는데 (phpstorm 라이센스도 오래 됐고, editorconfig 플러그인에 제대로 동작을 안 해서였다) 구미에 잘 맞았다. 설정 몇 개 조정하고, 플러그인 몇 개 깔아주니까 쓰기 편하다.
  • RabbitMQ를 도입해, PHP에서 비동기식 처리가 필요할 때 아예 하나의 task로 큐에 등록하는 방식을 썼다. 꼭 RabbitMQ가 아니라 DB를 써도 되었겠지만 읽고 쓰기의 유연함이나 확장성, 우선순위 적용 같은 걸 생각하면 올바른 선택이었다고 생각한다.
    • 당시에 여러 *MQ를 겉핥기로 찾아봤고, 그나마 RabbitMQ가 OpenStack에서 쓰인다는 점 때문에 믿을만한 것 같아서 선택했다. 초기 도입한 버전에서 몇 가지 문제를 겪긴 했다. :P
    • 차라리 ReactPHP 같은 걸 진작에 도입해버렸으면 어땠을까 싶기도 하지만, 그것도 나름대로 여러가지 문제가 있었겠지.
  • 이메일 대량 발송을 매주 해야 해서, 어쩌다보니 RabbitMQ 기반으로 여러 대의 발송 서버를 구축하는 데까지 갔다.
    • 대체로 쓸만하지만 자동화할 여지가 있긴 하다. 작성 단계가 민감한 수작업의 성격을 완전히 벗지 못했고, 발송도 사람 손을 완전히 안 타지는 않는다.
    • DNS에 SPF니 뭐니 이런저런 것들을 설정하는 건 지금도 남한테 설명해줄 만큼 정확히는 모르겠다.
  • AWS는 익숙해질만큼 오래 쓰진 않았다.
    • Route 53은 개인 도메인을 옮겨간 상태다. DNSever를 잘 썼지만 AWS의 각종 기능에 잘 녹이려면 어쩔 수가 없었다.
    • S3 + CloudFront는 미투데이 백업 html을 올려서 아주 가끔 보곤 한다.
  • Azure. 애증의 MS. 몇몇 부분에서 요긴하게 쓰고는 있지만 개인적으로는 쓰고 싶지 않다고 매번 투덜거린다.
    • S3 + CloudFront에 대응하는 역할로 쓰고 있다.
    • Azure Function은 웹서버로 접속이 한 번에 확 몰릴 때를 대비하기 위해서 도입했다. 처음에는 도입만 했지 제대로 구성을 못해놔서 한바탕 곤란을 겪고, 그 뒤에 Consumption Plan이라는 걸 썼어야 한다는 걸 배웠다. 지금 구성도 아주 깔끔하다고 생각하지는 않지만 개념 자체는 복잡하지 않기 때문에 앞으로도 원래 의도했던 역할은 잘 해줄 거라고 본다.
    • Azure Function이 대외적으로는 PHP나 Node.js를 지원한다고 선전하지만 실제로는 런타임 적재를 하느라 생기는 것 같은 초기 지연이 상당하다. 단순히 콜드 스타트 문제라고 하기엔 양상이 달랐던 걸로 기억한다. 콜드 스타트를 방지하기 위해 Consumption Plan이 아니라 App Service Plan을 써야 한다는 얘기도 있던데 필요한 방향이랑 안 맞아서 채택할 수 없었다.
  • C#은 맛만 봤지만 느낌이 좋았다.
    • Azure Function 동네에선 역시 C#이 1등 시민이다. 덕분에 PHP와 Node.js로 짠 코드를 C#으로 똑같이 다시 짜는 경험을 해본 적이 있다.
    • 그 뒤로 PHP와 달리 타입이 전제되는 언어에 호감이 생겼다. 익숙하지 않은 Azure Function 환경에서 PHP로 짰을 때는 어디선가 실제 서비스에서 오류가 터지고 나서야 놓친 부분이 있는 걸 알았지만, C#으로 짰을 때는 빌드 단계에서부터 오류가 보였기 때문에 실제 서비스에는 문제를 내보내지 않을 수 있었다. Application Insights라는 Azure의 모니터링 도구에도 오류가 거의 없다시피 하다. 그 전에는 드문드문 잡히는 오류에 이유를 짐작할 수도 없어서 그냥 PHP 구동 문제 아니면 네트워크 통신 문제이려나 짐작만 할 뿐이었는데.
  • React를 도입했다.
    • ReactDOM 모듈이 분화되기 직전의 버전으로 시작했었는데, 구버전 IE를 지원하기 위함이기도 했지만, JSX 렌더링을 Node가 아니라 PHP의 v8js 모듈로 처리하다 보니 굳이 React 버전을 올려가면서 쓸만한 상황까지 되지 않았다. v8 엔진 자체의 발전은 종종 구경거리 삼아 보곤 한다.
    • 눈에 보이는 영역이고 다양한 상태가 존재한다면 JSX를 쓰는 게 직관적이라고 생각한다. jQuery로 이미 그려진 DOM을 바꾸기보다는 한 방향으로만 흘러서 DOM을 만들어내는 게 머리도 덜 복잡하다. JSX를 도입하지 않은 부분에서도 JSX처럼 '주어진 조건에 맞게 그려내기만 하는' 방식을 쓰기도 했다.
    • 덕분에 함수형 프로그래밍이라는 것에도 호감이 있다.
    • 다만 업무 담당자가 분화될 때 JSX라는 게 진입장벽으로 작용해서 아쉽다. 흔히 눈에 보이는 것을 다룬다면 HTML5에 CSS3 정도면 전문적이라고 여겨지는데, 거기에 자바스크립트라는 생소한 물건을 들이밀면 그게 아무리 HTML처럼 생겼다 할지라도 동작 방식이 쉽사리 전달되지는 않았다. v8js를 채택한 게 이런 상황을 더 나쁘게 만들었던 것 같기도 하다. 차라리 흔한 Node 환경이었으면 적응하기는 낫지 않았을까 싶다.
  • PG사를 옮겨봤다.
    • 이니페이, 나이스페이, 그리고 검토만 했던 여러가지들.
    • 아임포트 같은 중간 서비스가 왜 존재하는지 너무나도 잘 알 것만 같다.
    • 매번 '일단 되게'하는 데 급급하다 보니 내재한 논리는 설명해야만 알 수 있고 코드 수준에서 정리는 거의 안 되어 있지만. 
    • 거래 고유번호(!)가 겹칠 수도 있고, PG사도 부하가 걸리면 새벽에 통보없이 자사 IP를 변경할 수 있다는 것도 알게 되었다.
  • Google Tag Manager를 도입했다.
    • 어느 정도 서비스가 궤도에 오르고 나서는 서비스의 현황을 파악하는 것도 일이었는데, 이런저런 분석 도구를 서비스에 붙이는 게 빈번해지고, 어떤 조건에서 어느 시점이 측정되어야 하는지도 점점 복잡해졌기 때문에 이 부분을 해소할 도구를 찾다가 ᅟGTM이 적당해서 서비스에 붙였다.
    • Google Analytics를 비롯해 이런저런 분석 도구를 상당한 자유도로 엮어넣을 수 있어서 좋다.
    • 하지만 역시 학습곡선이 가파르다. 그냥 기본 사용법대로 쓰는 수준을 벗어나기 시작하면, HTML 페이지의 렌더링이 어떤 방식으로 일어나는지 알아야 딱 맞는 설정을 할 수 있는 식이다 보니 아무래도 '측정은 해야 하지만 기술과는 거리가 있는' 직군의 사람들에게 친근하게 다가갈만한 물건은 못 되는 것 같다. 그래도 SuperTag라는 GTM 유사 제품의 소개 행사에 마케팅 관련 인원이 관심을 보였던 걸 생각하면 기능적으로는 필요한 물건을 맞게 찾았구나 하고 생각했다.
    • GA 외에 FB Pixel 등도 같이 지원해야 한다거나, 유튜브에 가서 일어난 사용자의 활동을 사이트 안에 붙은 GA와 연계할 수 없는 등의 한계가 있었다. 검색에 딱히  나오는 것도 없고 논리적으로 생각해보면 불가능한 게 맞을 것 같아서 깊이 파고들지는 않았다.
  • 분석업무에 R과 Python 쓰는 걸 도왔다.
    • 내가 분석업무를 직접 긴밀하게 다뤄본 적은 없지만 눈으로 따라가면서 막힌 부분을 설명해줄 수 있는 정도는 되다 보니 중간중간 나도 키보드 너머로 구경을 좀 했다. IPython Notebook이 Jupyter라는 이름이 되면서 다룰 수 있는 게 아주 많아진 것 같아 신기했다. 
    • 분석업무의 부담을 덜어줄 생각으로 아예 DB 내용을 가져다가 시각화까지 해준다는 곳을 몇 가지 구경해봤지만 개인정보 제3자 제공의 문제도 생기고 해서 본격적으로 검토하지는 않았다. 아파치 재단 산하에 Spark, Zeppelin 같은 게 존재하는 건 알고 있지만 깊이 검토해본 적은 없다.
  • SVN을 쓰다가 Git으로 넘어왔다. FTP에 직접 접속해 편집하던 시절에 비하면 (하략)
  • 외부 서비스 연동도 이것저것 해야 했었다.
    • 제공되는 API를 다루는 건 OAuth 등으로 여러 서비스를 붙여봤다. API 버전이 바뀔 때마다 손을 대야하거나, OAuth 토큰에 생명주기가 들어가면서 생긴 번거로움은 서비스 자체의 동작 방식과 상충되는 면이 있어서 깔끔한 해결책을 찾지는 못했다.
    • 그래도 API가 제공되는 게 나은 편이라, SMS 발송 때 발신자 번호표시 정책 때문에 업체를 바꿀 때는HTTP API를 제공하는 업체를 선정했다. 여기도 API 버전이 한 번 바뀌어서 급하게 대응을 하긴 했지만 내부 서버에 데몬을 돌리는 것보다는 훨씬 만족스럽다.
    • 소위 크롤링이라고 하는, 외부 사이트의 멀쩡한 HTML 페이지 긁어서 원하는 작업을 수행하는 것도 해야 했었다. GET뿐 아니라 POST도 했으니 거의 API 껍데기를 씌운 셈이었는데 다행히 이걸 쓰는 동안 그 사이트가 바뀌지는 않았는지 따로 손을 본 적은 없었다.

3. 다뤘어야 하는 것
  • DB를 읽는 시점에 많은 계산을 하면 안 되는 거였다. DB에 자료가 기록되는 시점에 이런 저런 일련의 작업들이 일어나고, 읽을 때는 그 흔적만 가져오는 식이어야 했다. 나중에 일어나는 많은 문제들이 여기서 비롯하는 셈이었다.
  • 캐시 관리를 진작 강화해야 했었다. DB의 읽기/쓰기를 똑똑하게 다루지 못했다면 캐시 정책이라도 똑똑해야 했다. Decorator 입히는 수준으로 캐시 적용을 쉽게 만들었어야 했는데 그러지 못했다. 한편으로는 static site generator의 껍데기를 입히면 어떨까 생각도 해보았지만 거기까지 가기엔 그 사이에 건너야 할 것들이 많았다.
  • 미들웨어 개념이 있는 프레임워크를 도입해야 했다. 꼭 Laravel 같이 널리 알려진 물건일 필요는 없어도, 하나의 HTTP Request가 최종 Response로 끝나기 전에 여러 동작을 다양한 층위에서 다루는 방식이 필요했다. 미들웨어라는 게 없는 상태에서 여러 다양한 로직을 처리하려다 보니 길고 난삽한 코드가 만들어지기 십상이었다. klein.php라는 걸 라우팅 강화 목적으로 도입하면서 미들웨어를 지원하는 기본적인 틀은 갖추었는데 더 발전을 시켰어야 했다.
  • 검색 엔진. Elasticsearch를 도입하려는 시도는 했지만, 결과적으로 접혀버렸다. 검색 엔진 자체보다는 인덱싱 부문에 공을 들여야 했는데 아무래도 내가 너무  쉽게 덤볐다.
  • 하나의 큰 목표를 작은 과제로 나누고 단계적으로 진행하는 걸 정착시켜야 했는데 여러 정황으로 볼 때, 그렇게 해야 하고 그렇게 하고 있다는 걸 다른 사람들에게는 인식시키지 못했던 것 같다.
  • 다 적자면 끝이 없겠지.

4. 다루고 싶은 것
  • 여전히, 자료를 다양한 기준으로 표현하는 게 충분하지 않다고 생각한다. 시간 순서로는 iCalendar 같은 게 있고, 위치 기준으로는 지도가 있고, 그 밖에도 여러 기준축이 존재할 수 있는데 쌓여있는 데이터에 비해 가공과 정리와 표현의 노력이 충분치 않았다.
  • PHP 외의 환경을 다뤄야 한다. 꼭 PHP를 벗어나기 위함이라기보다는 다른 언어나 프레임워크에서 어떤 접근방식을 쓰는지, 어떤 간단한 방법이 있는지를 구경하다 보면 딱 이럴 때 쓰면 좋겠구나 싶은 지점들이 있어서 도움이 된다. 개인적으로는 Django를 선호하는 편이고, Node도 좋고.
  •  템플릿 엔진을 개선하고 싶다. 흔히 템플릿 엔진이라는 건 무슨 자료가 채워져야 하는지 다 아는 상태에서 미리 준비를 하고 끝에 오는 템플릿 엔진으로 넘겨주는 식인데, 방향을 바꿔서 템플릿에 어떤 요소들이 있는지에 따라 그에 맞는 동작을 하면 장점이 있다고 본다. 
    • 가령 흔히 프론트엔드라고 하는 작업 단계의 결과물을 백엔드라고 하는 작업 단계에서는 다 분해해서 새로 조립하는 식인데, 일이 이렇게 돌아가면 어떤 수정이 필요할 때 프론트엔드 작업자가 기존 작업물을 그대로 수정하는 것만으로는 부족해서, 백엔드 환경까지를 어느 정도 이해할 수 있거나, 짐작으로 글자만 수정하거나, 아니면 백엔드 작업자가 수정을 하는 식이어야 한다. 이 방식을 바꿔서 템플릿을 그대로 둔 상태로 작업을 한다면 서로 작업영역이 겹치지 않고 해야 할 걸 할 수 있다.
    • Level-2/Transphporm이라는 게 DOM+CSS 선택자(내부에선 XPath) 구성으로 이런 게 가능하도록 만들었는데 잠깐 검토한 바로는 CSS 선택자의 해석을 직접 구현하고 있어서 버그라고 할까 의도와 다른 동작들도 보이고 해서 실사용엔 적합하지 않아 보였다. 하지만 개념만은 충분히 훌륭하다.
  • 프로비저닝과 배포 자동화, 혹은 CI라고 하는 것까지를 다뤄야 한다. 생각이 여기에 이르면 매번 지치는 기분이 들지만 필요한 작업이다. 그동안 기술적인 고민을 나름대로는 많이 했지만 당장 매출이 나는 서비스에 나쁜 영향이 조금이라도 갈 것 같다면 쉽게 저지를 수가 없었다. 배포 쪽이 매끄럽게 다듬어진다면 이런 부분이 덜 무서워지겠지. 
    • Docker는 환경 빌드 차원에서 만지작거리기만 했고, 도입했을 때의 뒷감당을 할 여건이 되지 않는다고 판단하여 완전히 적용하지는 못하였다.