django를 관리툴로 쓰자
서버를 둘로 나누자

개요

서비스를 개발하다보면 어떤 식으로든 어드민 툴이 필요해진다. 간단한 게시판을 만든다고 생각하자. 관리자의 업무중에는 게시판의 스팸글을 지우는게 있을 것이다. 스팸글 몇 개는 SQL query로 대응할수 있을 것이다. 그러던 어느날, 쿼리에 게시글 id를 잘못 넣어서 의도하지 않은 게시글을 지워버릴지도 모르는 일이다. 또는 게시판 관리하는 업무를 다른 사람한테 넘기고 싶은데 SQL을 아는 사람만 업무를 이어받을 수 있는 상황이 될지 모른다. 어드민 툴을 만들었으면 마우스 클릭 하나로 글을 지울 수 있었을것이다. 그렇다면 쿼리 입력하다 실수할 가능성도 낮아지고 다른 사람한테 일을 넘기도 쉽다.

하지만 관리툴을 직접 짜는건 귀찮다. 시키면 못할건 아닌데 이런 뻔한 기능에 시간과 돈을 써야하나? 관리툴 짤 시간과 노력으로 기능을 추가하는게 돈이 더 되지 않을까? 귀찮다고 미루다보면 시간이 갈수록 문제가 커질수 있다.

사용 가능한 자원도 제한되어있다. 시간도 자원이고 인력도 자원이고 예산도 자원이다. 어떻게 하면 관리툴 개발 기간을 줄일 수 있는가? 어떻게 하면 관리툴 개발에 사람을 덜 넣어도 될까? 이런 고민을 하고 효율적으로 일해야 프로젝트의 성공률을 올릴 수 있다.

이런 고민의 결과, 이전에 작업한 프로젝트는 rust로 API 서버를 짜고 django로 관리툴을 짰다. 그때의 경험을 기반으로 글을 써본다.

개념

테이블 구조를 API 서버와 관리툴에서 똑같이 맞춰둔다. 내 경우 API 서버는 rust였고 그냥 SQL query로 테이블을 만들어서 썼다. django로는 생성된 테이블과 똑같이 생긴 django model을 만들었다. 그리고 API 서버와 관리툴이 같은 DB를 바라보게 하면 된다. API 서버가 DB에 집어넣은 것인지 관리툴이 DB에 집어넣은 것인지 구분할 방법이 없으니 잘 굴러갈 것이다.

장점

숟가락 얹기

관리툴의 기능 중 SQL DB 서버를 다루는 부분은 매우 뻔하다. 테이블에서 적당히 검색할수 있고 테이블의 내용을 적당히 수정할 수 있으면 된다. 하지만 뻔한 부분이 쉬운 부분은 아니다. 직접 짜기 귀찮은 부분도 잘 만들기 어려운 부분도 있다. 간단해 보이지만 생각보다 거대한게 관리툴이라는 물건이다.

다행히도 관리툴은 남들이 만들어둔게 많다. 적당히 묻어가면 된다. django admin은 잘 만들어져있고 기능도 강력한 물건이라서 나는 django를 좋아한다. django admin 말고도 비슷한 기능을 하는 프로그램은 많다. 적당히 줏어다 써도 직접 구현하는 것보다는 싸게 먹힌다.

복잡도 낮추기: 언어 선택의 자유

API 서버와 관리툴을 따로 구현한다면 둘의 언어, 프레임웍을 다르게 써도 된다. 둘은 DB만 공유하면 된다. API 서버는 API 만들때 최적화된 언어와 프레임웍을 선택할 수 있다. 관리툴은 관리툴 만들때 최적화된 언어와 프레임웍을 선택할수 있다.

이전에 작업한 프로젝트는 성능이 중요해서 rust로 API 서버를 구현했다. 관리툴은 빨리 개발할수 있는 파이썬을 선택했다. 만약 rust로 API 서버부터 관리툴까지 개발했다고 개발 일정이 터졌을 것이다.

요새는 서버 구현할 일이 있으면 typescript를 쓴다. IDE에서 자동완성도 잘 되고 리펙토링도 잘 되고 쓸만한 라이브러리도 많이 있고 좋더라. 근데 typescript, javascript 진영에서는 멀쩡한 ORM과 ORM에 붙는 관리툴을 찾기 못했다. 그래서 django로 관리툴만 붙여서 쓰고있다.

API 서버와 관리툴을 합쳐서 만들어야한다는 생각에서 벗어나면 다른 길이 보인다.

복잡도 낮추기: 작은 프로젝트

API서버와 관리툴은 테이블을 공유하지만 모든 테이블을 공유할 필요는 없다. 관리툴에서만 필요한 기능이 있다면 관리툴에서만 필요한 테이블도 있을 것이다. 거대한 하나의 프로젝트를 잘 나누면 작은 프로젝트 2개가 될 것이다.

rust 컴파일 시간은 매우 느리다. 만약 API 서버안에 관리툴도 때려박았으면 컴파일 시간이 2배가 되지 않았을까? 그랬으면 하루종일 컴파일 돌아가는것만 보고있었을지도? 그리고 프로젝트가 망했겠지.

커스텀 관리툴

django admin은 좋지만 django admin만으로 모든 관리툴을 대응하는건 좋은 선택이 아니다. 일부 관리툴은 직접 짜는게 나을때가 있다. django는 웹 어플리케이션 만들라고 나온 물건이니까 django admin 만으로 대응하기 어려운 부분은 그냥 짜면 된다. 저번에 작업한 프로젝트 였으면 rust로 관리툴용 API 구현하고 react로 프론트엔드를 구현했을 것이다. 이렇게 하는것보다는 django로 로직 구현하고 jinja2로 템플릿 짜는게 빠르더라.

모든 DB가 관계형 데이터베이스인건 아니다. elasticsearch나 redis에 저장된 데이터를 보여주는 관리툴이 필요할 수 도 있다. 내 경우 유저의 이벤트 로그를 보여주는 관리툴을 짜야했다. 그리고 이벤트 로그는 elasticsearch에 저장되어 있었다. 파이썬으로 elasticsearch에서 로그를 빼오는 것은 rust로 elasticsearch에서 로그를 빼오는 것보다 구현하기 쉽다. python 정도면 좋은 언어라서 관리툴을 직접 짜야하는 상황도 적당히 대응할 수 있다.

준비된 서버

rust로 짜면 안전하게 메모리를 사용할수 있고 좋은 성능을 얻을 수 있다. 하지만 rust의 컴파일 시간은 느리다. rust로 개발하면 개발 이터레이션도 늘어진다. rust로 모든 API를 구현하는건 비용이 비싸다.

그래서 잔머리를 굴렸다. 성능이 중요하지 않은 일부 API는 rust로 말고 딴걸로 짜자! 프록시 서버로 사기치면 밖에서 볼때는 API 서버인지 따로 구현된 서버인지 구분할 방법도 없다.

다른 팀 같았으면 rust가 아닌 다른 언어로 추가 API를 구현한 후 새로운 서버에 올리는게 가능했을지 모른다. 근데 나는 외주라서 갑님들 인프라에 접근할 수 없었다. 인프라에 접근할수 있어야 새로운 서버를 띄워서 올릴텐데 그건 안되겠더라.

그래서 이미 돌아가고 있는 관리툴 서버를 이용하기로 했다. 관리툴 서버는 이미 돌아가고 있으니 인프라는 신경 안써도 된다. 관리툴 띄우느라 도커 이미지도 이미 만들었고 포트도 뚫었고 밖에서의 접속까지 확인된 상태였다. 나는 API만 구현하면 된다. 신경쓸게 없으니 편하더라.

물론 야매에 가까운 것이다. 인프라 접근 권한이 있어서 새로운 서버를 직접 띄울 권한이 있다면 굳이 이렇게까지 쓸 이유가 없다.

단점

모델 유지보수

API 서버 구현하면서 table schema를 고치면 django 에서도 model을 고쳐야한다. 안고치면 django admin에서 500을 보게 된다. 이것에 대해서 긴 이야기는 나중에 다른 글에서 취급할지 모르겠다. 또는 레거시 시스템에 django로 다가가기를 읽어보자.

그래도 관리툴 서버 터졌다고 API 서버도 같이 터지지 않는다. 둘은 다른 서버니까. 같은 테이블을 바라볼 뿐이다. API 서버가 앞서가고 관리툴 서버가 뒤따라가면 서비스가 통째로 뒤지지 않을 것이다.

고오급 개발자가 필요

언어를 섞어 쓸수 있다는 것은 장점이 될수 있지만 단점이 될 수 도 있다. 저번 프로젝트의 경우 API 서버는 rust, 관리툴은 django를 썻다고 했다. 그렇다면 관리툴 유지보수를 위해서는 rust와 django를 동시에 쓸 줄 아는 사람이 필요하다. 관리툴을 django로 짠다고해도 모든 관리툴을 django만으로 대응하는건 어렵기 떄문이다.

뷰어 기능은 django만으로 구현 가능하다. DB에서 값을 가져와서 이쁘게 보여주는게 전부니까 문제 없다. 하지만 테이블에 저장된 값을 이용해서 계산한 후에 보여줘야한다면? 계산 코드는 rust에만 있다면? django에도 똑같은 값을 보여주고 싶다면? rust 코드를 읽고 해당 부분을 파이썬으로 다시 짜야한다. 아니면 테이블에 계산된 값을 같이 때려박는 것도 방법이다.

간단한 예를 생각해보자. DB에는 생일이 저장된다. API 서버는 나이를 계산해서 보여주는 기능이 이미 있다고치자. 그리고 관리툴에서도 나이를 보여주고 싶다. 이 경우 생일로부터 나이를 계산하는 로직이 django에도 필요하다. 똑같은 로직을 API서버, django에 구현하는 경우 로직이 바뀌면 유지보수가 귀찮다. 이를 피하는 가장 쉬운 방법은 테이블에 나이 정보도 때려박는거다.

수정 기능이 들어간 관리툴은 django로만 구현하는게 어렵다. 테이블의 필드 하나만 고치는 간단한 수준이면 django 관리툴을 써도 된다. 하지만 수정할때 수행되어야하는 기능이 복잡하다면 어떨까? django로 똑같이 구현하는 것도 방법이지만 나중에 문제가 생길 가능성이 크다. API 서버 개발자가 로직을 바꿀 가능성이 있다. 뷰어 기능의 경우는 django에서 구현한 것이 깨져도 보이는 것이 깨질 뿐이다. 하지만 수정 기능은 django에서 구현한 것이 깨지면 저장되는 데이터가 깨지고 DB의 무결성을 보장할수 없게 된다. 내 생각에 수정 기능 만큼은 API 서버에 배치하는게 안전할거같더라.

관리자가 유저의 포인트를 차감하는 기능을 만든 적이 있다. 포인트를 뺀 다음에 음수가 되지 않도록 금액 검증하는 기능이 필요했다. 포인트가 수정되면 유저의 현재 포인트를 저장하는 balance table의 내용도 바뀌어야 했다. 포인트 수정 기록은 balance log table에 저장하고 싶었다. 기능이 충분히 복잡해보여서 관리툴용 API를 rust로 구현해서 API 서버에 집어넣었다. 그리고 django에서는 http request를 호출했다.

복잡도 상승: 2개의 시스템

거대한 프로그램을 2개로 쪼개면 복잡도가 줄어든다. 하지만 하나인것을 2개로 쪼개면서 없던 복잡도도 생긴다.

관리툴에서 이런 기능도 구현했었다.

  1. 관리툴에서 시작 버튼 누름
  2. 관리툴 DB를 읽어서 적당한 JSON을 생성
  3. API 서버의 시작 API를 호출. 아까 만든 JSON을 넘긴다
  4. API 서버에서 시작 ID를 얻을수 있다.
  5. 관리툴에 시작 ID 저장하기

시스템을 통짜로 구성했으면 API 호출이 http request가 아니라 함수 호출이 있을 것이다. 하지만 시스템을 나눠쓰기 때문에 http request가 생겼다. (프로토콜을 바꾼다고 해도 로컬은 아니다) 서버간의 경계에서 문제가 생기는건 디버깅하는게 쉽지 않을수 있다.

이 경우 서버의 경계를 넘는 API 호출때문에 복잡도가 상승한게 있다. 하지만 데이터를 구성하는 부분과 데이터를 처리하는 부분으로 쪼개면서 복잡도가 줄어든 지점도 있다. 복잡도가 늘어난 것과 줄어든 것을 합쳐봣을때 복잡도가 늘어났으면 그건 뻘짓이다. 프로젝트를 진행하는 동안 각을 잘 재야한다.

summary

새로운 기법이 있을때 장점만 떠들면 사기꾼이다. 장점만 있는 기술이 세상에 어디에 있는가? 트레이드 오프(trade-off) 라는 말이 괜히 있는게 아니다. django를 관리툴로 쓰면 장단점이 있다. 현재 프로젝트에 적용했을때 쓸만한지 생각해보자.


comments powered by Disqus