스토리 홈

인터뷰

피드

뉴스

조회수 1881

파이썬의 개발 “환경”(env) 도구들

안녕하세요. 스포카 프로그래머 홍민희입니다.파이썬 패키징 생태계에서 개발 환경을 구성하기 위해 널리 쓰이는 virtualenv나 pyvenv, virtualenvwrapper 같은 각종 도구가 왜 필요한지 (또는 자신에게는 큰 도움이 안 되는지) 알려면 그 이전의 파이썬 라이브러리 배포 방식에 대한 이해가 많은 도움이 됩니다. 여기서는 필요한 몇 가지 역사적 사실과 파이썬 패키징 개념 중 현재의 생태계 이해에 필요한 것들을 위주로 정리하고, 최종적으로 각자의 필요에 따라 어떤 도구를 활용하면 될지 지침을 제안합니다.sys.path패키징이고 뭐고 아무것도 없던 90년대 말에는 라이브러리 소스 코드 파일들을 타르볼(tarball)로 압축해서 배포했습니다. 쓰는 사람은 그걸 자신의 애플리케이션 소스 트리 안에 풀어서 사용했습니다.파이썬에는 지금도 sys.path라는 인터프리터 전역적인 상태가 존재합니다. PATH 환경 변수가 실행 바이너리를 찾을 디렉터리 경로들의 목록인 것과 비슷하게, sys.path도 import foo를 하면 foo.py (또는 foo/__init__.py) 파일을 찾을 디렉터리 경로들의 목록을 담습니다. 그리고 기본 동작으로 그 목록의 맨 처음에는 현재 디렉터리(./)가 들어갑니다. 따라서 라이브러리 타르볼을 애플리케이션 소스 트리에 풀어두면 import해서 쓸 수 있습니다.하지만 자신이 작성한 애플리케이션 코드와 남이 작성한 라이브러리 코드를 같은 소스 트리에서 관리하는 것은 여러모로 불편합니다. 따라서 라이브러리는 애플리케이션 소스 트리와는 별도의 디렉터리(예: ../libs/)에 풀어서 관리하고, 애플리케이션 소스 코드 맨 위에 아래와 같이 써두는 패턴이 많았습니다.import sys sys.path.append('../libs') 또는 sys.path를 소스 코드를 건드리지 않고 조작하기 위해 PYTHONPATH 환경 변수를 활용하는 경우가 많았습니다.세기말, 파이썬 1.5를 쓰던 때의 이야기입니다.site-packages새 천 년이 밝았고 파이썬 2.0이 나왔습니다. 표준적인 라이브러리 배포 방식 및 설치 방식이 제안되었고, 표준 라이브러리에 distutils도 들어왔습니다. (지금도 setuptools는 distutils에 의존하고, pip는 setuptools에 의존합니다.) 제안된 방식은 이랬습니다.애플리케이션 코드가 아닌 라이브러리 소스 코드는 모두 /usr/local/lib/pythonX.Y/site-packages/ 디렉터리 안에 둡니다. X.Y는 파이썬 인터프리터 버전이고, 경로는 인터프리터를 빌드할 때 (./configure) 정합니다. 데비안 계열은 site-packages 대신 dist-packages라는 이름으로 바꿔서 빌드하는 등, 파이썬 인터프리터의 설치 방식에 따라 달라집니다. 어떻게 정하든 이를 site-packages 디렉터리라고 부릅니다. 파이썬 인터프리터를 빌드할 때 경로가 결정되므로, 파이썬 인터프리터 별로 각자의 site-packages 디렉터리를 갖게 됩니다. (한 시스템에서 여러 파이썬 버전을 설치했을 때 pip 역시 pip2.7, pip3.6 등과 같이 버전 별로 명령어가 생기는 것도 같은 이유입니다.)기본적으로 sys.path 목록에는 맨 앞에 현재 위치(./), 뒤쪽에는 site-packages 경로가 들어있습니다. import를 하면 현재 위치에서 찾고, 없으면 site-packages를 찾아본다는 뜻입니다.표준 라이브러리의 distutils.core.setup() 함수는 라이브러리 파일들을 시스템의 site-packages 디렉터리에 복사해주는 함수입니다. 라이브러리 타르볼 파일 맨 바깥에는 이 함수를 이용해 라이브러리를 시스템 site-packages에 설치해주는 스크립트를 setup.py라는 파일명으로 포함하는 관례가 있었습니다. pip 같은 게 없던 때에는 라이브러리 타르볼을 받아서 푼 다음 python setup.py install 명령을 실행하는 것이 일반적인 라이브러리 설치법이었습니다. 지금도 pip는 *.whl 파일이 아닌 *.tar.gz/*.zip 파일인 패키지를 설치할 때 내부적으로 python setup.py install 스크립트를 실행합니다.참고로 이때 정립된 파이썬 패키징 표준은 리눅스에서 쓰이는 dpkg나 RPM 같은 일반적인 패키징 방식을 의식하며 만들어졌습니다.1 당시는 도커는 커녕 가상화 자체가 보편적이지 않던 때로, 한 시스템에 여러 애플리케이션을 함께 설치해서 쓰는 멀티테넌시 환경이 일반적이었기 때문입니다.workingenv파이썬으로 작성한 애플리케이션 여럿이 한 시스템에 설치되면 공통으로 의존하는 라이브러리의 버전을 결정하는 게 문제가 됩니다. A 애플리케이션은 foo >= 1.0.0, < 2>에 의존하고 B 애플리케이션은 foo >= 1.5.0에 의존하면 시스템에 설치할 수 있는 foo의 버전은 >= 1.5.0, < 2>으로 한정됩니다. 만약 C 애플리케이션을 설치하려는데 foo > 2.0.0에 의존한다면, A나 C 중 하나는 포기해야 합니다.시스템에 파이썬 애플리케이션을 단 하나만 설치한다 해도, 설치하는데 시스템 관리자 권한이 필요하다는 것도 문제였습니다. 일반적으로 site-packages 디렉터리는 시스템 관리자만 수정할 수 있고 나머지는 읽기만 가능한 /usr 아래 어딘가로 정해졌기 때문입니다. 이를 우회하려고 사용자가 시스템에 설치된 파이썬 인터프리터를 쓰지 않고 직접 파이썬 인터프리터를 빌드해서 사용하는 편법도 쓰였습니다.이런 문제를 해결하기 위해, 애플리케이션·프로젝트마다 별도의 site-packages 디렉터리를 두는 방식이 제안됐습니다. 나중에 virtualenv을 만들게 되는 이안 비킹이 그 전신인 workingenv를 만들어 이 아이디어를 실현했습니다. 현재의 virtualenv 사용 방식은 workingenv에서 만들어진 것입니다.애플리케이션마다 별도의 “환경”(env)을 만듭니다.애플리케이션을 실행하기 전에 우선 그 “환경”을 “활성화”(. bin/activate 또는 Scripts\activate.bat)합니다.workingenv가 만들어주는 활성화 스크립트는 PATH와 PYTHONPATH 환경 변수를 재정의하여 시스템에 설치된 파이썬 인터프리터의 실행 바이너리 디렉터리 및 site-packages 디렉터리를 가리키는 대신, “환경” 내의 bin/ 및 site-packages 디렉터리를 바라보도록 해줍니다. 이안 비킹은 이렇게 분리된 실행 파일들(bin/)과 site-packages 등을 묶어서 “환경”이라고 명명했는데, workingenv 이후로 파이썬 패키징 및 배포 분야에서 이 용어가 정착됩니다.최근에 만들어진 신생 언어의 패키지 관리자는 대부분 파이썬과 달리 애플리케이션·프로젝트마다 별도의 환경을 두고 설치되는 경우가 많습니다. 예를 들어 npm은 -g 옵션을 일부러 켜지 않는 한 현재 디렉터리를 기준으로 ./node_modules 디렉터리에 라이브러리를 설치하게 되어 있고, 별도의 “활성화” 없이도 노드 인터프리터가 해당 경로에서 라이브러리를 찾습니다. 하지만 파이썬의 패키징 표준은 앞서 언급한 것처럼 멀티테넌시 환경이 일반적이었던 시대에 만들어졌고, 또 많은 라이브러리가 실행 파일도 함께 제공하기 때문에2 PYTHONPATH 뿐만 아니라 PATH 환경 변수도 재정의해야 해서 activate 과정이 필요합니다.workingenv는 파이썬 웹 프로그래머 사이에서 빠르게 퍼지기 시작했습니다. 웹 애플리케이션은 정통적인 CLI 및 GUI 애플리케이션과 달리 FHS 표준 같은 것에 크게 구애될 필요가 없었고, 웹 애플리케이션의 배포도 점차 가상화 기술을 통해 완전히 격리된 시스템에 설치되는 식으로 보안 문제에서 많이 자유로워졌기 때문입니다.무엇보다 workingenv는 프로그래머가 여러 프로젝트를 동시에 작업하는 경우 골치 아팠던 라이브러리 버전 충돌 문제를 우회했기 때문에, 배포 도구보다는 개발 도구로 정착되는 면이 컸습니다.virtualenv이안 비킹은 PYTHONPATH를 조작하여 별도의 site-packages 공간을 두는 workingenv의 방식이 복잡하게 패키징된 기존 라이브러리 및 프로젝트에서 호환되지 않는 문제로 골머리를 썩이다, 아예 PYTHONPATH를 이용하지 않는 방식으로 새 도구를 만듭니다.새로운 방식은 아예 파이썬 인터프리터 실행 바이너리를 복사한 뒤, sys.path 기본값에 박힌 시스템 site-packages 경로를 환경 내 site-packages 경로로 바꿔버리는 것이었습니다. 이러한 동작 원리의 차이는 이용자 입장에서 크게 중요한 것은 아닙니다.하여튼 이안 비킹은 virtualenv라는 이름으로 새 도구를 만들었고, workingenv를 빠르게 대체했습니다.virtualenvwrapper앞서 언급한 것처럼, workingenv와 그 후계자인 virtualenv는 저자의 의도와 무관하게 애플리케이션 배포보다는 개발 용도로 더 널리 쓰입니다. 파이썬 프로그래머가 새로운 프로젝트를 시작할 때는 항상 “환경”도 생성합니다. 또 개발을 시작할 때마다 “활성화” 과정도 거칩니다. 너무나 반복적이기 때문에 당연히 이를 자동화하는 도구도 만들어졌습니다. virtualenvwrapper는 바로 그런 목적으로 만들어진 bash/zsh/fish 스크립트 모음입니다.여러 단축 명령을 제공하지만, 핵심 기능은 다음의 두 가지입니다.A라는 프로젝트 작업을 시작할 때마다 cd ~/projects/a; . .venv/bin/activate라고 쳐줘야 했던 것을 workon a 명령으로 줄여줍니다.프로젝트 디렉터리마다 .venv/ 또는 .env/ 등의 이름으로 환경 디렉터리를 생성해두고 버전 관리 시스템에서는 제외되도록 .gitignore 목록에 해당 디렉터리를 넣었어야 했습니다. 예를 들어 ~/projects/a/.venv/, ~/projects/b/.venv/ 같은 식이었습니다.virtualenvwrapper를 쓰면 환경 디렉터리들을 일정한 위치로 모아줍니다. 위치는 기본값이 없으며 virtualenvwrapper 설치할 때 WORKON_HOME 환경 변수를 통해 입맛대로 정할 수 있습니다. 예를 들어 WORKON_HOME을 ~/.virtualenvs/ 디렉터리로 정했다면, 프로젝트별 환경은 ~/.virtualenvs/a/, ~/.virtualenvs/b/ 같은 식으로 저장됩니다.pyvenv파이썬 3.3부터는 virtualenv가 아예 파이썬에 내장됐습니다. 환경을 만드는 명령어는 virtualenv가 아닌 pyvenv로 좀 다르지만, 그 이후의 과정은 같습니다. 파이썬 3만 사용한다면 이제 virtualenv를 따로 설치할 필요가 없어진 것입니다.참고로 아래에서 설명할 pyenv와는 다른 도구입니다. 철자의 “v”에 주의해주세요.pyenv애플리케이션을 개발할 때는 하나의 파이썬 버전을 정하면 되지만, 라이브러리는 여러 파이썬 버전과 호환되어야 합니다. 그러다 보니 라이브러리 개발자는 여러 버전의 파이썬을 시스템에 동시에 설치할 필요가 있습니다. 데드스네이크스 PPA나 데드스네이크스 홈브루 탭 같은 것을 이용해서 설치할 수도 있지만, 보통은 pyenv를 많이 씁니다.pyenv는 동시에 여러 버전의 파이썬을 시스템에 설치해주며, 이렇게 설치된 파이썬은 시스템의 패키지 시스템(데비안·우분투의 APT나 맥OS의 홈브루 등)을 통해 설치되는 것이 아니라, pyenv가 다운로드와 빌드 및 설치를 직접 하여 별도로 관리합니다. 설치된 파이썬들은 PEP 394에 따라 일정한 형식으로 이름지어진 명령어(예: python2.7, python3.6)로 실행할 수 있게 됩니다.또한, 여러 파이썬 버전 중에 하나의 시스템 기본 파이썬 버전도 선택 가능하며, 특정 프로젝트 디렉터리 안에서만 기본 파이썬의 버전이 달라지게 할 수도 있습니다.pyenv-virtualenvpyenv가 여러 파이썬 버전을 동시에 설치해주기는 하지만, 그렇다고 자동으로 site-packages가 프로젝트마다 격리되는 것은 아닙니다. 예를 들어 pyenv로 파이썬 3.6을 설치한 뒤, 파이썬 3.6으로 두 프로젝트를 한 시스템에서 개발할 경우 두 프로젝트는 시스템 site-packages를 함께 쓰게 됩니다.따라서 pyenv를 쓰더라도 virtualenv는 따로 써야 하는데, 따로 사용할 수도 있지만 pyenv-virtualenv를 쓰면 pyenv virtualenv 명령으로 프로젝트에 쓸 파이썬 버전 지정과 가상 환경 생성을 한 번에 할 수 있게 됩니다.비슷하게 pyenv와 virtualenvwrapper를 통합해주는 pyenv-virtualenvwrapper 같은 도구도 있습니다.마치며여러 파이썬 개발 환경 관리 도구를 소개했지만, 여기 있는 모든 도구를 꼭 써야 하는 것도 아니고, 가장 최근에 나온 도구로 하루빨리 갈아타야 하는 것도 아닙니다. 글을 쓴 저 자신도 pyenv 같은 도구가 나온 지 몇 년이나 지났고 주변에서 쓰는 사람이 많음에도 쓰지 않고 있습니다. virtualenvwrapper를 대체하는 Pipenv 같은 실험적인 방식3도 생겨나고 있지만, 어느 쪽이든 동시에 여러 파이썬 프로젝트를 작업하는 사람이 아니라면 굳이 쓸 필요가 없는 도구입니다. 각자의 용도에 따라 필요한 수준의 도구를 이용하면 됩니다. 2017년 10월 현재, 아래의 지침으로 정리할 수 있겠습니다.파이썬 프로그래머가 아니지만, 파이썬 애플리케이션을 설치해서 이용합니다.시스템에서 제공하는 패키지 관리자(APT나 홈브루 등)를 통해 애플리케이션을 설치하세요.파이썬 프로그래머가 아니지만, 파이썬 애플리케이션을 유난히 많이 이용합니다.pipsi를 이용해 파이썬 애플리케이션을 설치하는 것을 권합니다.파이썬 프로그래머이고, 하나의 애플리케이션을 개발합니다.파이썬 3.3 이상을 이용할 경우 pyvenv로 개발 환경을 만들어서 개발하세요. 그 이전의 파이썬 버전을 이용할 경우 virtualenv를 활용하세요.파이썬 프로그래머이고, 여러 애플리케이션을 개발합니다.virtualenvwrapper를 활용하세요.파이썬 프로그래머이고, 여러 애플리케이션을 다양한 파이썬 버전으로 개발합니다.pyenv-virtualenvwrapper를 활용하세요.파이썬 프로그래머이고, 라이브러리를 개발합니다.pyenv와 tox를 활용하세요.파이썬으로 만든 애플리케이션을 distutils를 통해 패키징한 뒤, RPM 기반의 리눅스 배포본 용으로 python setup.py bdist_rpm 명령을 통해 *.rpm 파일을 제공하기도 했습니다. 이를 통해 애플리케이션을 설치할 경우, 각 파일들은 리눅스 FHS 표준과 해당 시스템 설정에 따라 흩어지게 됩니다. ↩예를 들어 파이썬에서 가장 많이 쓰이는 국제화 라이브러리인 바벨은 pybabel 명령어를, 구문 강조 라이브러리인 파이그먼츠는 pygmentize 명령어를, 장고는 django-admin 명령어를 제공합니다. ↩저는 2017년 4월에 한 번 써보았으나, 아직은 실무에서 쓰기에는 이르다는 결론을 내렸습니다. 이에 관한 그때의 제 감상은 별도의 글로 다루었습니다. ↩#스포카 #파이썬 #개발팀 #개발자 #인사이트 #후기 #일지
조회수 2874

리디북스 웹뷰어의 이어보기를 개발하며

최근 리디북스에서는 판타지 연재물을 웹에서 바로 볼 수 있는 기능을 새롭게 선보였습니다.기존에는 별도의 앱을 설치하고 다운로드하는 과정을 거쳐야 했기에 연재물을 보는 사용성이 좋지 않았습니다만, 브라우저에서 바로 볼 수 있는 “웹뷰어” 기능을 제공함으로써 사용성을 높일 수 있었습니다.그리고 여기에 사용성을 더하기 위해 추가된 것이 이어보기 기능입니다. 짧아도 100화 이상, 길게는 1000화가 넘는 연재물에서 다음 화로의 매끄러운 연결은 매우 중요합니다. 혹은 잠시 읽기를 중단했다가 다시 돌아왔을 때, 어디까지 보고 있었는지를 빠르게 알려준다면 호흡을 이어서 작품에 더욱 몰입할 수 있을 것입니다.이어보기가 구현된 모습리디북스에 로그인되어 있다면, 이곳에서 확인하실 수 있습니다.이번 글은 이어보기 기능에 대한 개발 후기입니다. 요구 사항에 따라 여러 저장소 솔루션을 비교해 보았으며 최종적으로 Couchbase를 선택한 이유와 간단한 벤치마크 결과, 그리고 겪었던 문제를 공유합니다.요구 사항기획된 내용을 요약하니 아래와 같습니다.연재물의 가장 마지막에 읽은 화를 알 수 있다.보았던 모든 연재물에서 가장 마지막에 읽은 연재물을 알 수 있다.사용자가 본 모든 연재물 목록을 확인할 수 있다.이를 개발자 용어로 다시 풀어보면 아래와 같습니다.연재물을 읽을 때마다 연재물 ID와 화(episode) 정보를 기록한다.보았던 연재물을 최신순으로 정렬하여 가져온다.선택된 연재물의 마지막으로 읽은 화를 가져온다.목록에서 특정 연재물을 삭제한다.이어보기는 가장 마지막에 읽은 연재물을 기억하기 위해 작품을 열 때마다 해당 정보를 기록해야 합니다. 그런데 수십 화를 연달아서 보는 연재물의 특성상 내가 어디까지 읽었는지를 조회하는 것(read)보다 내가 읽은 연재물을 기록하는 것(write)이 더 많을 것으로 판단했습니다. 즉, 읽기보다 쓰기가 더 많을 것으로 예상했습니다.NoSQL을 쓰자대부분의 연산이 쓰기(write)와 관련된 이상, 어떤 저장공간을 사용할 것인지가 주된 관심사였습니다.특히 RDBMS와 NoSQL 사이에서 어떤 것을 사용할지 많은 고민과 테스트를 했고, 결국 아래와 같은 이유로 NoSQL을 사용하는 것이 적합하다고 판단했습니다.현재 사용 중인 MariaDB를 그대로 사용한다면 마스터에 부담을 줄 수 있다.별도로 MariaDB를 구성하더라도 운영 및 쓰기 분산하기가 여전히 어렵다.반면 NoSQL은 RDBMS 대비 확장(Scale out)이 간편하므로 운영에 대한 부담이 적다.단순 Key-Value 보관 용도면 충분하다.이어보기 데이터는 독립적인 성격을 가지고 있어서 다른 사용자 데이터와 JOIN을 할 필요가 없다.이어보기 데이터는 크리티컬한 트랜잭션이 필요하지 않다.MongoDB vs. Couchbase데이터를 영속적으로 유지해야 한다는 요구 사항을 충족하기 위해, Redis 등의 메모리만 사용하는 NoSQL은 제외했습니다. 물론 디스크에 기록할 수 있지만, 성능이 급감하기 때문에 실용적이지 못 합니다. 또한, 메모리 사이즈에 기반을 두기 때문에 Scale up 비용이 크고, 서비스 확장시 Scale out 빈도가 높습니다.그래서 MongoDB와 Couchbase를 비교 대상으로 했습니다. 둘 다 도큐먼트 기반의 NoSQL이고 확장이 용이합니다. 과거에는 MongoDB가 Write lock 사용에 있어서 문제점이 있었지만, 최근 버전에서는 문제가 되지 않습니다.[1] 둘 다 기업용 서비스 및 충분한 부가 기능들을 제공하므로 선택하기 어려웠지만, 최종적으로 아래와 같은 이유로 Couchbase(CE)를 선택했습니다.1. 이미 사내에서 다른 서비스에 사용되고 있습니다.가장 중요한 요인이었습니다. 더 좋은 솔루션이 있더라도 어디까지나 서버 스택을 늘리는 것 이상의 효용이 있는지를 따져보아야 합니다. 이미 사용하고 있는 솔루션이 있다면, 검증이 되었을 뿐만 아니라 개발 및 운영 경험도 활용할 수 있습니다.2. 이어보기는 복잡한 쿼리(Query)가 필요 없습니다.이어보기에서 사용할 쿼리는 간단하기 때문에 Couchbase의 뷰(View)만으로 충분했습니다.Couchbase, 실제 성능은 어떨까?테스트를 하기 전 우리가 어떤 식으로 사용할 것인지 정리해야 합니다. 애플리케이션 액세스 패턴이나 동시성 문제, 데이터 구조화 등을 파악하고 그에 맞는 테스트를 진행해야 합니다. 이번 이어보기는 쓰기 연산이 보다 많기 때문에 이로 인한 뷰의 인덱싱(Indexing)에 초점을 맞추고 테스트를 진행했습니다.성능을 위협하는 요소들View IndexingCouchbase는 MapReduce를 이용하여 뷰를 제공합니다. MapReduce는 일반적으로 리소스를 많이 소모하는 동작입니다. 그래서 Couchbase는 버킷의 새로 갱신된 데이터만 인덱싱하는 Incremental MapReduce라는 기법을 적용해서 리소스 소모를 줄였다고 합니다.[2] 하지만 해당 작업으로 인한 부하는 여전히 발생합니다.Auto CompactionCouchbase는 데이터와 인덱스를 디스크에 데이터를 저장할 때 파일에 추가하기(Append) 모드로만 쓰기를 수행합니다.[3] 그리고 오래되고 불필요한 데이터들은 추후 한꺼번에 정리하는데, 이는 디스크 쓰기 성능을 최대화하기 위함입니다.그런데 이렇게 추가만 하게 되면 오래된 정보들은 파일의 앞에 쌓이게 됩니다. 그리고 사용하지 않게 된 데이터도 남아있습니다. 이를 주기적으로 정리해서 최적화하는 작업을 Auto Compaction이라고 합니다. 뷰의 인덱스는 디스크에 존재하기 때문에 디스크 작업이 있으면 인덱싱에 영향을 미치게 됩니다.성능 테스트Couchbase는 기본적으로 5,000ms마다 Index를 업데이트합니다.[2] 그리고 데이터를 비동기적으로 응답합니다. 비동기는 응답속도를 빠르게 하지만, 데이터 불일치가 발생할 수 있습니다. 데이터 불일치가 신경 쓰이고 이 시간이 길다고 생각되면, stale 옵션을 지정해서 뷰의 인덱스를 업데이트할 수 있습니다.이어보기는 뷰가 간단하기 때문에 응답시간에 큰 문제가 없을 것으로 예상하고 stale 옵션을 꺼두었습니다. 이 옵션은 뷰를 조회했을 때 버킷의 변경사항에 따라 뷰를 인덱싱하고 데이터를 응답합니다. 하지만 예상한 것과 같이 실제로도 응답시간이 짧은지 확인할 필요가 있습니다. 그래서 다음과 같이 테스트를 진행했습니다.테스트 환경은 아래와 같이 2-tier로 준비하고 요청을 늘려가면서 RPS를 측정했습니다.서버 구성OS: Ubuntu 14.04Application: Couchbase Server (CE) 3.1.3클라이언트 구성클라이언트 1개에서 50개의 세션으로 요청10만 사용자 가정책은 1만개의 책중 랜덤으로 선택됨요청의 70%는 책 읽기(Bucket Write)요청의 30%는 연재물의 마지막에 읽은 책 가져오기(View Read)그래프 분석성능 테스트 주요 지표RPS : Response Per SecondSP : Saturation PointBuckle zone : 시스템 과부하로 인해 내부 자원이 서로 경쟁상태나 적체 상태가 심해지기 때문에 최대 처리량보다 더 떨어지는 경우가 발생함성능테스트 결과그래프를 보면 요청이 늘어남에 따라 RPS가 선형으로 증가하지만, SP인 8,000 RPS에 도달하고 나서 Buckle zone에서 7,000 RPS로 수렴하고 있습니다. 물론 1개의 클라이언트에서 세션을 생성해서 테스트를 진행했기 때문에 서버의 성능 부족이 아닌 클라이언트의 병목 현상이 원인일 수 있습니다. 또한 JMeter나 다른 부하 테스트 툴을 사용하지 않고 간략하게 만든 테스트 툴을 사용하였기 때문에 수치가 부정확할 수 있습니다. 그러나 어디에서 병목이 있었든 현재 이 이상의 성능이 필요하지 않기 때문에 테스트 결과에 만족할 수 있었습니다.이어보기 배포 후모바일 브라우저 캐시 문제이어보기 기능을 배포하자마자 당일 저녁 이슈 하나를 접수했습니다. 아이패드와 PC를 번갈아 이용할 경우 이어보기 데이터가 맞지 않다는 것이었습니다.데이터를 쌓을 때 모든 이력을 기록하지는 않았지만, 다행히도 Couchbase에 이용기기와 시간은 기록하였기 때문에 이를 바탕으로 디버깅을 할 수 있었습니다. (서비스 초기라 할지라도 최대한 많은 이력을 남기는 것이 중요함을 다시 느꼈습니다)원인은 아이패드의 멀티태스킹으로 인한 캐시 소멸이었습니다. 아이패드 브라우저의 캐시가 소멸되면서 마지막으로 열어두었던 페이지가 강제적으로 리로딩되었고, 이때 의도치 않게 마지막 위치 정보가 덮어씌워진 것입니다.이 문제는 기술적으로 해결이 쉽지 않아 결국 기획을 수정하게 되었습니다. 사용자가 해당 책을 읽었다고 판단하는 기준이 “페이지를 열어본 즉시”였다면, 이를 “페이지를 열고 수 초 이상을 유지”하는 것으로 기준을 변경하였습니다. 물론 근본적인 해결책은 아니었지만, 실제 사용에는 지장이 없는 합리적인 해결책이라고 생각합니다.Key 구조의 변경 및 동시성 문제Couchbase는 높은 성능을 위해 메타데이터(Key + @)를 모두 메모리에 적재하는 특징이 있어서, Document 하나가 평균 350Byte를 차지하고 있었습니다. 따라서 현재 상태로 1000만개의 데이터를 저장할 경우 최소 3.5G의 메모리를, 2개의 사본(Replica)를 유지할 경우 약 10.5G의 메모리를 사용하게 될 것으로 예상되었고 이는 큰 부담으로 다가왔습니다.처음에는 단순히 “사용자ID_연재물ID” 형태의 Key를 사용하였지만, 보다 빠르게 증가할 것으로 예상되는 것은 사용자보다 연재물 이었으므로 아래와 같이 Key값을 변경하여 메모리 사용량을 크게 줄였습니다.// U_id : S_id 조합을 사용하면 Key가 엄청 많아진다. // 그래서 사용자당 Key를 100개로 제한하도록 한다. Count = 100 Key = '사용자ID' + ('연재물ID' % Count) 그런데 이렇게 Key 구조를 변경하였더니, 간단한 업데이트 동작임에도 불구하고 정상적으로 수행되지 않는 경우가 빈번하게 발생하였습니다. 이유는 낙관적 동시성(Optimistic concurrency) 모델의 특징 때문이었는데, Couchbase는 명시적인 잠금 이외에도 “Check and Set(CAS)”이라는 기능을 제공하고 있었습니다.공식 문서의 예제를 참고하여 아래와 같이 로직을 수정한 뒤로는 다행히도 동시성 문제가 아직까지 발생하지 않고 있습니다.boolean updateUsingCas(key, value) {  for (tryCount = 0; tryCount < MAX>    orgValue, cas = getValueAndCas(key)           // Update the original value.     // newValue = ... if setValueWithCas(key, newValue, cas)      return SUCCESS sleep(0.1) // 부하를 줄이기 위해  }  return FAIL } 맺으며동작하는 서비스에 새로운 기능을 추가한다는 것은 어려운 일입니다. 특히 새로운 데이터 스토리지를 필요로 하는 일이라면 더더욱 어렵다고 생각합니다. 그리고 그럴 때일수록 설계에 많은 시간을 들여야 한다는 것을 느꼈습니다. 설계 초기에는 RDBMS의 샤딩까지 고려하였지만, 요구 사항을 구체화할수록 단순 Key-Value로도 같은 문제를 해결할 수 있음을 깨달았기 때문입니다.또한, 서비스 개발에 있어서 어려운 문제를 마주했을 때 기술적으로만 접근할 것이 아니라 고객이 정말 원하는 것이 무엇인지를 고민하여 기획적으로 해결하는 능력도 중요하다는 것을 실감하였습니다.마지막으로 Couchbase는 현재로서도 꽤 좋고 앞으로도 많은 발전이 기대되는 NoSQL입니다. 도입을 고민하시던 분들께 조금이라도 도움이 되었기를 바랍니다.참고자료[1] MongoDB - Concurrency[2] Couchbase - Views Operations[3] Couchbase - File write#리디북스 #개발 #개발자 #서버개발 #서비스개발 #고객중심 #기능개발 #Couchbase #인사이트 #개발후기
조회수 1922

Tips for building fast portrait segmentation network with TensorFlow Lite

PrefaceDeep learning has led to a series of breakthroughs in many areas. However, successful deep learning models often require significant amounts of computational resources, memory and power. Deploying efficient deep learning models on mobile devices became the main technological challenge for many mobile tech companies.Hyperconnect developed a mobile app named Azar which has a large fan base all over the world. Recently, Machine Learning Team has been focusing on developing mobile deep learning technologies which can boost user experience in Azar app. Below, you can see a demo video of our image segmentation technology (HyperCut) running on Samsung Galaxy J7. Our benchmark target is a real-time (>= 30 fps) inference on Galaxy J7 (Exynos 7580 CPU, 1.5 GHz) using only a single core.Figure 1. Our network runs fast on mobile devices, achieving 6 ms of single inference on Pixel 1 and 28 ms on Samsung Galaxy J7. Full length video. There are several approaches to achieve fast inference speed on mobile device. 8-bit quantization is one of the popular approaches that meet our speed-accuracy requirement. We use TensorFlow Lite as our main framework for mobile inference. TensorFlow Lite supports SIMD optimized operations for 8-bit quantized weights and activations. However, TensorFlow Lite is still in pre-alpha (developer preview) stage and lacks many features. In order to achive our goal, we had to do the following:Understand details of TensorFlow and Tensorflow Lite implementation.Design our own neural network that can fully utilize optimized kernels of TensorFlow Lite. (Refer to 1, 2 and 3)Modify TOCO: TensorFlow Lite Optimizing Converter to correctly convert unsupported layers. (Refer to 4)Accelerate several quantized-layers with SIMD optimized code. (Refer to 5 and 6)We are willing to share our trials and errors in this post and we hope that readers will enjoy mobile deep learning world :)1) Why is depthwise separable layer fast in Tensorflow Lite ?Implementing low-level programs requires a bit different ideas and approaches than usual. We should be aware that especially on mobile devices using cache memory is important for fast inference.Figure 2. Memory access requires much more energy (640 pJ) than addition or multiplication.Accessing cache memory (8 pJ) is much cheaper than using the main memory (table courtesy of Ardavan Pedram) In order to get insight into how intrinsics instructions are implemented in Tensorflow Lite, we had to analyze some implementations including depthwise separable convolution with 3x3 kernelsBelow we describe some of the main optimization techniques that are used for building lightweight and faster programs.Loop UnrollingCan you spot the difference between the following two code fragments?for (int i = 0; i < 32; i++) { x[i] = 1; if (i%4 == 3) x[i] = 3; } for (int i = 0; i < 8; i++) { x[4*i ] = 1; x[4*i+1] = 1; x[4*i+2] = 1; x[4*i+3] = 3; } The former way is what we usually write, and the latter is loop-unrolled version of former one. Even though unrolling loops are discouraged from the perspective of software design and development due to severe redundancy, with low-level architecture this kind of unrolling has non-negligible benefits. In the example described above, the unrolled version avoids examining 24 conditional statements in for loop, along with neglecting 32 conditional statements of if.Furthermore, with careful implementation, these advantages can be magnified with the aid of SIMD architecture. Nowadays some compilers have options which automatically unroll some repetitive statements, yet they are unable to deal with complex loops.Separate implementation for each caseConvolution layer can take several parameters. For example, in depthwise separable layer, we can have many combinations with different parameters (depth_multiplier x stride x rate x kernel_size). Rather than writing single program available to deal with every case, in low-level architectures, writing number of case-specific implementations is preferred. The main rationale is that we need to fully utilize the special properties for each case. For convolution operation, naive implementation with several for loops can deal with arbitrary kernel size and strides, however this kind of implementation might be slow. Instead, one can concentrate on small set of actually used cases (e.g. 1x1 convolution with stride 1, 3x3 convolution with stride 2 and others) and fully consider the structure of every subproblem.For example, in TensorFlow Lite there is a kernel-optimized implementation of depthwise convolution, targeted at 3x3 kernel size:template <int kFixedOutputY, int kFixedOutputX, int kFixedStrideWidth, int kFixedStrideHeight> struct ConvKernel3x3FilterDepth8 {}; Tensorflow Lite further specifies following 16 cases with different strides, width and height of outputs for its internal implementation:template <> struct ConvKernel3x3FilterDepth8<8, 8, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 2, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 1, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 2, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 1, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 4, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 1, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 4, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 1, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 4, 2, 2> { ... } Smart Memory Access PatternSince low-level programs are executed many times in repetitive fashion, minimizing duplicated memory access for both input and output is necessary. If we use SIMD architecture, we can load nearby elements together at once (Data Parallelism) and in order to reduce duplicated read memory access, we can traverse the input array by means of a snake-path.Figure 3. Memory access pattern for 8x8 output and unit stride, implemented in Tensorflow Lite's depthwise 3x3 convolution. The next example which is targeted to be used in much smaller 4x1 output block also demonstrates how to reuse preloaded variables efficiently. Note that the stored location does not change for variables which are loaded in previous stage (in the following figure, bold variables are reused):Figure 4. Memory access pattern for 4x1 output and stride 2, implemented in Tensorflow Lite's depthwise 3x3 convolution. 2) Be aware of using atrous depthwise convolutionAtrous (dilated) convolution is known to be useful to achieve better result for image segmentation[1]. We also decided to use atrous depthwise convolution in our network. One day, we tried to set stride for atrous depthwise convolution to make it accelerate computation, however we failed, because the layer usage in TensorFlow (≤ 1.8) is constrained.In Tensorflow documentation of tf.nn.depthwise_conv2d (slim.depthwise_conv2d also wraps this function), you can find this explanation of rate parameter.rate: 1-D of size 2. The dilation rate in which we sample input values across the height and width dimensions in atrous convolution. If it is greater than 1, then all values of strides must be 1.Even though TensorFlow doesn’t support strided atrous function, it doesn’t raise any error if you set rate > 1 and stride > 1. <!-- The output of layer doesn't seem to be wrong. -->>>> import tensorflow as tf >>> tf.enable_eager_execution() >>> input_tensor = tf.constant(list(range(64)), shape=[1, 8, 8, 1], dtype=tf.float32) >>> filter_tensor = tf.constant(list(range(1, 10)), shape=[3, 3, 1, 1], dtype=tf.float32) >>> print(tf.nn.depthwise_conv2d(input_tensor, filter_tensor, strides=[1, 2, 2, 1], padding="VALID", rate=[2, 2])) tf.Tensor( [[[[ 302.] [ 330.] [ 548.] [ 587.]] [[ 526.] [ 554.] [ 860.] [ 899.]] [[1284.] [1317.] [1920.] [1965.]] [[1548.] [1581.] [2280.] [2325.]]]], shape=(1, 4, 4, 1), dtype=float32) >>> 0 * 5 + 2 * 6 + 16 * 8 + 9 * 18 # The value in (0, 0) is correct 302 >>> 0 * 4 + 2 * 5 + 4 * 6 + 16 * 7 + 18 * 8 + 20 * 9 # But, the value in (0, 1) is wrong! 470 Let’s find the reason why this difference happened. If we just put tf.space_to_batch and tf.batch_to_space before and after convolution layer, then we can use convolution operation for atrous convolution (profit!). On the other hand, it isn’t straightforward how to handle stride and dilation together. In TensorFlow, we need to be aware of this problem in depthwise convolution.3) Architecture design principles for efficient segmentation networkUsually segmentation takes more time than classification since it has to upsample high spatial resolution map. Therefore, it is important to reduce inference time as much as possible to make the application run in real-time.It is important to focus on spatial resolution when designing real-time application. One of the easiest ways is to reduce the size of input images without losing accuracy. Assuming that the network is fully convolutional, you can accelerate the model about four times faster if the size of input is halved. In literature[2], it is known that small size of input images doesn’t hurt accuracy a lot.Another simple strategy to adopt is early downsampling when stacking convolution layers. Even with the same number of convolution layers, you can reduce the time with strided convolution or pooling within early layers.There is also a tip for selecting the size of input image when you use Tensorflow Lite quantized model. The optimized implementations of convolution run best when the width and height of image is multiple of 8. Tensorflow Lite first loads multiples of 8, then multiples of 4, 2 and 1 respectively. Therefore, it is the best to keep the size of every input of layer as a multiple of 8.Substituting multiple operations into single operation can improve speed a bit. For example, convolution followed by max pooling can be usually replaced by strided convolution. Transpose convolution can also be replaced by resizing followed by convolution. In general, these operations are substitutable because they perform the same role in the network. There are no big empirical differences among these operations. <!-- substitutable -->Tips described above help to accelerate inference speed but they can also hurt accuracy. Therefore, we adopted some state-of-the-art blocks rather than using naive convolution blocks.Figure 5.  Atrous spatial pyramid pooling (figure courtesy of Liang-Chieh Chen) Atrous spatial pyramid pooling[1] is a block which mimics the pyramid pooling operation with atrous convolution. DeepLab uses this block in the last layer.We also substitute most of the convolution layers with efficient depthwise separable convolution layers. They are basic building blocks for MobileNetV1[3] and MobileNetV2[4] which are well optimized in Tensorflow Lite.4) Folding batchnorm into atrous depthwise convolutionWhen quantizing convolution operation followed by batchnorm, batchnorm layer must be folded into the convolution layers to reduce computation cost. After folding, the batchnorm is reduced to folded weights and folded biases and the batchnorm-folded convolution will be computed in one convolution layer in TensorFlow Lite[5]. Batchnorm gets automatically folded using tf.contrib.quantize if the batchnorm layer comes right after the convolution layer. However, folding batchnorm into atrous depthwise convolution is not easy.In TensorFlow’s slim.separable_convolution2d, atrous depthwise convolution is implemented by adding SpaceToBatchND and BatchToSpaceND operations to normal depthwise convolution as mentioned previously. If you add a batchnorm to this operation by including argument normalizer_fn=slim.batch_norm, batchnorm does not get attached directly to the convolution layer. Instead, the graph will look like the diagram below: SpaceToBatchND → DepthwiseConv2dNative → BatchToSpaceND → BatchNorm The first thing we tried was to modify TensorFlow quantization to fold batchnorm bypassing BatchToSpaceND without changing the order of operations. With this approach, the folded bias term remained after BatchToSpaceND, away from the convolution layer. Then, it became separate BroadcastAdd operation in TensorFlow Lite model rather than fused into convolution. Surprisingly, it turned out that BroadcastAdd was much slower than the corresponding convolution operation in our experiment:Timing log from the experiment on Galaxy S8 ... [DepthwiseConv] elapsed time: 34us [BroadcastAdd] elapsed time: 107us ... To remove BroadcastAdd layer, we decided to change the network itself instead of fixing TensorFlow quantization. Within slim.separable_convolution2d layer, we swapped positions of BatchNorm and BatchToSpaceND. SpaceToBatchND → DepthwiseConv2dNative → BatchNorm → BatchToSpaceND Even though batchnorm relocation could lead to different outputs values compared to the original, we did not notice any degradation in segmentation quality.5) SIMD-optimized implementation for quantized resize bilinear layerAt the time of accelerating TensorFlow Lite framework, conv2d_transpose layer was not supported. However, we could use ResizeBilinear layer for upsampling as well. The only problem of this layer is that there is no quantized implementation, therefore we implemented our own SIMD quantized version of 2x2 upsampling ResizeBilinear layer.Figure 6. 2x2 bilinear upsampling without corner alignnment. To upsample image, original image pixels (red circles) are interlayed with new interpolated image pixels (grey circles). In order to simplify implementation we do not compute pixel values for the bottommost and rightmost pixels, denoted as green circles.for (int b = 0; b < batches; b++) { for (int y0 = 0, y = 0; y <= output_height - 2; y += 2, y0++) { for (int x0 = 0, x = 0; x <= output_width - 2; x += 2, x0++) { int32 x1 = std::min(x0 + 1, input_width - 1); int32 y1 = std::min(y0 + 1, input_height - 1); ResizeBilinearKernel2x2(x0, x1, y0, y1, x, y, depth, b, input_data, input_dims, output_data, output_dims); } } } Every new pixel value is computed for each batch separately. Our core function ResizeBilinearKernel2x2 computes 4 pixel values across multiple channels at once.Figure 7. Example of 2x2 bilinear upsampling of top left corner of image. (a) Original pixel values are simply reused and (b) – (d) used to interpolate new pixel values. Red circles represent original pixel values. Blue circles are new interpolated pixel values computed from pixel values denoted as circles with black circumference. NEON (Advanced SIMD) intrinsics enable us to process multiple data at once with a single instruction, in other words processing multiple data at once. Since we deal with uint8 input values we can store our data in one of the following formats uint8x16_t, uint8x8_t and uint8_t, that can hold 16, 8 and 1 uint8 values respectively. This representation allows to interpolate pixel values across multiple channels at once. Network architecture is highly rewarded when channels of feature maps are multiples of 16 or 8:// Handle 16 input channels at once int step = 16; for (int ic16 = ic; ic16 <= depth - step; ic16 += step) { ... ic += step; } // Handle 8 input channels at a once step = 8; for (int ic8 = ic; ic8 <= depth - step; ic8 += step) { ... ic += step; } // Handle one input channel at once for (int ic1 = ic; ic1 < depth; ic1++) { ... } SIMD implementation of quantized bilinear upsampling is straightforward. Top left pixel value is reused (Fig. 7a). Bottom left (Fig. 7b) and top right (Fig. 7c) pixel values are mean of two adjacent original pixel values. Finally, botom right pixel (Fig. 7d) is mean of 4 diagonally adjacent original pixel values.The only issue that we have to take care of is 8-bit integer overflow. Without a solid knowledge of NEON intrinsics we could go down the rabbit hole of taking care of overflowing by ourself. Fortunately, the range of NEON intrinsics is broad and we can utilize those intrinsics that fit our needs. The snippet of code below (using vrhaddq_u8) shows an interpolation (Fig. 7d) of 16 pixel values at once for bottom right pixel value:// Bottom right output_ptr += output_x_offset; uint8x16_t left_interpolation = vrhaddq_u8(x0y0, x0y1); uint8x16_t right_interpolation = vrhaddq_u8(x1y0, x1y1); uint8x16_t bottom_right_interpolation = vrhaddq_u8(left_interpolation, right_interpolation); vst1q_u8(output_ptr, bottom_right_interpolation); 6) Pitfalls in softmax layer and demo codeThe first impression of inference in TensorFlow Lite was very slow. It took 85 ms in Galaxy J7 at that time. We tested the first prototype of TensorFlow Lite demo app by just changing the output size from 1,001 to 51,200 (= 160x160x2)After profiling, we found out that there were two unbelievable bottlenecks in implementation. Out of 85 ms of inference time, tensors[idx].copyTo(outputs.get(idx)); line in Tensor.java took up to 11 ms (13 %) and softmax layer 23 ms (27 %). If we would be able to accelerate those operations, we could reduce almost 40 % of the total inference time!First, we looked at the demo code and identified tensors[idx].copyTo(outputs.get(idx)); as a source of problem. It seemed that the slowdown was caused by copyTo operation, but to our surprise it came from int[] dstShape = NativeInterpreterWrapper.shapeOf(dst); because it checks every element (in our case, 51,200) of array to fill the shape. After fixing the output size, we gained 13 % speedup in inference time.<T> T copyTo(T dst) { ... // This is just example, of course, hardcoding output shape here is a bad practice // In our actual app, we build our own JNI interface with just using c++ code // int[] dstShape = NativeInterpreterWrapper.shapeOf(dst); int[] dstShape = {1, width*height*channel}; ... } The softmax layer was our next problem. TensorFlow Lite’s optimized softmax implementation assumes that depth (= channel) is bigger than outer_size (= height x width). In classification task, the usual output looks like [1, 1(height), 1(width), 1001(depth)], but in our segmentation task, depth is 2 and outer_size is multiple of height and width (outer_size » depth). Implementation of softmax layer in Tensorflow Lite is optimized for classification task and therefore loops over depth instead of outer_size. This leads to unacceptably slow inference time of softmax layer when used in segmentation network.We can solve this problem in many different ways. First, we can just use sigmoid layer instead of softmax in 2-class portrait segmentation. TensorFlow Lite has very well optimized sigmoid layer.Secondly, we could write SIMD optimized code and loop over depth instead of outer_size. You can see similar implementation at Tencent’s ncnn softmax layer, however, this approach has still its shortcomings. Unlike ncnn, TensorFlow Lite uses NHWC as a default tensor format:Figure 8. NHWC vs NCHW In other words, for NHWC, near elements of tensor hold channel-wise information and not spatial-wise. It is not simple to write optimized code for any channel size, unless you include transpose operation before and after softmax layer. In our case, we tried to implement softmax layer assumming 2-channel output.Thirdly, we can implement softmax layer using pre-calculated lookup table. Because we use 8-bit quantization and 2-class output (foreground and background) there are only 65,536 (= 256x256) different combinations of quantized input values that can be stored in lookup table:for (int fg = 0; fg < 256; fg++) { for (int bg = 0; bg < 256; bg++) { // Dequantize float fg_real = input->params.scale * (fg - input->params.zero_point); float bg_real = input->params.scale * (bg - input->params.zero_point); // Pre-calculating Softmax Values ... // Quantize precalculated_softmax[x][y] = static_cast<uint8_t>(clamped); } } ConclusionIn this post, we described the main challenges we had to solve in order to run portrait segmentation network on mobile devices. Our main focus was to keep high segmentation accuracy while being able to support even old devices, such as Samsung Galaxy J7. We wish our tips and tricks can give a better understanding of how to overcome common challenges when designing neural networks and inference engines for mobile devices.At the top of this post you can see portrait segmentation effect that is now available in Azar app. If you have any questions or want to discuss anything related to segmentation task, contact us at [email protected]. Enjoy Azar and mobile deep learning world!References[1] L. Chen, G. Papandreou, F. Schroff, H. Adam. Rethinking Atrous Convolution for Semantic Image Segmentation. June 17, 2017, https://arxiv.org/abs/1706.05587[2] C. Szegedy, V. Vanhoucke, S. Ioffe, J. Shlens, Z. Wojna. Rethinking the Inception Architecture for Computer Vision. December 11, 2015, https://arxiv.org/abs/1512.00567[3] A. Howard, M. Zhu, B. Chen, D. Kalenichenko, W. Wang, T. Weyand, M. Andreetto, H. Adam. MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications, April 17, 2017, https://arxiv.org/abs/1704.04861[4] M. Sandler, A. Howard, M. Zhu, A. Zhmoginov, L. Chen. MobileNetV2: Inverted Residuals and Linear Bottlenecks. January 18, 2018, https://arxiv.org/abs/1801.04381[5] B. Jacob, S. Kligys, B. Chen, M. Zhu, M. Tang, A. Howard, H. Adam, D. Kalenichenko. Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference. December 15, 2017, https://arxiv.org/abs/1712.05877
조회수 1409

EOS Smart Contract 배포

Smart Contract 배포를 위한 준비 과정은 이전글 확인 부탁드립니다.저번시간과 연계하여 이번 시간엔 스마트 컨트랙트를 배포해 보도록 하겠습니다. 지갑 key와 계정 이름등은 본 포스팅에서 그대로 사용하시면 됩니다.배포할 컨트랙트는 eosio.token 으로 eos 개발환경 세팅 시 존재하는 코드를 컴파일하여 실제 사용하는 계정에 setting 하겠습니다. 먼저 컴파일을 위해 ../eos/contracts/eosio.token 으로 이동 하겠습니다.eos/contracts/eosio.token이동 하면 위와 같은 파일들을 확인 하실 수 있습니다.hpp : cpp 파일에서 사용하는 변수, 상수, 함수를 담는 헤더파일cpp : contract 함수를 구현하는 소스 파일eosiocpp 를 통해 소스코드를 컴파일 해보겠습니다. eosiocpp 는 WASM 및 ABI 컴파일러 로써 블록체인에 업로드 되는 .wasm, .wast, .abi 파일을 생성합니다. 또한 기본 스켈레톤 파일을 제공합니다eosiocppwasm 컴파일wasm 파일은 아래 명령어를 사용하여 컴파일 만들 수 있습니다.$ eosiocpp -o eosio.token.wast eosio.token.cppeosiocpp 명령어를 사용하여 컴파일 하게 되면 .wast 파일과 .wasm 파일을 생성하게 됩니다. 각 확장자는 다음을 의미합니다.wast : 텍스트 파일로써 읽을 수 있는 webAssembly 파일wasm : 컴퓨터가 실제로 이해할 수 있는 webAssembly 파일abi 파일 생성$ eosiocpp -g eosio.token.abi eosio.token.cppabi 파일은 JSON과 Binary 간에 사용자 작업을 변환하는 방법에 대해 설명해주는 파일입니다. 실제로 이 JSON 파일을 통해 블록체인 위에서 개발자와 사용자간 상호작용 하는데 도와주게 됩니다.위 2과정을 통해 abi 파일 과 wast 파일을 생성하게 됩니다.compile 결과Contract 세팅하기아래 명령어를 입력하여 contract 를 set 해줍니다.$ cleos set contract hexlanthenry ../eos/build/contracts/eosio.token account : contract 를 배포할 계정이름contract-dir : 계정에 set 할 contract 가 저장된 directoryset contract 수행 결과만약 해당 계정이 RAM 을 보유하고 있지 않다면 다음과 같은 에러가 나타날 것입니다. 이를 해결하기 위해 RAM 을 구매합니다.RAM을 보유없이 contract$ cleos system buyram hexlanthenry hexlanthenry "100.0000 EOS"payer : EOS 를 지불할 계정receiver : RAM 을 사용할 계정amount : 지불할 EOS의 양 ( eos 1.1 기준 소수점4개 자리와 symbol을 무조건 넣어주어야 정상 동작 합니다)contract 확인계정에 contract가 잘 배포 되었는지 확인해 보겠습니다.$ cleos get code hexlanthenry배포한 contract 가 있을때의 code hash배포한 contract 가 없을때의 code hash또한 abi 를 통해서도 확인할 수 있습니다.$ cleos get abi hexlanthenryget abi위 과정을 통해 해당 계정에 실제로 contract 가 잘 배포 되었는지 확인 할 수 있습니다.다음 시간에는 배포된 contract 를 통하여 토큰을 발행 해보고 token에 대한 balance 체크 및 transfer 하는 과정을 진행해 보도록 하겠습니다.+또한 abi를 분석하여 struct 와 action 을 어떻게 확인 하는지에 대한 자세한 방법은 다른 포스팅에서 다루도록 하겠습니다.감사합니다.#헥슬란트 #HEXLANT #블록체인 #개발자 #개발팀 #기술기업 #기술중심
조회수 2780

Good Developer 2 | 커뮤니케이션 잘하는 개발자가 되는 방법

프로그래머와 개발자는 다르다.커뮤니케이션에 대한 이야기를 하기 전에 프로그래머와 개발자의 차이에 대해 명확히 하려 한다. 먼저 프로그래머는 컴퓨터를 이용해서 프로그램을 만들거나 수정하는 일을 하는 사람이다. 프리랜서로 일하면서 외주 프로젝트를 맡거나 학교 과제를 하면서 프로그래밍을 하는 사람들 모두 프로그래머라 할 수 있겠다.반면, 개발자는 회사나 조직에 소속이 돼서 다른 사람들과 함께 일하면서 개발을 사는 사람이다. 즉 어딘가에 소속이 돼서 규칙이나 규율 혹은 그 조직의 원칙을 가지고 일을 한다면 개발자로 볼 수 있는 것이다. 정리해 보자면 모든 개발자는 프로그래머지만 모든 프로그래머는 개발자는 아니다. 프로그래머와 개발자를 굳이 나누어서 말하는 이유는 개발자에게는 커뮤니케이션 능력이 절대적으로 필요하기 때문이다. 이와 관련해 아주 적절한 비유를 소개하려고 한다. 이 비유는 칼럼니스트 임백준 님의 '개발자의 생명은 커뮤니케이션 능력'에서 가져왔다.(이 글도 아주 좋으니 읽어보는 것을 추천)비유를 해보자면 이렇다. 프로그래머나 해커는 강호를 떠돌면서 혼자서 행동하는 무사라고 한다면 개발자는 군대에 소속되어 있는 정규군이다. 칼럼에서는 정확이 이렇게 표현한다.외톨이 무사에게 생명은 칼 솜씨고 정규군의 생명은 규칙과 규율이다.칼 솜씨는 코딩 실력이 되겠고, 규칙과 규율은 다른 사람과의 커뮤니케이션 능력이라 볼 수 있겠다. 이것이 개발자에게 있어 코딩 실력이 중요하지 않다는 것은 아니다. 코딩 실력은 기본이요. 커뮤니케이션 능력도 반드시 필수적이라는 뜻이다. 군대에 속해서 전투를 치르기 위해서는 기본적인 전투능력이 필요하다. 즉, 개발자는 자기가 맡은 프로그래밍 업무를 성공적으로 수행할 수 있는 능력을 가져야 하고 이것 은 기본이다!좋은 개발자가 되기 위한 첫 번째 방법, '소통'많은 시니어 개발자들이나 개발 관련된 직종에서 오래 근무한 사람들이 가장 많이 하는 말 중 하나가 바로 커뮤니케이션에 대한 이야기다.  개발자를 뽑을 때 중요한 것이 커뮤니케이션 능력이라고 한다. 커뮤니케이션이 원활하지 않아 개발 업무에 차질이 생기는 일이 다반사며 원활한 커뮤니케이션은 막혔던 문제를 훨씬 더 빠른 속도로 풀릴 수 있게끔 만든다.그럼 구체적으로 좋은 커뮤니케이션을 하기 위해 어떻게 해야 하는지 알아보자. 한 번쯤 들어봤을 이야기들이긴 하지만 구체적인 실행방안들을 추가해서 실제 기업이나 조직에서 바로 적용할 수 있도록 했다.건설적인 대화를 하라!너무나 당연한 말이지만, 이 말이 얼마나 업무 현장에서 지켜지고 있는지는 의문이다. 먼저 건설적인 대화의 방법들을 살펴보기 전에 어떤 대화들이 건설적인 대화가 아닌지를 살펴보자. 그리고 그것을 어떻게 건설적인 대화로 바꿀 것인지 말할 것이다.(1) 대화가 끝났어도 명확한 합의점이나 결과, action item, 해결책이 나오지 않았다.- > 이 문제는 두 가지 이유에서 비롯된다. 첫 번째는 대화의 목적(대화를 하는 이유)이나 목표(해결하고자 하는 것)가 불문명해서 대화가 어느 방향을 전개되야 하는지 갈피를 못 잡기 때문이다. 그리고 두 번째는 대화가 끝난 후 테스크로 전환하는 일을 하지 않은 것이다.==> 대화의 목적과 목표를 분명히 하라! 이야기를 시작할 때는 목적과 목표를 분명히 하라. '우리 지금 이 문제를 해결하기 위해 이야기하는 거죠?' '이 문제를 어떻게 처리할지에 대해 이야기해 봐요.' 일차원적일 수도 있겠지만 이렇게 직접적으로 이야기하는 것이 원활한 커뮤니케이션을 하는데 더 효과적이다. 목적과 목표를 정하지 않고 이야기를 하게 되면 이야기가 중간에 표류할 공산이 크다.==> 대화가 끝난 후에는 반드시 대화에서 얻어낸 결과물들을 테스크로 전환하고 각자에게 배분하라! 업무적 성격의 대화인 경우 문제 해결에 대한 이야기일 가능성이 크다. 이때 액션 아이템이나 합의점이 도출되지 않았다면 건설적인 대화가 이루어지지 않은 것이다. 업무 관련 일이 아닐 경우, 단순 아이디어 회의일 경우에는 대화하면서 나온 아이디어를 적고 문서화시켜야 한다. 그래야 나중에 '너 그때 이렇게 말했잖아!' 하면서 싸우는 일이 없다. 결론이나 결과가 없는 대화는 나중에 그 문제로 인해 다시 대화하게 될 가능성이 크다. 그리고 그것은 곧 리소스의 낭비다.(2) 논쟁을 하다 삼천포로 빠지고, 논쟁이 논쟁을 위한 논쟁으로 변질된다.-> 대화를 하다 보면 항상 좋게 좋게만 흘러가는 것이 아니다. 또 원활한 커뮤니케이션이 의견 충돌이 없는 소통을 의미하는 것도 아니다. 의견이 충돌하되 그것을 건설적이고 긍정적인 방향으로 풀어내는 것이 커뮤니케이션 능력이다. 이 케이스는 목적과 목표의 설정이 제대로 이루어지지 않아서이기도 하지만 대화를 하는데 있어서 서로가 명확히 해야 할 부분을 하지 않아서이기도 하다.==> 논쟁의 지점을 분명히 하라! 특히, 논쟁 지점이 여러 가지라면 뒤죽박죽 이 얘기 저 얘기 다 하면서 시간 소모를 할 공산이 크다. 건설적인 논쟁을 위해서는 우리가 어떤 포인트 때문에 논쟁을 하는지 서로 동의하는 부분은 무엇이고 동의하지 않는 부분은 무엇인지 명확히 해야 한다. ==> 용어를 분명히 하라! 서로 쓰는 용어의 의미가 달라서 논쟁이 되는 경우도 많다. 같은 문제를 바라봐도 다르게 말할 수 있고, 다른 문제를 이야기하는데 같은 용어를 통해 이야기할 수 있다. 원활한 커뮤니케이션의 기본은 용어 통일, 논의의 통일이다. 같은 수준에서 이야기할 때 비로소 원활한 소통을 할 수 있다.커뮤니케이션에 있어서 핵심은 '당신'이다.물론, 커뮤니케이션은 쌍방의 문제다. 내가 문제일수도 있고 상대방이 문제일 수도 있다. 하지만, 원활한 커뮤니케이션에 있어서 상대방을 바꾸는 것은 매우 어렵지만, 나를 바꾸는 것은 상대방을 바꾸는 것보다는 수월하다. 그리고 진정으로 커뮤니케이션을 잘하는 사람은 커뮤니케이션을 못하는 사람과도 '잘' 하는 사람이다. 커뮤니케이션을 잘하는 개발자로 인정받고 싶다면 그 누구와도 잘 할 준비가 되어 있어야 한다.그럼 어떻게 바뀌어야 커뮤니케이션을 잘 할 수 있게 되는지 세 가지 조건을 통해서 알아보자.(1) 자신과 상대방의 커뮤니케이션 스타일을 파악한다.서로 누구의 잘못이라기보다는 방식의 차이 때문에 싸우는 경우가 다반사다. 말투, 어투, 말하는 방식, 시기 등 자신의 스타일을 모르고 상대방의 스타일을 이해하지 못할 때 커뮤니케이션은 막혀버린다. 가장 좋은 것은 글로 적어보는 것이다. 나는 이렇고 상대방은 이렇다. 직접적으로 적어본다면 보다 커뮤니케이션 스타일을 이해하기 쉬워진다. 그리고 커뮤니케이션 스타일을 이해하는 것만으로도 커뮤니케이션을 할 때 많은 도움이 된다.(2) 상대방이 당신에게 망설임 없이 커뮤니케이션할 수 있게 하라!어떤 사람과는 커뮤니케이션 시작 자체를 하기가 어려운 사람들이 있다. 바쁘거나 시작하면 논의가 이루어지지 않거나 많은 조건들이 있을 것이다. 특히, 이 부분에 대해서는 스스로를 돌아보기가 매우 힘들다. 이때는 딱 두 가지의 것을 확인하면 된다.첫 번째로는 주변에 커뮤니케이션하기 망설여지는 상대를 찾아보라. 그리고 그 사람과는 왜 커뮤니케이션이 망설여지는지 생각해 보고 나를 돌아보면 된다. 타산지석(他山之石)이라 했던가. 혹시 내가 커뮤니케이션이 망설여지는 사람이 아닌지 다른 사람을 통해 되돌아보자.두 번째로는 다른 사람에게 솔직하게 물어보는 것이다. 이 방법이 사실 제일 중요하다. 내가 커뮤니케이션에 있어서 부족한 점은 없는지 상대방에게 물어보는 것이 가장 효과적인 방법이다. 물론, 솔직한 말을 듣는 것이 처음에는 두렵고 상처가 될 수도 있다. 하지만, 이것은 당신을 가장 성장시켜줄 대화 중 하나다. 동료만큼 당신과 커뮤니케이션을 많이 하는 사람도 없을 테니 바로 옆자리의 동료에게 자신의 커뮤니케이션에 있어서 부족한 점을 솔직히 말해달라고 부탁하라!(3) 동료와 친밀한 관계를 형성하고 공감하는 것은 중요하다.여기 회사 동료와 친할수록 일의 효율이 올라간다는 연구결과가 있다. 커뮤니케이션의 기본은 열린 마음이다. 그리고 마음은 상대방에게 호의가 있을 때 더 쉽게 열린다. 좋은 커뮤니케이션을 위해서라면 사전에 좋은 관계를 형성하는 것도 중요하다. 좋은 관계와 좋은 커뮤니케이션은 서로 밀접한 상관관계가 있다.대화가 커뮤니케이션의 전부가 아니다.대화만으로 모든 커뮤니케이션을 할 수는 없다. 효율적이지도 않고 물리적으로 불가능한 상황이 있을 수도 있다. 원활한 커뮤니케이션을 위해서라면 적절한 도구의 사용이 필요하다. 즉, 협업 툴을 효과적으로 사용하여 자신이 하고 있는 일들을 상대방에게 알려주고 상대방의 업무를 파악하려고 노력하라!도구의 사용은 커뮤니케이션에 사용하는 비용을 엄청나게 절감해 준다. 자신이 커뮤니케이션에 자신이 없고 언변이 부족하다 생각한다면 도구를 잘 쓰는 방식으로 커뮤니케이션 능력을 향상시킬 수 있다. 지금까지 위에서 언급한 것들은 쉽게 바뀔 수 있는 것들이 아니다. 왜냐하면 지금까지 몸에 체화된 자신만의 대화 방식을 바꾸는 것이기 때문이다. 하지만 커뮤니케이션 도구의 사용은 프로그램이다. 프로그램은 사용법을 배우면 된다.예를 들어, ASANA라는 협업 툴로 자신과 동료의 업무를 리스트화하고 체크할 수 있다. 또는, 구글 캘린더에 자신의 스케줄을 올려서 일정을 공유할 수 있다. 협업 툴을 이용하면 일의 진행사항들을 쉽게 공유하고 상대방의 일정을 파악할 수 있다. 그리고 이런 정보의 공유는 원활한 커뮤니케이션의 기본이다. 이런 도구들을 통해 커뮤니케이션이 부족한 사람들도 충분히 좋은 '커뮤니케이터'가 될 수 있다.커뮤니케이션도 실력이다.다시 처음으로 돌아가 커뮤니케이션의 필요성에 대해 다시 강조하려고 한다. 어떤 사람은 개발자의 핵심은 개발 능력이고 커뮤니케이션은 잘하면 좋은 것이라 생각한다. 위에서도 언급했지만, 개발자는 떠돌이 무사나 용병이 아니다. 조직에 소속되어 있는 개발자라면 소통하고 커뮤니케이션을 하는 능력이 핵심이다.그래서 개발자가 되려는 사람들에 항상 하는 말 중 하나가 다른 사람과 함께한 협업 프로젝트를 해보라는 것이다. 함께 프로젝트를 하는 경험은 프로그래밍 능력을 향상시킬 뿐만 아니라 어떻게 함께 개발하는지에 대해 많은 고민을 할 수 있게 해준다. 단순히 프로그래머가 되려면 코딩 실력에만 집중하라! 그러나, 다른 사람들과 함께 개발을 하는 개발자를 지향한다면 반드시 커뮤니케이션 역량도 향상시켜라!Good Developer 두 번째는 커뮤니케이션에 대해서 다루었다. 다음 Good Developer 는 나쁜 개발자에 대해서 알아볼 것이다.
조회수 1760

[인터뷰]미미박스의 TECHNOLOGY를 이끄는 CTO KAY를 만나다

안녕하세요. Ava입니다.여러분에게 더 건강하고, 아름다운 라이프스타일을 제공하기 위해 노력하는 미미박스 뒤에는여러분의 니즈를 만족시키고 안정된 서비스를 제공하기 위한 개발이끊임없이 진행되고 있습니다.오늘은 미미박스 TECHNOLOGY UNIT를이끌고 계신 김종광 CTO(이하 KAY) 님을 소개해드리겠습니다.KAY는 대기업과 IT기업에서 앱 개발과 웹 개발을 진행했던 커리어를 갖고 계신데요. 경력과 전문성 뿐만 아니라 개발자들이 상상할 수 있는 문화를 강조하며 만들어나가고 있습니다.항상 푸근한 아빠 미소로 미미박서의 질문과 제안을 받아주고,열린 리더의 모습을 보여주시는 KAY를 소개합니다.김종광 (Kay) 한국기술교육대학교 전기전자공학 석사전) NC소프트전) SK communicationsUNIT1. KAY를 소개해주세요.Q. 안녕하세요. 항상 아빠 미소를 짓고 계신 KAY~KAY를 소개해주세요.A. 처음에 미미박스에 모바일 앱 개발 총괄로 입사했어요. 지금은 개발 UNIT 전체를 맡고 있고요. 세 가지의 주 업무가 있는데요. 개발 전체 프로젝트를 leading 하고 다른 팀과 연관된 업무에 대해서 지원하는 일과, 새로운 개발자를 충원하는 업무를 하고 있습니다.Q. 미미박스에 입사하시게 된 계기는 무엇인가요? A. 처음에는 지인이 추천해서 미미박스를 알게 되었어요. 그 후에 미미박스에 대해 조사를 해봤죠. 비즈니스 모델, 성장 가능성을 봤을 때 '될 것 같다'는 생각을 가졌어요. 그리고 몇 번 찾아갔었는데 회사 분위기가 활기차고 재밌었어요. 그리고 일하는 사람들 중에 아는 사람들이 4~5명 정도 더 있었어요. 이분들과 다른 구성원들을 보면서 '이 친구들이랑 같이 일하면 즐겁게 일할 수 있겠다'라는 생각이 들었죠.Q. 여러 조직에서 있으셨던 만큼 개발 업무 자체에 대한 이유도 있을 것 같아요. A. 보통 기업에서는 개발자의 역할이 상당히 제한되어있어요. 업무에 대한 의사결정 권한이 거의 없죠. TOP-DOWN 방식으로 내려온 것들을 그냥 해야 하는 경우가 많거든요. 개발자들은 다들 알 거예요. 만들면서 '이거 안될 것 같다.'라는 감이 있는데, 느낌상으로 안될 것 같은 것을 만드니까 의욕이 생기지 않는 경우가 있었어요. 효과성보다는 어떤 서비스를 오픈했다는 것 자체가 실적이 되는 경우가 많거든요. Q. 그런 경우가 있군요. 그렇다면 미미박스 내에서는 개발 업무에 대해 어떤 식으로 의사결정이 이루어지나요?A. 업무에 대한 의사결정은 반반인 것 같아요. 우선 TECHNOLOGY UNIT 내부에서 프로젝트를 진행하는 것이 있어요. '이렇게 하면 회사와 서비스에 도움이 되겠다. 매출도 좋아질 것 같다. 사람들도 좋아할 것 같다'이런 의견을 내고 직접 만들 수 있고요. 서비스를 같이 진행하는 마케팅팀이나 플랫폼 운영팀에서 요청이 들어오면 그 요청에 대해 저희가 납득하고 하면 좋겠다고 생각하는 업무에 대해서 일정을 짜고 진행해요. 각 요소 별로 개발 측면의 논의도 많이 하고 실제 만드는 사람의 의견이 많이 반영됩니다. Q. 직접 만들고 구축하는 사람들의 의견은 정말 중요한 것 같아요.TECHNOLOGY UNIT을 이끌고 있는 UNIT 장님으로서 KAY의 하루 스케줄은 어떻게 되나요?A. 출근 후, 오전에는 집중 개발 업무를 하고 있어요. 제가 플랫폼 개발 팀장도 겸임하고 있거든요. 오후부터는 대부분 팀미팅이나 프로젝트 미팅을 많이 합니다. 프로젝트의 진행사항을 체크하고, 개발이 어려운 부분과 개발하면서 중요하게 생각하는 부분에 대해서 토론하죠. 그리고 새로운 개발 인력들 채용을 위해 면접을 많이 봅니다.Q. TECHNOLOGY UNIT 내부에서 소통과 역량 강화를 위해 주기적으로 여는 세션이 있다고 들었는데 소개해주세요!A. 일주일에 한 번씩 주니어 개발자를 대상으로 스터디를 진행하고 있습니다. 저는 스터디를 leading 하고 멘토 역할을 하고 있고요.이런 시간을 만들게 된 이유는 소통의 장을 만들고 개발자들의 역량을 키우기 위해서입니다.주니어 개발자들이 시니어 개발자들 앞에서 의견을 내는 것에 대해서 소극적인 면이 있어요. 틀릴까 봐 의견을 쉽게 못 내죠. 그래서 주니어 개발자들끼리 모여서 얘기할 공간을 만들어 주는 것이 중요하다고 생각했어요. 서로 의견을 내고 토론하면서 개발에 대한 역량도 쌓고 의견을 내는 훈련도 할 수 있죠.그리고 DATA UNIT의 협조를 받아서 빅데이터 관련 스터디를 진행하고 있어요. 그래서 개발자들 중 관심 있는 사람이들 모여서 빅데이터 관련 LOGIC을 만들어보고, 아이디어를 실현시켜보는 작은 프로젝트를 그룹별로 진행하고 있어요.앞으로는 이런 세션들을 발전시켜 세미나를 열 예정이에요. 그래서 각 개발자들이 적어도 1년에 2번 이상은 주제 발표할 수 있도록 환경을 만들려고 합니다. 개발 업무는 집중도가 높아서 건조해질 위험이 있어요. 집중하다 보면 일에 치여서 자기계발이 어려워질 수 있기 때문에 계속 자기계발하는 분위기를 만들어가려고 합니다. 전사적으로도 그런 분위기가 계속 만들어지면 좋겠어요.Q. 건조하긴요! 제가 보기엔 개발팀들이 가장 활발하고 참여도도 높은 것 같은데요! 열려있는 분들도 많고요.A. 개발팀이 아닌 팀들이랑 많이 소통하라고 조언을 많이 해요. 미미투게더(2개 이상 팀이 함께 회식하면 회식비를 지원해주는 기업문화 제도)를 할 때도 개발팀 내부에서만 하지 말고 무조건 다른 팀들과 함께하라고 하고 있어요. 새로운 아이디어를 나눌 수 있고 인간관계가 힘이 될 때가 많기 때문에 다른 팀들이랑 얘기를 많이 나누는 게 필요하죠. UNIT2. TECHNOLOGY UNIT을 소개해주세요.Q. TECHNOLOGY UNIT을 소개해주세요.A. TECHNOLOGY UNIT에서는 지금 미미박스에서 서비스하는 모든 PRODUCT, 플랫폼, 모바일 앱, PC 웹, 내부 직원들이 쓰는 모든 것들을 개발하고 있습니다. 대부분의 기업에서는 계약직이나 파견직의 고용형태로 진행하는 경우도 있는데, 저희는 모든 구성원이 정직원으로 개발 업무를 하고 있습니다.Q. TECHNOLOGY UNIT의 분위기는 어떤가요?A. 개발자라는 직무를 하는 사람들은 생각 자체가 자유로워야 합니다. 경직되어있으면 좋은 아이디어가 떠오르지 않죠. 그래서 TECHNOLOGY UNIT은 최소한의 규제나 룰을 두고 자유롭게 활동하게 하고 있어요. 특별한 일이 아니면 회의 소집도 지양하고 있어요.다양하게 상상하려면 경직되지 않고, 룰에 집착하지 않는 문화를 만들어야 하기 때문이에요. 그래야 본인의 의견도 편하게 이야기할 수 있죠. 구성원들을 보면 시니어 개발자들은 적응을 잘해요. 주니어 개발자들이 아직 조금 경직되어있긴 해요.지금 신입 공채 2기를 뽑고 있는데요. 보통은 스타트업에서 입사 후 바로 투입될 수 있는 사람을 뽑아요. 하지만 저는 확신이 있어서 저희 미미박스의 DNA를 가지고 처음부터 함께할 수 있는 신입을 뽑고 싶어요. 미미박스의 DNA를 가지고 더 성장하게 되면 저희 개발 조직에 기둥이 될 수 있을 거라고 생각합니다. <채용공고 보러 가기 클릭>Q. KAY와 함께 하는 구성원들이 점점 부러워지네요. 정말 구성원들의 성장에 많은 비중을 두고 여러 계획을 실천하는 것 같아요. 미미박스에는 여성 개발자도 점점 많아지는 것 같은데 재미있는 에피소드 있나요?A. 먼저 여성 개발자들의 비중이 점점 늘어나고 있어요. 우리가 여성 고객을 위한 서비스를 많이 하고 있잖아요. 그 감성을 같이 공유할 수 있는 사람이 많아지면 기술적인 부분뿐 아니라 감성적인 부분에서도 큰 시너지가 나죠. 실제로 웹페이지에 제품 가격이 잘못 올라간 적이 있어요. 저희 남성 개발자들이 그 데이터를 먼저 보는데 '이게 맞는 가격인가' 의심하는 사람이 아무도 없었어요. 그때 여성 개발자분이 '이 제품이 이 가격이 아닐 텐데? 문제를 제기했고 다행히 수정할 수 있었죠. Q. 그래도 남성 개발자들의 화장품 가격에 대한 감이 점점 정확해질 것 같아요. 호호KAY 님이 UNIT을 운영하시면서 가장 보람을 느끼신 적은 언제인가요?A. 고객들이 많이 와서 저희 서비스를 이용해 주실 때 보람을 느낍니다. 저희 UNIT 자체에서도 무언가를 만들어가고 있다는 것을 느끼고, 실제로 좋은 반응을 얻었을 때 기분이 정말 좋아요. Q. 점점 더 많은 분들이 미미박스를 찾아주신다는 게 느껴져요! 앞으로의 목표는 무엇인가요?A. 첫 번째는 글로벌로 플랫폼을 옮기는 것입니다. 저희 내부에서 개발한 플랫폼과 서비스가 점점 확대돼서 미미박스가 해외에 진출할 때마다 플랫폼을 그대로 이동시켜 글로벌화하는 것이 첫 번째 목표고요. 두 번째는 앞으로 온라인을 넘어 오프라인에 대한 서비스도 진행할 예정이에요. 현재 미미박스 플랫폼과 오프라인 요소의 연계성을 찾고 최고의 고객 경험을 만드는 것이죠. 일반적인 O2O 서비스를 넘어 대부분의 고객이 여성이기 때문에 IT 기술 자체가 숨어있고, 알아서 돌아가게 만드는 서비스를 만들 것입니다.미미박스는 뷰티에 대해서 많은 강점과 다양성을 가지고 있기 때문에 이런 것들을 통해 저희만 할 수 있는 서비스를 만들고 싶어요. 마지막은 Data-driven 방식을 더욱 견고히 가져가는 것이에요. 축적되어있는 경험과 데이터를 통해서 고객 맞춤형 서비스에 대한 역량을 강화하는 것이죠. Q. 글로벌 플랫폼, O2O 서비스, Data-driven 앞으로의 TECHNOLOGY UNIT이 만들어낼 것들이 기대돼요. 두근두근. 마지막으로 KAY가 TECHNOLOGY UNIT을 리드하면서 가장 집중하는 3가지가 무엇인지 궁금합니다. A. 가장 중요한 것은 우리 개발자들의 커리어를 관리해주는 것이에요. 이분들이 미미박스에 와서 자기의 역량이 발전하지 않고 정체되다면 제가 역할을 제대로 못했다는 뜻이거든요.그래서 구성원들이 고생을 하든 뭘 하든 해가 갈수록 성장할 수 있도록 관리하는 것에 집중하고 있어요. 두 번째는 우리가 TECH 조직이기 때문에 서비스가 아주 정상적으로 운영되는 것이 목표에요. 단순한 장애를 없애는 것뿐만 아니라 계속 플랫폼이 발전하면서 문제가 없게 만들어야 하죠. 매출, 데이터가 계속 쌓이면서 안정적인 서비스를 만드는 것, 기본적인 것 같지만 가장 중요한 것 같아요. 마지막으로는 Align이에요. 개발팀이 성장할 수 있는 서비스, 개발 역량을 강화시키다며 보면 회사의 목표에 Align 되는 것을 놓칠 수 있어요. 그렇기 때문에 개발자들이 관심 있는 것들과 회사의 목표를 Align시켜서 시너지 효과를 낼 수 있도록 집중하고 있습니다.UNIT2. TECHNOLOGY UNIT으로서 어떤 사람과 일하고 싶나요?Q. TECHNOLOGY UNIT에서 일하기 위하여 갖추어야 할 역량은 어떤 것이 있나요?A. 첫 번째로 성장 가능성을 봅니다. 성장 가능성에는 여러 가지 의미가 있지만 적극적이고, 새로운 지식에 대한 욕구가 항상 강한 사람이어야 합니다. 배우고 싶은 열망, 해보고 싶다는 열망을 가지고 실제 구체적으로 실행해본 경험이 있고, 뭔가를 해본 사람이 성장 가능성이 있는 사람이라고 생각합니다. 제가 면접을 볼 때마다 항상 물어보는 것이 '5년 후 계획, 5년 후 모습은 어떨 것 같아요?'에요. 면접자가 적극적으로 대답하면 '그것을 위해 어떤 실행계획이 있는지' 물어보죠.두 번째로는 스타트업 마인드 FIT이 맞는 것이에요. 저도 미미박스에 처음 왔을 때 힘들었어요. 갖춰져 있는 게 없었거든요. 하나부터 열까지 하려면 뭔가 어디서 걸리는 거예요. 큰 회사는 세팅이 다 되어있는데 말이죠. 그래서 뭔가를 하려면 그 업무뿐 아니라 처음부터 다 찾고 만들어야 해요. 이렇게 만들어가는 걸 좋아하는 사람이 있어요. 준비가 안되어있다고 불평하는 것이 아니라 부족한 환경에서 할 거리가 많은 것을 반기는 사람들. 이런 사람들은 '이것저것 해봐야지~' 신나있어요. 이런 마인드 FIT을 많이 봅니다.Q. 스타트업 마인드 FIT 정말 공간되는 말인 것 같아요. 저도 갖춰져있는 틀에서 무언가를 하는 것보다 이것저것 찾아서 만드는 걸 좋아하거든요! 그런 분들이 많이 오시면 재밌는 일이 많이 벌어질 것 같아요. 우리 미미박스의 비전은 'Beautify the people'인데요. 혹시 취업이나 이직을 준비하는 분들께 이것만은 아름답게 관리하라고 조언하고 싶은 게 있나요?A. 이력서와 경력기술서를 아름답게 해야 해요. 개발자들 중에 '내 역량만 좋으면 되지'라고 생각하시는 분들이 있는데 자신의 커리어 패스를 만드는 것도 중요하거든요. 회사에서 처음에 서류전형을 진행하는 게 많은 내용을 내포하고 있어요. 경력기술서의 내용이 부실하면 회사도 본인도 FIT이 맞는 곳을 찾기가 어려워지죠. 어디서든 인정받는 사람이 되려면 자신의 업무와 역할을 충실하게 표현한 경력기술서를 작성하라고 말씀드리고 싶네요.Q. 정말 실질적인 조언이네요. 누구보다 깊게 고민하고 집중한 일일수록 경력기술서와 이력서를 잘 쓸 수 있고 자신의 경력도 잘 전달할 수 있을 것 같아요.마지막으로 함께 일하고 있는 미미박서분들께도 한마디 해주세요!A. 제가 여기 처음 와서 한 이야기가 있어요. "여기가 제 마지막 회사입니다."그렇게 이야기한 이유는 미미박스의 성장 가능성, 발전 가능성을 보았고 믿음이 있기 때문이죠. 모두가 같이 노력한다면 원하는 것을 이룰 수 있을 거라 생각해요. 다 같이 파이팅!
조회수 2445

데이블 주니어 개발자 직무 인터뷰

오후 두 시의 회의실. 개발자들의 스터디하는 소리로 뜨겁다. 국내 최고의 추천 기술을 보유했다는 데이블. 10년 이상의 경력을 가진 노련한 시니어 개발자들 사이에서, 스쳐 지나가는 단어 하나하나 놓치지 않으려 귀 기울이고 있는 주니어 개발자들을 만났다.안녕하세요? 간략한 소개와 두 분의 업무에 관해 설명해주세요.형주: 안녕하세요? 저는 데이블 개발팀 최형주입니다.저는 백앤드 개발팀의 신입 개발자로서 데이블의 인프라 관리, 백앤드 개발 그리고 가끔 데이터 분석을 하고 있습니다. 주로 사용하는 서버는 클라우드 플랫폼인 AWS(Amazon Web Service)과 Nodejs 이고, MySQL, Redshift, Python을 사용하여 데이터 처리와 분석을 하고 있어요.성현: 안녕하세요. 저는 데이블 개발팀 이성현입니다.제 메인 업무는 데이블 위젯의 스타일링과 관련 문제 해결입니다. 고객사 페이지를 분석해서 위젯 디자인을 만들고, 추천 결과가 안 나오는 경우에 문제를 수정하는 작업입니다. 특별한 기능이 필요한 위젯이 있으면 스크립트 작업도 하고요. 작업 도구는 회사 내부 시스템이 있어서 그 안에서 직접 작업하고, CSS로 작성합니다.위 업무가 메인이지만 다른 영역과 겹칠 때도 잦아서 회사에서 사용하는 여러 시스템을 만질 수 있어야 합니다. 도구는Html+CSS+js 외에 Node, gulp, react, angular angularJS, PHP, 젠킨스, AWS, MYSQL, git를 사용하고 있습니다.두 분 다 신입 개발자이신 만큼 회사를 선택하는 데 있어 신중했을 것 같아요.데이블을 선택한 이유는 무엇인가요?형주:  저는 대학원에서 빅데이터 처리관련 연구를 주로 했었어요. 졸업할 때쯤 제 전공과 관련된 회사에 지원했었고 많은 면접을 보았습니다. 여러 회사에서 면접을 봤지만 데이블에서 봤던 면접 경험이 만족스러웠고 특히 개발자들의 실력과 내공이 느껴져 신입으로서 많은 것을 배우고 싶어서 입사하게 되었습니다. 복지 또한 여느 알려진 회사들에 비해 부족하지 않아서 굉장히 만족하고 있습니다.성현: 처음 데이블에 호감을 느끼게 된 건 기술 중심 스타트업이라는 점이었습니다. 도전하는 자세, 유연한 사고, 성장 가능성, 복지 등 여러 가지 기준들이 있겠지만, 내가 재미를 느낄 수 있는가, 개발자로서의 성장 이 두 가지로 압축되었어요. 저 같은 경우에는 블로그를 보면서 회사 분위기를 대략 파악했던 것 같네요. 자유로운 분위기도 잘 느껴지고, 서로를 배려하면서 열심히 일하는 것을 간접적으로 경험할 수 있었어요. 면접 보러 갔을 때, 블로그에서 보던 사람들이 블로그 글과 비슷한 느낌으로 편하게 얘기하는 걸 보면서 마음을 굳히게 됐어요.데이블의 분위기는 어떤가요?형주: 분위기는 실제로 굉장히 수평적입니다. 서로 존댓말을 사용해서 존중받는 기분이 들어요.성현: 저는 데이블 오기 전에 잠시 다른 회사에 있었는데, 거기서는 과한 예절이나 눈치를 보는 분위기가 있었어요. 데이블은 수평적인 분위기이다 보니 스트레스 받지 않고 일에 집중할 수 있어 좋아요.형주: 저 같은 경우, 잠에 굉장히 민감한 편인데 출퇴근이 탄력적이어서 지각에 대한 스트레스가 없어서 좋아요. 그래서 저는 보통 9시 넘어서 일어나서 10시쯤 출근하고 7시쯤 퇴근하는 편입니다. 그리고 식대도 지원해주고 있어요~성현: 매일 4시쯤 회사가 지원하는 간식 타임이 있어요. 오랜 시간 앉아서 일하다 보면 집중력 떨어질 때 쯤 다 같이 모여 대화를 나누면서 간식을 같이 먹습니다. 만약 생일이 있으면 간식 타임과 더불어 생일 파티를 해요.형주: 간식과 음료수가 항상 냉장고에 갖춰져 있어서 먹을 것을 좋아하는 사람에게 최고인 것 같아요. 저는 살이 잘 안 찌는 체질인데 입사 후 2킬로가 쪘어요.성현: 거의 슬랙과 트렐로 위주로 업무를 하는데 간식 타임에는 여러 사람과 대화를 할 수 있어 좋습니다. 서로 대화도 같이하고, 같이 활동할 수 있는 시간을 마련하기 위해 ‘플레이 데이’ 도 2개월에 한 번씩 열고 있어요! 회사-집, 집-회사를 반복하다가 다 같이 뭔가를 하니 신선했어요. 업무 외적으로 같이 활동하면서 사람들과 친밀감을 느낄 수 있어서 좋았어요.데이블을 선택했던 이유 중 개발자로서 성장 가능성도 있었는데 이것은 어떻게 채워지고 있나요?성현: Dabler, Be The Expert 프로그램(이하 BTE 프로그램)이 있고 업무 관련 스터디도 활발히 진행하고 있어요.자세히 설명해주세요. 성현: BTE 프로그램의 경우 장기목표를 정하고 반기별로 관련 학습 계획을 세워요. 그 안에서 책도 사고 강의도 신청하고 하는 거지요. 스스로 목표를 잡고 자유롭게 계획을 세울 수 있어서 좋아요. 본인이 정말 원하는 것을 배울 수 있고, 필요한 자금은 회사가 지원하는 거죠. 단, 업무에 관련된 성장 계획이어야 한다는 가이드라인이 있어요.이 외에도 백엔드 개발자들과 함께 AWS 사용법을 주제로 스터디도 해요! 보통 프론트엔드를 담당하지만, 백엔드 영역도 경험할 수 있어요. 본인 스스로 영역을 넓히기 위해 공부하고 능력이 된다면 활동 범위가 굉장히 넓어져요. 회사 차원에서도 그런 시도를 장려해요. 빨리 성장해야겠다는 욕심이 있어요.형주: 전 회사에서 일주일에 2번 모여서 스터디도 하고 있고 MOOC 강의를 수강하거나 책을 사고 싶을 때 눈치 볼 필요 없이 신청하면 돼요. 그리고 반기별로 자기 개발을 잘한 직원에게 인센티브를 줘요.※BTE 프로그램이란?그럼 두 분은 BTE 프로그램을 통해 어떤 것들을 배우고 계시는가요?형주: 저는 Coursera에서 Recommender System 수업을 듣고 있어요. 아무래도 우리 회사의 핵심기술이 추천 기술이다 보니까 이쪽 분야를 깊게 공부해야겠다는 생각이 들었습니다.성현: 저는 웹을 능숙하게 다루고 싶어서 상반기에는 인프라, 자바스크립트, 웹 표준, node 등 기본을 다시 챙기고 하반기에는 웹 최신 기술을 공부하려고 해요.지금은 자바스크립트 관련 책 3권과 강의 2개를 신청해서 주로 퇴근 후 또는 주말에 듣고 있어요. 업무와 관련된 것을 공부하고 나서 코드를 작성하면 대충 넘어갔던 부분들이 보여요. 그 부분을 놓치지 않고 수정하고 개선하다 보면 예전보다 나은 결과물이 나오고 뭔가 아는 게 늘었구나! 하는 보람을 느낍니다.데이블에서 개발자로 일하며 느끼는 점형주: 저의 경우에는 신입 개발자 관점에서 경험 많은 개발자분의 피드백을 통해 노하우를 전수하는 점이 좋았어요. 그러면서 기존에 놓치고 있던 부분이나 실무와 이론 사이의 괴리감을 좁히는 경험이었습니다. 저도 학부, 대학원 시절 많은 코딩을 했지만 제가 작성한 코드가 잘 작성된 코드인지 잘 읽히는 코드인지는 스스로 공부하기 힘들었는데 이러한 피드백을 통해 성장함을 느꼈습니다.어려웠던 점은 우리 회사는 애드테크 회사이다 보니 광고 용어를 굉장히 많이 사용하는데 광고에 관해 얘기할 때 처음에는 광고 용어를 몰라 답답했었는데, 스터디를 만들어서 어려운 점을 조금은 해소할 수 있었어요.성현: 자기만 할 수 있으면 얼마든지 여러 프로젝트에 참여할 수 있는 문화가 좋아요. 예를 들면 저는 위젯 담당이지만, 위젯 업무 틈틈이 데이블 시스템 페이지 수정을 할 수도 있고 내부 DB를 이용해서 사업팀에게 도움이 되는 통계 페이지를 만들기도 해요. 얼마 전에는 커뮤니티에 데이블 추천 기능을 직접 넣는 프로젝트를 했습니다. 보통 추천 연동은 고객사가 하고 저는 위젯만 만들고 있었거든요. 이번에 고객사 입장에서 서버 쪽을 만져본 거죠.미래의 데이블은 어떤 모습일까요?형주, 성현: 세계 No. 1 콘텐츠 디스커버리 플랫폼! 경영진이 자기 개발 지원이나 복지에 신경을 많이 쓰고 있어서 계속 나아질 것 같아요.데이블의 개발자가 되기 위해 어떤 것들이 필요할까요?형주: 제가 생각하기에 시니어 개발자분들이 가장 중요하게 여기는 부분은 CS 분야의 기본기였던 것 같습니다. 이 기본기를 통해 자주 사용하는 툴이나 오픈 소스가 내부적으로 어떻게 구성되어 있고 동작하는지에 대한 공부를 하면 도움이 될 것 같습니다.성현: 저는 주도적인 자세요! 스스로 일하고 배우는 자세가 필요합니다. 다른 개발자와 소통하면서도 자기 일의 진행 관리나 조율은 스스로 해야 해요. 다음 일을 직접 찾아야 할 때도 있고요. 또 전부를 물어볼 수는 없으니 어느 정도 혼자 찾아 공부하는 습관도 필요해요. 그리고 자기가 지원하는 포지션에서 사용하는 핵심 기술 하나는 능숙하게 사용할 수 있어야 해요. #데이블 #팀원 #개발자 #개발팀 #개발 #팀원소개 #인터뷰 #기업문화
조회수 1160

개발자의 경력관리란?

경력이 아닌 업력이 되는 단계에 이르러야 가능한 것 아닌가 합니다.대부분의 경력은 '어느 회사의 누구'라는 표현에서 만들어진 것이 아닙니다.진정한 경력의 결과는 '자신의 이름'이 곧 브랜드화 되는 것입니다.매우 당연하게,하루 이틀, 한 두해 한다고 해서 얻어지는 것이 아닙니다."10년 경력!"10년 이상 한 분야나 하나의 도메인, 하나의 테크, 하나의 경력, 하나의 경험을 꾸준하게 파고들었을 때에 얻어지고, 그러는 경험속에서 인사이트, 통찰력이 생기게 됩니다.물론. 그래서, 20대에도 명성을 얻을 수 있는 '경력관리'가 가능하다고 이야기합니다.(실제 얻은 사람을 많이 봤습니다. 그들은 10대에 시작했죠. )회사의 테두리 내에서 얻을 수 있는 '경력'은 '경험'일뿐입니다.자신의 이름을 중심으로 기술할 수 있을 때에 '경력'이라고 이야기할 수 있습니다.개발자라면...글을 써서도 얻을 수 있고,강연을 해서도 얻을 수 있고,GitHub에 오픈소스를 공개하면서도 얻을 수 있습니다.현재 30대와 그 이전의 개발자라면...10대와 20대도 똑같습니다.40대, 50대 이후를 준비하세요.반복적인 일, 똑같은 일, 회사의 프로세스의 하나인 일만 하는 '사람'이라면...그냥, 그 회사의 톱니바퀴가 되는 것입니다.대부분 '경력관리'가 잘 안됩니다.앞으로 50대 이후에도 '브랜드'를 얻을 사람이 되려면...자신의 '경력'관리를 잘 해야 얻을 수 있습니다.나중에 닭 튀기거나 치킨 배달할 것이 아니라면...관리를 잘해야 합니다.경력관리가 가능하려면 어떤 회사를 찾아야 할까요.다음을 기억하세요.1. 구루급 개발자가 있는 회사를 찾으세요.2. 자신이 주도적으로 무언가를 만들 수 있는 권한과 책임을 줄 수 있는 회사를 찾으세요.3. 커뮤니티나 외부 강연, 외부 오픈소스 개발 행사에 적극 참여할 수 있는 기회를 주는 회사를 찾으세요.4. 반복적인 업무와 정체된 마켓에서만 반복적으로 서비스를 하는 회사는 회피하세요.5. 우리 도메인은 원래 이래, 이 일은 원래 이래... 이런 식으로 이야기하는 '상급자'가 있는 회사를 피하세요.6. 쉽게 설명할 수 있도록 준비하고, 리뷰를 할 수 있는 기회와 시간이 주어지는 회사를 찾으세요.그리고, 마지막으로...비전은 누가 주거나 만들어 주지 않습니다.결국, 자기 자신이 찾아야 하는데...이것도, 주변에 이야기가 통하는 '구루급 개발자'가 있어야 그나마 방향성을 찾기 좋습니다.혼자 고민하거나,주변에 비슷한 사람들끼리 고민해봐야 답이 안 나옵니다.꼭, 기억하세요!'구루급 개발자'와 상의하세요.그분들은 실패와 성공, 포기와 단념, 선택과 집중에 대해서 알고 있답니다.퇴근시간이라면..구루급 개발자에게 치맥 한잔 하자고 하세요!
조회수 3038

GitHub 계정으로 Kubernetes 인증하기

초기에는 kube-aws가 만들어준 관리자 인증서를 통해 Kubernetes를 관리했는데 역시나 대내외적으로 여건이 바뀌니 변화가 필요했다. 내부적으로는 개발 인력이 늘고 여러 프로젝트가 동시 진행되니 Staging 환경이 급격히 바뀌는데 계정이 하나이니 누가 무슨 작업을 했는지 확인하기 어렵고 외부적으로는 경쟁사의 보안사고 등에 영향을 받아 보안을 강화할 필요가 생겼다. 하여 보안 관련 작업을 여럿했고 그 중 하나가 바로 GitHub와 Kubernetes를 OAuth로 엮는 일이다.기본적으로는 개발자 각자가 자신의 GitHub 계정으로 인증 토큰을 받고 이를 이용해 Kubernetes API에 접근하는 것이다. 전체적인 흐름은 How I built a Kubernetes cluster so my coworkers could deploy apps faster 등을 참고하면 이해하기 그리 어렵지 않다.1. Admin time should be saved (since they are also our developers)2. New users can generate their own credentials without needing the admin3. User credential is always private for security reasons4. Developers have their own space to experiment5. Project spaces can be accessed and changed by multiple users6. In the future, we may want to enable auditing to track changes다만 저들과 달리 Webhook 토큰 인증 플러그인을 직접 짜지 않고 coreos/dex를 이용했다. Dex를 이용하면 GitHub를 비롯해 다양한 OpenID, OAuth 2.0 인증 서비스와 Kubernetes 클러스터를 엮기 쉽다. 더욱이 kube-aws에 Dex가 통합되어서 설치하기도 쉽다.설치하기구구절절 어떻게 설정하는지 설명할 생각은 없는데 회사와 프로젝트에 따라 세부적인 차이가 꽤나 클 수 있기 때문이다. 그러니 대략적인 작업 순서를 간략히 기술하고 끝내려 한다.우선 kube-aws의 cluster.yaml를 보자.# # Enable dex integration - https://github.com/coreos/dex # # Configure OpenID Connect token authenticator plugin in Kubernetes API server. # # Notice: always use "https" for the "url", otherwise the Kubernetes API server will not start correctly. # # Please set selfSignedCa to false if you plan to expose dex service using a LoadBalancer or Ingress with certificates signed by a trusted CA. # dex: # enabled: true # url: "https://dex.example.com" # clientId: "example-app" # username: "email" # groups: "groups" # selfSignedCa: true # # # Dex connectors configuration. You can add configuration for the desired connectors suported by dex or # # skip this part if you don't plan to use any of them. Here is an example of GitHub connector. # connectors: # - type: github # id: github # name: GitHub # config: # clientId: "your_client_id" # clientSecret: "your_client_secret" # redirectURI: https://dex.example.com/callback # org: your_organization # # Configure static clients and users # staticClients: # - id: 'example-app' # redirectURIs: 'https://127.0.0.1:5555/callback' # name: 'Example App' # secret: 'ZXhhbXBsZS1hcHAtc2VjcmV0' # # staticPasswords: # - email: "[email protected]" # # bcrypt hash of the string "password". You can use bcrypt-tool from CoreOS to generate the passwords. # hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" # username: "admin" # userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"우선 GitHub의 Organization Settings 메뉴로 가서 OAuth Apps에 Dex를 추가한다. 이때 Authorization calllback URL은 https://dex.example.com/callback가 된다.GitHub가 준 Client ID와 Client Secret를 cluster.yaml에 적어넣는다.dex: enabled: true url: "https://dex.example.com" clientId: "example-app" username: "email" groups: "groups" selfSignedCa: false # # # Dex connectors configuration. You can add configuration for the desired connectors suported by dex or # # skip this part if you don't plan to use any of them. Here is an example of GitHub connector. connectors: - type: github id: github name: GitHub config: clientId: "GITHUB_OAUTH_APP_CLIENT_ID" clientSecret: "GITHUB_OAUTH_APP_CLIENT_SECRET" redirectURI: https://dex.example.com/callback org: DailyHotel # # Configure static clients and users staticClients: - id: 'example-app' redirectURIs: 'https://kid.example.com/callback' name: 'Example App' secret: 'ZXhhbXBsZS1hcHAtc2VjcmV0'staticPasswords: - email: "[email protected]" # # bcrypt hash of the string "password". You can use bcrypt-tool from CoreOS to generate the passwords. hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" username: "admin" userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"여기서 dex.example.com은 kube-aws가 띄울 dex Deployment와 연결되는 서비스(ELB)의 도메인주소가 되어야 한다. 그런데 kube-aws는 Dex의 External service를 생성해주지 않으므로 아래와 같이 직접 서비스를 생성해야 한다. GitHub가 이쪽으로 콜백을 보내야 하므로 방화벽을 열어야 하고 회사 도메인 인증서를 붙일 것이므로 `selfSignedCa`값은 `false`로 한다.apiVersion: v1 kind: Service metadata: name: dex namespace: kube-system labels: app: dex component: identity dns: route53 annotations: domainName: dex.example.com service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:blahblah service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https spec: ports: # the ports that this service should serve on - name: https port: 443 targetPort: 5556 protocol: TCP selector: app: dex component: identity type: LoadBalancer loadBalancerSourceRanges: - 0.0.0.0/0staticClients / example-app는 Dex에 포함된 예제 프로그램이다. 이를 이용하면 웹 브라우저를 통해 GitHub에 인증하고 토큰을 내려받을 수 있다. DailyHotel/kid 등의 도커 이미지를 사용하면 쉽게 띄울 수 있다. kube-aws는 이 예제 프로그램을 띄우지 않기 때문에 직접 올려야 한다.apiVersion: v1 kind: Service metadata: name: kid namespace: kube-system labels: app: kid dns: route53 annotations: domainName: "kid.example.com" service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:blahblah service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https spec: ports: - name: https port: 443 targetPort: 5555 protocol: TCP selector: app: kid type: LoadBalancer loadBalancerSourceRanges: - 사무실IP/32 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: kid namespace: kube-system spec: replicas: 1 template: metadata: labels: app: kid spec: containers: - name: kid image: dailyhotel/kid:latest livenessProbe: tcpSocket: port: 5555 timeoutSeconds: 120 ports: - containerPort: 5555 env: - name: CLIENT_ID value: example-app - name: CLIENT_SECRET value: ZXhhbXBsZS1hcHAtc2VjcmV0 - name: ISSUER value: https://dex.example.com - name: LISTEN value: http://0.0.0.0:5555 - name: REDIRECT_URI value: https://kid.example.com/callback이때 example-app의 REDIRECT_URI는 Dex의 REDIRECT_URI와는 다르다는 점에 주목하자. 옵션의 이름이 비슷하기 때문에 헷갈릴 수 있다. 또한 CLIENT_ID와 CLIENT_SECRET은 cluster.yaml 중 GitHub connector 설정이 아닌 staticClients 설정에서 쓴 값이라는 점도 눈여겨볼 필요가 있다.이 정도만 주의하면 dex를 설치하고 설정하는 것은 어렵지 않다. 이제 인증하는 방법을 알아보자.인증하기웹브라우저로 kid에 방문해서 토큰을 받는다. 첫 화면에서 Login 버튼을 누른 후 GitHub 로그인을 하면 토큰이 나온다.GitHub Public profile 메뉴로 가서 Public email 설정을 확인한다. 공개 이메일이 없다면 하나 추가한다. 로그인시 사용자 아이디로 쓰기 위함이다.kubeconfig 파일을 열고 kubeconfig 파일을 열고 MY_PUBLIC_GITHUB_EMAIL에는 GitHub 공개 이메일 주소를 적고 VISIT_KID_EXAMPLE_COM_AND_GET_TOKEN에는 앞서 받은 토큰을 적는다.apiVersion: v1 kind: Config clusters: - cluster: certificate-authority: credentials/ca.pem server: https://MY_KUBE_CLUSTER name: kube-aws-cluster contexts: - context: cluster: kube-aws-cluster namespace: default user: MY_PUBLIC_GITHUB_EMAIL name: kube-aws-context users: - name: MY_PUBLIC_GITHUB_EMAIL user: token: VISIT_KID_EXAMPLE_COM_AND_GET_TOKEN current-context: kube-aws-context인증 파일의 설정이 정확한지 확인하려면 kubectl --kubeconfig=./kubeconfig version을 실행해보자. 아래와 같이 Client/Server의 버전이 둘다 나오면 정상이다.$ kubectl --kubeconfig=./kubeconfig version Client Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.1", GitCommit:"b0b7a323cc5a4a2019b2e9520c21c7830b7f708e", GitTreeState:"clean", BuildDate:"2017-04-03T20:44:38Z", GoVersion:"go1.7.5", Compiler:"gc", Platform:"darwin/amd64"} Server Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.2+coreos.0", GitCommit:"79fee581ce4a35b7791fdd92e0fc97e02ef1d5c0", GitTreeState:"clean", BuildDate:"2017-04-19T23:13:34Z", GoVersion:"go1.7.5", Compiler:"gc", Platform:"linux/amd64"}참고 자료johnw188/dex-exampleKubernetes / Authenticating#데일리 #데일리호텔 #개발 #개발자 #개발팀 #기술스택 #도입후기 #일지 #경험공유 #Kubernetes #Github
조회수 2446

하얗게 불태웠다. 트레바리 홈페이지 리라이팅 후기

1월부터 4월까지 한 시즌에 걸쳐 트레바리 홈페이지를 다시 구현하였다. 겉으로 보이는 UI/UX 디자인 개편을 넘어, DB 설계와 서버 및 웹 페이지 개발까지 새롭게 진행했다. 기존의 홈페이지를 완전히 버리고, 새로운 아키텍처를 가진 홈페이지를 구현하여 데이터를 이전하는 일이었다.4개월 동안 반응형 웹 사이트 1개, 크루/파트너 어드민 사이트 2개와 함께 서버까지 구현했다..지난 시즌 동안 홈페이지의 여러 기능들을 개선하면서 변화가 필요하다고 생각했다. 단순히 '남이 짜둔 코드가 별로예요'에서 나온 불편 때문만은 아니었다. 회사가 겪는 빠른 성장에 발맞춰 시스템이 뒷받침이 되어줘야 하는데 기존의 아키텍처로는 그러기가 어려웠다. 적은 트래픽에도 툭하면 죽는 서버 덕에 접속이 몰리는 멤버십 신청 기간 동안에는 서버 비용을 배로 늘려야 했고, 푸시 알림의 필요성으로 모바일 앱을 구현하고 싶어도 별도의 API 서버가 존재하지 않아서 시도하기 힘들었다. 결국 지난 시즌 말, 홈페이지를 새로운 아키텍처에서 다시 구현하겠다는 호기로운 결정을 내렸다.처음 시작할 때만 해도 아주 큰 어려움은 없겠거니 했다. 트레바리 입사 이전에 여러 프로젝트를 턴키로 수주받아 진행했던 경험이 있었기 때문이었다. 그러나 몇천 명, 많게는 몇만 명이 접속하는 운영 중인 서비스를 만들어 이전하는 일은 새 서비스를 만드는 일과는 또 다른 일이었다.게다가 이전 글에서 이야기했던 것처럼 트레바리에는 풀타임으로 일하는 개발자나 디자이너가 나 혼자이기 때문에 해야 하는 일이 절대적으로 많았다. 개발 맨 아랫단부터 웹 페이지의 디자인까지 기간 내에 해내는 것은 쉽지 않은 일이었다. 덕분에 매일이 도전이었던 4개월을 보냈고, 런칭 3주 전쯤에는 잠시 슬럼프를 겪기도 했다. 하지만 트레바리가 한 번은 꼭 겪어야 하는 과제였기에 꾸역꾸역 해내면서 런칭까지 왔다. 오늘은 그 이야기를 정리해보려고 한다.리라이팅왜, 무엇을 했나요?1. 과도한 서버 비용과 느린 속도홈페이지를 다시 만들어야겠다는 생각을 가장 많이 하게 된 이유는 비용과 속도였다. 동시 접속 유저 수가 천 명이 안 되는 서비스에서 월 100만 원가량의 서버 비용이 나왔고, 평균 페이지 로딩 속도가 3초를 넘어갔다.그동안 트레바리 홈페이지는 여러 프리랜서 개발자들이 거쳐가며 유지되느라 DB나 쿼리 구조에 대한 고민을 장기적으로 해볼 기회가 없었다. 요청받은 기능을 구현하기 위해 필요한 테이블을 그때그때 만들고, 활용할 데이터가 다른 테이블에 있다면 조인을 해서 불러왔다. 그 결과 대부분의 데이터 요청에 n+1 쿼리가 존재했고, 한 명의 유저가 한 번의 접속만으로도 수많은 쿼리 요청을 하는 상황이었다.최대한 기존의 홈페이지에서 이를 해결해보려고 노력했다. 처음 입사했을 때만 해도 10초 이상의 시간이 들었던 독서모임의 리스트 요청을 3초까지 줄이고, 접속자 수가 40%가 늘어났어도 서버 비용을 늘리지 않을 수 있었다. 그러나 상대적으로 빨라졌을 뿐 느린 편이라는 점은 변함이 없었다. 매 시즌 멤버 수가 30~40% 씩 증가하는 추세대로라면 다음 시즌에도 비슷한 비용을 유지할 수 있을 거란 보장 또한 없었다.여기서 더 개선하려면 DB 구조를 변경하고, 수많은 코드를 갈아엎어야 했다. 필요하다면 하면 되는 일이었지만 기존의 아키텍처인 레일즈 웹 애플리케이션을 유지한다면 당장의 퍼포먼스를 개선하더라도 언제까지 높은 퍼포먼스를 유지할 수 있을지 의문이었다. 성장에 따라 요구되는 시스템들을 다 지원해줄 수 있을지도 미지수였다. 언젠가 아키텍처를 변경해야 한다면 최대한 빠른 시일인 지금 하는 것이 효율적이라 판단했다.Heroku에서 관리하던 서버를 AWS의 EC2로 변경하면서 DB 또한 PostgresSQL에서 AWS 의 DynamoDB로 이전했다. RubyOnRails를 사용하여 단일 웹 애플리케이션으로 구현했던 홈페이지를 Typescript를 기반으로 프론트엔드와 백엔드를 나눴다. React로 사용하여 웹사이트를 구현하였고, Node.js로 GraphQL을 적용하여 서버를 구현하였다.덕분에 월 100만 원가량이 들던 비용을 월 30만 원까지 낮출 수 있었다. 속도는 이전보다는 빨라졌으나 기대만큼 빨라지지는 않아 캐싱 등을 적용하여 차츰 줄여나가고 있다. 변경한 현재 아키텍처로는 트래픽이 늘어나더라도 이전처럼 비용을 배로 늘리지 않아도 되었으며, 다양한 방법으로 속도를 개선하는 작업도 시도해 볼 수 있게 되었다.2. 기술 부채기술 부채가 쌓인 모습 (...)이미지 출처: 스마트스터디앞서 말했던 것처럼 기존 홈페이지는 여러 프리랜서 개발자들이 거쳐간 터라 뻔하게도 기술 부채가 쌓였다. 홈페이지와 관련된 문서는 없고, 크루들은 사용하는 기능들을 부분적으로만 알고 있었다. 그런 상황에서 몇 명의 크루들이 퇴사와 입사를 거치니 그나마 구전으로라도 유지되던 홈페이지 정보가 점점 사라졌다.홈페이지에 대해 궁금한 점이 생기면 직접 코드를 뒤적이며 파악해보는 수밖에 없었다. 그래서 모든 크루들이 유일한 개발자인 나에게 물어보는 것 말고는 홈페이지에 대해 알 수 있는 다른 방도가 없었다. 이 외에도 새로운 기능을 구현했더니 미처 파악하지 못한 곳에서 버그가 터진다거나, 안 쓰는 줄 알고 삭제한 코드가 사실 어디선가 제기능을 하고 있거나 하는 때도 잦았다.이런 기술 부채를 청산하려면 1) 대부분의 기능들을 파악하고 있는 담당자가 있고 2) 지원하는 기능들을 잘 정리한 문서가 필요했다. 1번은 직접 처음부터 리라이팅을 진행했으니 자연스레 해결되었으나, 다른 크루들도 많은 기능들에 대해 파악하고 있으면 더 효율적일 거라 생각했다. 그래서 새로 구현되는 기능이나 변경 사항에 대해서 매주 주간 회의 때 공유를 하고 있으며, 배포를 할 때마다 실시간으로 에버노트와 슬랙의 배포 노트 채널을 통해 배포 내용을 공유하고 있다. 이전에도 하고 있었으나 더 잘, 자주, 자세히 해야겠다고 새삼 깨달았고 노력 중에 있다.2번을 위해서는 홈페이지 기능 설명에 대한 문서를 작성하기 시작했다. 아직 가장 효율적인 포맷이 무엇인지는 찾지 못해서 방황하고 있지만 최대한 쉽고 자세하게 쓰는 방향으로 진행 중이다.사랑과 따뜻함이 넘치는 우리 크루들 3. 복잡하고 이유 없는 UI기존의 홈페이지는 의외로(?) 다양한 기능들이 있었지만 유저들이 모르거나 사용하지 않는 경우가 많았다. 대부분의 기능들과 인터페이스들이 중요도에 대한 고민 없이 '있으면 좋을 것 같다'는 이유로 덕지덕지 추가되었다. 게시판이나 다이어리 같은 메뉴들은 사용률이 채 5%가 안되지만 상단 메뉴에 자리 잡고 있었고, 북클럽 리스트의 페이지에는 딱 한 번만 읽으면 되는 설명글이 화면의 반을 차지하고 있었다.멤버들이 트레바리에서 가장 활발하게 누려줬으면 좋겠다고 생각하는 활동은 독서모임과 이벤트다. 내 클럽이 아닌 다른 다양한 클럽에도 참여해보고, 살면서 해보지 못한 경험들을 이벤트를 통해 체험해봤으면 좋겠다. 그런 고민으로 상단 메뉴에는 독서모임과 이벤트, 내 활동 정보를 볼 수 있는 마이페이지만 배치하였고 FAQ나 공지사항과 같은 자잘한 것들은 하단의 footer로 내리거나 일부 기능들을 임시적으로 지원하지 않기로 했다.리라이팅 전리라이팅 후직관적인 UI는 파트너 어드민에서도 절실하게 필요했다. 기존의 어드민 UI는 따로 교육이 필요할 정도로 복잡했기 때문이었다. 한 명의 파트너에게 자신이 관리하는 클럽 외의 모든 클럽 정보가 노출되었다. 클럽 정보에서도 봐야 할 정보와 보지 않아도 될 정보가 혼재되어 보이고 있었다. 파트너의 수는 점점 늘어나는데 그때마다 홈페이지까지 교육까지 따로 해야 하는 것은 리소스가 많이 드는 일이었다.파트너가 자신의 모임을 이끌기 위해 정말 필요한 일에만 집중할 수 있도록 신경 써서 구현했다. 모임에 참석하는 멤버 리스트, 모임에서 읽을 책과 발제문 등을 등록하고 수정하는 페이지, 출석 체크를 할 수 있는 기능만으로 구성했다. 항시 봐야 하는 매뉴얼과 FAQ는 따로 메뉴로 빼두었다.파트너 어드민의 모임 정보 설정 페이지 리라이팅 전과 후4. 데이터로 소통하는 회사트레바리는 점점 데이터로 소통하는 회사가 되고 싶다. 어떤 유저가 어디에서 불편을 겪고, 어떤 부분을 좋아하는지 알고 싶다. 사람들이 독서모임에 만족하면 홈페이지에서 어떻게 활동하는지, 혹여 만족하지 않았다면 그때는 또 어떻게 활동하는지 궁금하다. GA와 A/B 테스트 등의 방법들을 통해 데이터를 보며 이를 파악하고 싶다.기존 홈페이지는 전통적인 페이지 단위로 돌아가는 레일즈 웹 애플리케이션이었으므로 따로 제이쿼리 등을 사용해야지만 이를 구현할 수 있었다. 그래서 페이지 단위의 웹을 벗어나 React를 활용한 컴포넌트 단위의 웹 사이트를 구축했다. 장기적으로 계획적이고 세밀한 트래킹이 가능하도록 기반을 닦았다.또 기존의 홈페이지에서는 유저에게 오류 제보를 받아도 이를 확인해보는 것이 어려웠다. 그래서 지금의 시스템에는 Apollo engine과 Cloud watch를 이용하여 여러 로그들을 트래킹 하기 시작했다.리라이팅 런칭 2주 차,아쉬웠던 점들리라이팅 한 홈페이지를 런칭한 지 2주일이 지났다. 런칭 후에 한참을 정신없이 보내다가 이제야 조금 숨을 돌릴 수 있게 되어 이 글도 쓰기 시작했다. 런칭만 하면 마음이 편해질 거라 예상했는데 막상 다가오니 그렇지도 않았다. 더 바쁘고 정신없던 것은 물론이요, 아쉬운 점들만 눈에 밟혀서 마음이 무거웠다. 잘한 것보다 아쉬웠던 점들이 나를 더 성장하게 만들어 줄 것이라는 생각으로 스스로를 위로하여 어떤 것들이 아쉬운지도 정리해보았다.1. 트래픽이 몰리는 피크타임에 대한 대비 미흡배달의 민족이 식사 시간마다 트래픽이 몰리는 피크타임이 존재하듯, 트레바리도 독후감 마감 시간이라는 피크타임이 존재했다. 유저들이 모든 시간 대에 일정하게 접속하는 하는 것이 아닌 특정 시간에 몰아서 접속하는 것을 고려하여 그때의 속도를 잘 잡았어야 했다. 이를 미리 고려하여 캐시와 같은 여러 대비책들을 세워두었다면 유저들이 느린 홈페이지가 주는 불편을 덜 겪었을 거라고 생각한다.2. 치밀하지 못한 안내런칭 직후 오는 많은 문의들이 실제 오류가 아닌 제대로 된 안내가 없어 오류로 인지하는 경우였다. 예를 들어 기존에는 있었으나 사라진 주소와 같은 404 페이지 접근 시에는 안내 후 메인 페이지로 보내버리거나 하는 안내가 있었으면 많은 문의들을 대응하지 않아도 됐을 것이다.3. 운영 크루 업무 이해도 낮음리라이팅을 할 때 다른 크루들과 커뮤니케이션을 하는 일에 많은 리소스를 쏟지 않았었다. 다른 크루들의 업무에 대해 꽤 잘 이해하고 있다고 생각했기 때문이었다. 내가 생각하기에 필요할 것 같은 기능들만 어드민에 담았고, 그 결과로 크루들이 런칭 직후에 엄청난 불편과 수고로움을 겪게 만들었다.4. 조급함리라이팅을 진행하는 기간 동안 마음이 급해서 눈앞에 보이는 기능들을 빨리 쳐내는 것에 급급했다. 그러다 보니 각 기술에 대한 문서들을 꼼꼼하게 읽어내지 못해 놓친 부분이 많았다. 특히 한 번도 경험해본 적 없는 각종 브라우저와 브라우저 버전, PC와 모바일 대응 등에서 많이 놓쳤다. 평소 웹 표준 관련 문서를 잘 읽어두었다면 이런 실수는 덜하지 않았을까 생각했다. 또 틈틈이 작성했던 코드를 되돌아보고 개선하는 시간도 가졌어야 했는데 조급함 때문에 그러지 못했다. 이런 부분들은 개발자가 평소에 항시 주의해야 할 모습이라 생각했다.이번 리라이팅을 시작으로 트레바리가 온라인의 경험까지 멋진 서비스가 될 수 있기를 희망한다. 아직은 부족한 점이 많지만 사람들이 독서모임에 참석하기까지 겪는 온라인에서의 경험을 멋지게 만들고 싶다. 필요한 기능들을 적재적소에 구현하고, 말보다는 UI로 커뮤니케이션을 잘하는 개발자가 되기 위해 계속 노력할 것이다.지난 4개월 동안 참 힘든 시간도 많았다. 그럼에도 불구하고 크루들과 주변의 개발자분들에게 여러 도움을 받으면서 어려운 난관들을 헤쳐나갈 수 있었다. 홈페이지 변경이 아니어도 바쁜 일이 많은 시즌 시작 시기에 홈페이지 관련 문의가 쏟아졌다. 그런 상황에서 나를 탓하기보다는 오히려 걱정해주고 격려해주는 동료들이 있었다. 새삼스레 좋은 사람들과 함께하고 있다는 생각을 하며 일을 더 열심히, 잘 하는 것으로 보답하고 싶다고 생각했다.#트레바리 #기업문화 #조직문화 #CTO #스타트업CTO #CTO의일상 #인사이트
조회수 10028

Estimator: BLE를 사용한 Planning Poker 애플리케이션

1. Planning PokerStyleShare 개발팀에서는 스크럼을 활용하여 일을 진행하고 있습니다.1 스크럼에는 일감의 크기를 추정estimate하는 과정이 있는데요. 구성원들 모두가 일감에 대해 이해하고 일감의 크기가 어느정도인지 함께 논의하여 합의에 이르는 과정입니다. 스프린트 회의에서 일감을 등록한 사람(리포터)이 일감에 대해 설명하고 나서 전체 구성원들이 일감의 크기를 추정하는데, 이 때 사용하는 것이 바로 Planning Poker입니다.Planning Poker는 0.5부터 시작해서 1, 3, 5, 8, 13, 20, … 100과 같이 피보나치 수열로 증가하는 숫자를 가진 카드 덱입니다. 리포터의 설명이 끝난 뒤 스크럼 마스터가 하나, 둘, 셋을 외치면 각자 생각한 일감의 크기에 맞는 카드를 꺼내고, 스크럼 마스터는 구성원들의 추정치가 최대한 가까워지도록 부가설명이나 질문을 유도합니다.2▲ Planning Poker는 이렇게 생겼다. (출처: Control Group 블로그)하지만 개발팀이 커지면서 불편함이 생기기 시작했습니다. 회의에 참여하는 인원이 7-8명씩 되다 보니, 각자가 어떤 카드를 들고있는지 한눈에 보기가 어려워진 것입니다. StyleShare에서 자칭 아이디어 뱅크 역할을 담당하고 있는 저는 획기적인 방법이 필요하다고 생각했고, 굳이 카드를 꺼내들지 않아도 각자가 무슨 카드를 선택했는지를 쉽게 볼 수 있는 애플리케이션을 만들기로 결심했습니다.2. BLE (Bluetooth Low Energy)불편함을 덜기 위한 애플리케이션이므로, 사용자 경험이 굉장히 직관적이고 단순해야 했습니다. N:N 통신이 가능해야하고, 사용자를 귀찮게 하는 페어링Pairing이나 네트워크 접속 과정이 없어야 했습니다. 한마디로, 카드를 꺼내들고 눈으로 확인하는 것보다 더 편한 무언가를 만들어야 했습니다!처음에는 근거리 무선 통신을 위한 기술로 스타벅스에서 사이렌 오더 개발에 사용한 고주파 인식 기술을 생각했습니다.3 각자의 기기에서 선택한 카드에 맞는 소리를 내보내고, 다른 기기에서는 고주파를 읽겠다는 것이었는데요. Soundlly(구 aircast.me)와 같은 상업용 SDK를 쓰지 않는 이상, 사운드 프로그래밍을 한 번도 해본 적 없는 저에게는 데이터가 실린 고주파를 만드는 것부터 소리를 인식해서 데이터를 읽어내는 과정이 마치 화성에서 감자 키우는 이야기처럼 들렸습니다.그러다 문득 생각난 것이 바로 비콘Beacon입니다. 언젠가 소비자가 오프라인 매장에 방문하면 BLE를 이용해서 매장 위치를 파악하는 기술이 있다는 이야기를 들은 적이 있었습니다. 찾아보니 시중에 나와있는 대부분의 모바일 기기에서는 BLE를 위한 최소 조건인 블루투스 4.0을 지원했고, 페어링이나 네트워크 접속 과정도 불필요했습니다. 무엇보다, 화성에서 감자 키우는 것보다는 쉬워보였습니다.3. Swift로 BLE 개발하기그래서 BLE를 사용해서 개발하기로 했습니다. 컨셉은 간단했습니다. 내가 선택한 카드를 브로드캐스팅하고, 다른 사람들이 선택한 카드를 내 모바일 기기에 보여주면 되는 것이었습니다. BLE를 사용하면 정보를 브로드캐스팅할 수 있고, 다른 기기에서 브로드캐스팅하는 정보를 읽을 수 있습니다.BLE에서 데이터를 브로드캐스팅하는 것을 Advertising이라고 합니다. 정보를 advertising하는 주체는 Peripheral이고, advertising되는 정보를 스캔하여 데이터를 읽어들이는 주체는 Central이라고 합니다. Peripheral에서 정보를 advertising할 때에는 특정한 정보를 실어나를 수 있는데요. 이를 Advertising Data Payload라고 합니다. 이 정보에 카드 숫자와 이름을 실어서 전송하면 될 것 같습니다.BLE를 구현하기 위해서, iOS에서는 SDK에 기본적으로 포함돼있는 CoreBluetooth 프레임워크를 사용하면 손쉽게 개발이 가능합니다. CBPeripheralManager 클래스와 CBCentralManager 클래스를 쓰면 되는데요. BLE를 이용하여 제 이름 석자를 advertising하는 코드는 다음과 같습니다.Peripheralimport CoreBluetooth let serviceUUID = CBUUID(string: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") let service = CBMutableService(type: serviceUUID, primary: true) /// 1. `CBPeripheralManager`를 초기화하고, self.peripheral = CBPeripheralManager(delegate: self, queue: nil) /// 2. 사용가능한 상태가 되면 특정 UUID를 가진 서비스를 추가한 뒤에 func peripheralManagerDidUpdateState(peripheral: CBPeripheralManager) { if peripheral.state == .PoweredOn { self.peripheral.addService(service) } } /// 3. 원하는 정보를 advertising합니다. func peripheralManager(peripheral: CBPeripheralManager, didAddService service: CBService, error: NSError?) { self.peripheral.startAdvertising([ CBAdvertisementDataLocalNameKey: "전수열", CBAdvertisementDataServiceUUIDsKey: [serviceUUID], ]) } 참고로, UUID는 커맨드라인 명령어를 통해 쉽게 만들 수 있습니다.$ uuidgen XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 마찬가지로, Peripheral에서 advertising하는 정보를 스캔하는 Central 코드는 다음과 같이 작성할 수 있습니다. UUID는 Peripheral에서 advertising에 사용한 UUID와 동일해야합니다.Centralimport CoreBluetooth let serviceUUID = CBUUID(string: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") let service = CBMutableService(type: serviceUUID, primary: true) /// 1. `CBCentralManager`를 초기화하고, self.central = CBCentralManager(delegate: self, queue: nil) /// 2. 사용가능한 상태가 되면 특정 UUID를 가진 서비스를 스캔합니다. func centralManagerDidUpdateState(central: CBCentralManager) { if central.state == .PoweredOn { // 이미 한 번 스캔된 정보라도 계속 스캔합니다. let options = [CBCentralManagerScanOptionAllowDuplicatesKey: true] self.central.scanForPeripheralsWithServices([serviceUUID], options: options) } } /// 3. Peripheral이 스캔되면 이 메서드가 호출됩니다. func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) { print(advertisementData[CBAdvertisementDataLocalNameKey]) // "전수열" } 스캔을 시작할 때 CBCentralManagerScanOptionAllowDuplicatesKey 옵션을 true로 설정해서 한 번 스캔된 정보라도 중복으로 계속 스캔하도록 합니다.4. 원하는 정보를 실어나르기CBPeripheralManager을 사용하여 advertising을 할 때에는 Advertising Data Payload를 포함시킬 수 있는데, 이 정보 중 개발자가 원하는 값을 넣을 수 있는 곳은 CBAdvertisementDataLocalNameKey밖에 없습니다. 그마저도 길이가 제한돼있기 때문에, 패킷을 효율적으로 사용하기 위해서는 정보를 저장하는 프로토콜을 직접 정의해야 합니다.우선, 카드에 대한 정의는 enum을 사용해서 작성했습니다. 0부터 0xFF까지의 숫자를 가지도록 정의했습니다.public enum Card: Int { case Zero = 0 case Half = 127 case One = 1 case Two = 2 case Three = 3 case Five = 5 case Eight = 8 case Thirteen = 13 case Twenty = 20 case Fourty = 40 case Hundred = 100 case QuestionMark = 0xFD case Coffee = 0xFE case None = 0xFF } 그리고 제가 정의한 패킷의 프로토콜은 다음과 같습니다.영역길이예시설명Version200프로토콜 버전 (00~FF)Channel201BLE 커버리지 내에서 회의하는 팀이 여럿일 수 있으니, 채널로 구분합니다. (00~FF)Card2FE카드의 16진수 값 (00~FF)Name12전수열사용자 이름 (UTF-8 기준 한글 4글자)이렇게 하면 총 18바이트 내에서 필요한 정보를 모두 전송할 수 있습니다. 이제 이 "00", "01", "FE", "전수열" 값을 직렬화해서 CBAdvertisementDataLocalNameKey로 advertising하면 됩니다.Peripheralself.peripheral.startAdvertising([ CBAdvertisementDataLocalNameKey: "0001FE전수열", CBAdvertisementDataServiceUUIDsKey: [serviceUUID], ]) 그리고, Central에서 정보를 스캔할 때에는 이 값을 각 영역의 길이에 맞게 끊어서 읽을 수 있습니다.5. 마치며비록 적은 양의 정보지만, BLE를 사용해서 실시간으로 근거리 통신을 할 수 있게 되었습니다. 이제 남은 것은 카드를 선택할 수 있는 화면과, 다른 사용자가 선택한 카드를 화면에 보여주는 인터페이스입니다. UI 개발은 본 포스트에서 중점적으로 다루고자 하는 주제와는 조금 벗어난 이야기가 될 것 같아, 오픈소스로 공개된 코드로 대신하려고 합니다. 소스코드는 GitHub에서 볼 수 있으며, Estimator는 앱스토어에서 받아보실 수 있습니다.6. 참고 자료BLE(BLUETOOTH LOW ENERGY) 이해하기 - Hard Copy World스타일쉐어의 스크럼이 지나온 길 포스트에 보다 자세히 설명되어 있습니다. ↩구성원들의 추정치에 차이가 난다는 것은 해당 일감에 대해 서로가 이해하고 있는 정도가 다르기 때문입니다. 스크럼 마스터는 구성원들이 일감에 대해 모두 비슷한 생각을 가지도록 커뮤니케이션을 유도해야합니다. ↩http://www.bloter.net/archives/226643 ↩#스타일쉐어 #개발 #개발팀 #개발자 #인사이트
조회수 6137

개발자 직군 파헤치기 1 | 프론트(Front), 백(Back), 풀스택(Full-Stack) 개발자

수많은 개발자 직군들개발자가 되기 위해서는 프로그래밍 언어만 배운다고 끝나는 것이 아닙니다. 자신이 배운 언어를 가지고 어떤 개발자가 될지 고민도 해야합니다. 실제로, 많은 분들이 내가 어떤 분야의 개발자가 되야 할지 고민을 많이 합니다. 그런데 이 고민에 앞서 어떤 개발자의 종류가 있고 직군이 있는지를 살펴보아야 합니다. 그래서 이번에 준비한 연재는 개발자 직군 파헤치기 시리즈입니다. 우리가 개발의 한 직군이라고 부를 수 있는 것들 중 관심이 많이 가는 직군들을 위주로 알아볼 것입니다. 일하는 분야에 대한 직군(게임 개발자)에 대한 이야기도 할 것이고, 지금 이야기할 프론트-엔드, 백-엔드처럼 개발의 영역에 대한 이야기도 할 것입니다. 다양한 관점에서 개발자의 영역들을 살펴볼 것입니다. 자, 그럼 지금부터 시작해 보죠!(※이 글은 유다시티 3Web Dev Careers Decoded: Front-End vs Back-End vs Full Stack을 번역한 것입니다.Front, Back and Full Stack우리가 매일같이 인터넷을 사용하는 과정을 떠올려봅시다. 새 브라우저 탭을 열고 URL을 입력 한 다음 Enter 키를 누르면 그 즉시 사이트가 로딩이 됩니다. 깔끔한 레이아웃, 잘 구성된 페이지, 그리고 화려한 시각적 효과들은 때로 감탄을 자아내죠.순식간에 일어난 이 모든 경험을 담당하는 사람들이 바로 웹 개발자들입니다.2018 년 4 월 현재 인터넷에 있는 페이지의 수는 45 억개가 넘습니다.  지금 이 순간에도 그 수는 계속 늘어나고 있습니다. 누군가 안정적인 직업을 찾고 싶다면 웹 사이트의 코딩, 설계, 분석 및 유지 관리를 담당하는 사람들, 곧 웹 개발자들을 찾아야 할 것입니다.웹 사이트는 이제 모든 비즈니스가 경쟁력을 유지하는 데 중요한 구성 요소입니다. 웹 개발의 트렌드와개발의 패러다임이 거의 매 시즌마다 바뀌는 상황에서, 개발자에 대한 수요는 부족함이 없습니다.그러나 웹 개발자의 범위는 매우 넓기 때문에 정확히 어떤 종류의 웹 개발자 채용 공고를 찾아보아야 하고, 그러한 개발자가 되기 위해 무슨 교육을 받아야 하는지를 판단하기는 쉽지 않습니다.  만약 구직 사이트를 둘러 보거나 온라인 강좌를 알아본 경험이 있다면, 프론트엔드, 백엔드, 그리고 풀스택 개발자라는 용어를 한번쯤은 들어보았을 것입니다.HTML, 자바 스크립트 또는 약간의 파이썬을 사용해 본 적이 있지만, 막상 개발자 직군에 대해서는 막막했다면 이 글이 매우 유용할 것입니다. Photo by Annie Spratt on UnsplashFront-End Developer프론트엔드는 웹사이트 중 사용자가 직접 상호작용을 하게 되는 부분입니다. 글꼴 부터 색상, 드롭 다운 메뉴 및 슬라이더에 이르기까지 인터넷에서 보게 되는 모든 것들은 브라우저의 제어를 받는 HTML, CSS 및 JavaScript의 조합입니다.SKILLS AND TOOLS프런트엔드 개발자는 웹 사이트에서 사용자가 직접 경험하는 부분과 그 경험의 아키텍처를 담당합니다. 이를 위해 프론트엔드 개발자는 HTML, CSS, Javascript 활용에 능숙해야합니다. 언어를 잘 다루는 것 외에도 프런트 엔드 개발자는 사용자의 도구에 따라 유연한 방식으로 컨텐츠를 보여줄 수 있게 하는 Bootstrap, Foundation, Backbone, AngularJS, EmberJS와 같은 프레임워크에 익숙해야합니다. 또한 jQuery, LESS와 같은 라이브러리를 사용할 수 있다면 더욱 유용하고 효율적인 코드를 작성할 수 있게됩니다. 프론트엔드 개발자를 채용할 때에는 Ajax 사용 경험을 요구하는 경우도 많습니다. 백그라운드에서 서버 데이터를 가져와 페이지를 동적으로 만드는 Javascript를 활용하는 데 있어서 Ajax는 보편적으로 사용되는 기술이기 때문입니다.프런트 엔드 개발자는 백엔드 개발자가 만든 집의내부 디자인을 담당합니다.프론트엔드 개발자는 이러한 기술을 사용하면서도, 목업(mockup) 혹은 와이어프레임(wireframe)의 개발에서 전달의 단계까지 디자이너 또는 사용자 경험 분석가와 긴밀히 협력합니다. 실력 있는 프론트엔드 개발자는 사용자 경험에서의 문제를 정확하게 발견하고, 디자인을 수정에 관한 조언과 문제 해결을 위한 코드를 제공합니다. 또한 목표와 필요, 기회들을 정확히 이해하고 수행하기 위해서는 다른 팀들과 유연하게 협력하는 능력이 중요합니다. 이처럼 프론트엔드 개발자의 작업은 여러 영역에 대한 책임을 동시에 감당하는 일이면서도 그만큼의 보람이 따라오는 것이기도 합니다. 8 년차 프론트엔드 개발자 인 Mikey Ilagan은 아래와 같은 이야기를 합니다.저는 기술적인 사람이면서도 시각적인 사람입니다.그래서 저는 자연스럽게 목업과 코드를 동시에 다루며 사람들이디지털 플랫폼과 상호작용하는 방식을 만들어 낼 수 있게 되었습니다.-Mikey Ilagan-종합해보자면, 프론트엔드 개발자는 백엔드 개발자가 만든 집의 내부 설계를 담당합니다. 집을 장식하는취향과 스타일은 집주인이 결정합니다. Apptix의 제품 마케팅 디렉터인 Greg Matranga는 "프론트엔드에서 작업하는 개발자는 자신의 창의성을 실질적으로 작업에 반영할 수 있기 때문에 때로는 자신이 하는 일에 대해 더욱 흥분한다"며 자신이 관리하는 프론트엔드 및 백엔드 개발자 팀 모두에게 말하기도 했습니다.HOW IT TRANSLATES지금 이 블로그에서 보고있는 모든 것은 프론트엔드 개발자의 손을 타지 않은 곳이 없습니다. 물론 로고와 그래픽은 디자이너가 만들고, 사진은 자신 작가가 찍었으며, 텍스트는 지금 글을 쓰고 있는 제가 작성합니다. 그러나 이 모든 조각들을 모아 웹으로 만들고, 각 페이지마다 사용자가 경험할 것을 설계한 것은 프론트엔드 개발자입니다.Photo by Lee Campbell on UnsplashBack-End Developer프론트엔드에서 일어나는 일을 이해했다면, 그것만으로는 웹사이트가 완성되지 않는 다는 것 역시 이해했을 것입니다. 그렇다면 프론트엔드 자체를 가능하게 만드는 것은 무엇일까? 데이터는 어디에 저장되는 것일까? 바로 백엔드입니다. 웹 사이트의 백엔드는 서버, 응용 프로그램 및 데이터베이스로 구성됩니다. 백 엔드 개발자는 이러한 구성요소들이 작동할 수 있게하는 기술을 만들고 유지하는 일을 합니다. 이러한 작업을 통해 비로소 사용자에게 보여지는 측면이 존재할 수 있게 됩니다.SKILLS AND TOOLS서버, 응용 프로그램, 데이터베이스가 서로 통신 할 수 있도록 만들기 위해 백엔드 개발자는 PHP, Ruby, Python, Java, .Net과 같은 서버 측 언어를 활용하여 응용 프로그램을 만듭니다. 또한 데이터를 검색, 저장 또는 변경하고 이를 프론트엔드 코드로 사용자에게 다시 제공하기 위해서는 MySQL, Oracle 및 SQL Server를 사용합니다. 백엔드 개발자에 관한 채용 공고는 그 외에도 1) Zend, Symfony 및 CakePHP와 같은 PHP 프레임 워크에 대한 경험, 2) SVN, CVS 또는 Git과 같은 버전 제어 소프트웨어 사용 경험, 3) 개발 및 배포 시스템으로서의 Linux 사용 경험을 요구하는 경우가 있습니다.백엔드 개발자는 이러한 도구를 사용하여 깔끔하고 모듈화가 가능한 코드로 웹 응용 프로그램을 만듭니다. 그러나 이와 같이 코드를 작성하기 전에 백엔드 개발자는 비즈니스 이해 관계자와 소통하면서 구체적인 요청 사항을 파악해야 합니다. 그런 다음 이를 기술적 내용으로 변환하여 기술 설계를 위한 가장 효과적이고 효율적인 솔루션을 제시할 수 있어야 합니다.나는 데이터를 다루는 것을 좋아하기 때문에항상 백엔드 개발을 선호 해 왔습니다.-JP Toto-오랫동안 백 엔드 개발자였던 JP Toto는 현재 와일 비트의 소프트웨어 개발자입니다. 그는 "최근 공개 및 비공개 API는 모바일, 웹 사이트를 포함한 여러 시스템간에 데이터를 교환하는 데 필수적인 부분이되었으며, 사람들이 유용하다고 생각하는 API를 만드는 것은 그에게 큰 만족감을 주는 일 중에 하나"라고 말한다.HOW IT TRANSLATES여러분이 코드스테이츠의 웹사이트를 찾아 들어오는 과정을 봅시다.  웹사이트의 서버는 여러분의 컴퓨터 또는 모바일로 정보를 보내고, 그 정보는 코드스테이츠 소개가 담긴 페이지로 보여집니다. 이 프로세스는 백엔드 개발자의 작업 결과입니다. 또한 회원가입을 할 때 저장되는 개인정보, 그리고 로그인을 할 때마다 각 계정의 정보가 불러와지는 과정 역시 백엔드 개발자 덕분입니다.Full Stack Developer프론트엔드 개발과 백엔드 개발 간에는 흑백 구분이 없는 경우가 종종 있습니다. 프론트엔드 개발자는 종종 추가 백엔드 기술을 습득해야하며 그 반대의 경우도 있습니다. 개발자는 여러 분야를 넘나들어야 할 때가 많아 종종 제너럴리스트가 되어야 합니다.  풀스택 개발자라는 역할은 7년 전 페이스북의 엔지니어링 부서에서 대중화되었습니다. 풀스택 개발자라는 호칭은 프론트엔드와 백엔드 모두에서 교차적으로 작업 할 수 있는 역할을 지칭하는 것에서 시작했습니다. 말 그대로 풀패키지(full package)를 제공하는 개발자라는 뜻입니다.서버와 클라이언트 측 모두에서 작업할 있는 전문성은더 많은 기회를 열어줍니다.-Federico Ulfo-Grovo의 풀스택 개발자인 Federico Ulfo는 이를 음식에 비유에 이야기합니다. "요리와 베이킹 중에 하나를 잘할 수는 있습니다. 그러나 두 가지를 모두 마스터하는 데에는 시간과 경험이 필요합니다. 마스터 한다는 것은 단지 레시피를 따라서 만드는 것을 말하는 게 아닙니다. 그건 누구든지 할 수 있습니다. 마스터 한다는 것은 직접 재료를 고르고 자신의 레시피로 훌륭한 음식을 만들어내는 것입니다."Photo by freestocks.org on UnsplashSKILLS AND TOOLS풀스택 개발자는 백엔드 개발자와 마찬가지로 웹 프로그래밍의 서버 측에서 작업하지만, 이와 동시에 사용자 측에서 콘텐츠가 보여지는 방법에 관해 프론트엔드의 언어로 능숙하게 소통할 수 있습니다. 아래의 이미지는 풀스택 개발이 얼마나 복잡해지고 있는지를 체감하게 해줍니다. 몇년 전까지만 해도 3-4가지의 기술의 종합으로 표현될 수 있었던 풀스택은 현재 7개의 기술이 종합된 형태로 훨씬 복잡해졌음을 알 수 있습니다.출처: Techrunch출처: Techrunch구체적인 기술의 종류는 프로젝트나 클라이언트에 따라 달라질 수 있지만, 풀스택 개발자는 기술의 종류와 상관없이 Linux 서버의 설정과 구성, 서버 측 API 작성, 클라이언트 측 JavaScript, 디자인을 맡는 CSS 등 웹이 작동하는 모든 차원에 있어서 해박해야합니다.풀스택 개발자는 이러한 기술들을 사용하여 클라이언트 측과 서버 측이 담당할 영역을 즉각적으로 구분해내고, 다양한 솔루션들의 장단점을 명확히할 수 있어야 합니다. HOW IT TRANSLATES풀스택 개발자는 로딩 시간부터 레이아웃, 그리고 사용자와의 상호작용성과 구조적 토대에 이르기까지이 게시물이 주는 경험의 전체적인 흐름을 책임집니다.The Bottom Line웹 개발에는 많은 면모가 있습니다. 그러나 당신이 어떠한 개발자가 되고 싶든지 디테일에 주의를 기울이는 능력, 빠르게 학습할 수있는 능력, 문제를 효율적으로 해결하는 능력, 그리고 커뮤니케이션 능력은 당신을 돋보이게 만들것입니다. 다행히 웹 개발 분야에서 경력을 쌓기 위해 이보다 더 좋은 시기는 없습니다. 웹 개발자의 고용은 2014 년에서 2024 년까지 10 년 동안 27 % 증가 할 것으로 예상되며 이는 모든 직종의 평균보다 빠릅니다. 지금까지 Front, Back, Full Stack 개발자에 대해서 알아보았습니다.다음 포스팅은 개발의 한 축을 담당하고 있기는 하지만 일반 개발 분와야는 다른 '게임 개발자'에 대해서 알아보도록 하겠습니다.

기업문화 엿볼 때, 더팀스

로그인

/