스토리 홈

인터뷰

피드

뉴스

개발에 관심있다면 꼭 읽어야하는 글
조회수 3338

eventlet을 활용한 비동기 I/O 프로그래밍

안녕하세요. 스포카 크리에이터팀 문성원입니다. 현대적인 프로그래밍 환경에서 네트워크는 더는 특정 직군의 개발자만 접하는 분야가 아닙니다. 그런 만큼 대량의 요청을 네트워크를 통해 송수신하는 프로그램이 생각보다 성능이 나오지 않는 경우를 경험하신 분들도 많으실 겁니다. 물론 스포카 개발팀도 예외는 아니었습니다. 그래서 오늘은 저희의 이러한 경험과 그 해결책-eventlet을 통한 비동기 I/O(Asynchronous I/O)-에 대해 소개합니다.Why우선 스포카 개발팀에서 겪었던 문제부터 시작하죠. 얼마 전 페이스북(facebook)의 FQL(Facebook Query Language)를 통해 정보를 수집해서 이를 활용하는 기능을 작성해야 했습니다. 기존의 함수들은 필요할 때마다 FQL을 요청하는 방식이었고 당연히 이건 너무 느렸죠. 그래서 생각한 것이 “하루의 일정 시간마다 대량의 FQL 요청을 보내서 필요한 정보를 미리 갱신시켜놓자.”였습니다. 여기까진 좋았죠. 이때 제가 작성한 코드의 얼개를 살펴보면 대강 이렇습니다.# 페이스북 계정들을 가져와서 반복하면서for account in FacebookAccount.query:    account.update() #FQL을 보내자.view rawgistfile1.py hosted with ❤ by GitHub그런데 문제가 있었습니다. 기존의 FQL을 보내는 FacebookAccount.update()는 FQL요청이 완료될때까지 멈추고 기다립니다. 대부분의 FQL요청이 2, 3초 정도 걸린다고 했을 때 이러한 지연은 매우 치명적입니다. 대안이 필요했고 자연스레 떠오른 것이 서두에 소개한 비동기 I/O(Asynchronous I/O)였습니다.Asynchronous과거 일부 고급 서버 개발자만 알고 있는(혹은 알아야 하는) 기술로 치부되던 ‘비동기(Asynchronous)’란 개념은 2000년대 들어 등장한 Ajax(Asynchronous JavaScript and XML)의 성공 이후 많은 개발자에게 강한 인상을 줬습니다. 사용자는 HTTP 요청이 끝날 때까지 멈추어 있는 하얀 화면으로부터 해방되었고, 다양하고 많은 요청과 응답들이 자연스럽게 서버로 흘러들어 가서 나왔습니다. 개발자들의 이러한 경험과 통찰은 이후 node.js와 같은 플랫폼의 등장에도 많은 영향을 끼쳤습니다.다시 문제로 돌아가죠. 그렇다면 이러한 비동기에 관한 개념은 위의 상황을 어떻게 해결할 수 있을까요? 문제의 원인부터 다시 살펴봅시다. 2, 3초 정도씩 걸리는 FQL 요청이 문제일까요? 물론 요청이 매우 빨리 처리된다면 별도의 처리 없이도 저 코드는 문제없이 동작합니다. 하지만 현실적으로 이런 I/O의 속도를 빠르게 하는데에는 물리적으로 한계가 있습니다. 오히려 여기에서 주목해야 할 점은 ‘2, 3초’ 보다 ‘기다린다’라는 점입니다. FacebookAccount.update() 같은 경우, I/O가 처리되는 동안 CPU는 하던 일을 멈추고 문자 그대로 기다리게 됩니다. 만약 CPU가 멈추지 않고 다른 요청을 보낸다면 어떨까요? 이렇게 말이죠.비동기만으로는 부족하다?이러한 아이디어는 그동안 많은 개발자가 대량의 I/O를 다루는 올바른 방식으로 여겨왔습니다. 하지만 보통 이러한 비동기 I/O를 통한 구현은 동기식 I/O와는 좀 다른 형태를 띠게 됩니다. 이렇게 말이죠.# http://docs.python.org/library/asyncore.html#asyncore-example-basic-http-clientimport asyncore, socketclass HTTPClient(asyncore.dispatcher):    def __init__(self, host, path):        asyncore.dispatcher.__init__(self)        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)        self.connect( (host, 80) )        self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path    def handle_connect(self):        pass    def handle_close(self):        self.close()    def handle_read(self):        print self.recv(8192)    def writable(self):        return (len(self.buffer) > 0)    def handle_write(self):        sent = self.send(self.buffer)        self.buffer = self.buffer[sent:]client = HTTPClient('www.python.org', '/')asyncore.loop()view rawgistfile1.py hosted with ❤ by GitHub불행하게도, 이 경우 기존에 사용하던 urllib2대신 HTTP 요청을 처리하는 핸들러를 이처럼 재작성 해야합니다. 거기에 FacebookAccount.update()의 호출 방식마저 바뀔 수 있죠. 더군다나 콜백(Callback) 투성이의 코드는 유지보수가 쉬어 보이지도 않습니다. 여러모로 손이 많이 가는 상황이죠.결국, 기존 코드를 최대한 수정하지 않으면서도, 어느 정도 성능은 보장되는 그런 해결책이 필요했습니다. 그런 해결책이 있을까요? 다행히도 그렇습니다.What저희가 해결책으로 택한 eventlet은 Python(정확히는 CPython)에서 코루틴(Coroutine)을 지원하기 위해 만들어진 greenlet을 이용해 작성된 네트워크 관련 라이브러리입니다. 생소한 용어가 갑자기 튀어나와서 놀라셨을지도 모르니 우선 eventlet에 대해 설명하기 전에 앞에 나온 용어들을 찬찬히 한번 살펴보죠.코루틴과 greenlet먼저 코루틴(Coroutine)부터 살펴보죠. 전산학도라면 누구나 그 이름을 한번은 들어봤을 도널드 카누쓰(Donald Knuth)는 자신의 저서 The Art of Computer Programming에서 코루틴을 다음과 같이 설명합니다.Subroutines are special cases of more general program components, called “coroutines.” In contrast to the unsymmetric relationship between a main routine and a subroutine, there is complete symmetry between coroutines, which call on each other.코루틴은 우리가 잘 알고 있는 서브루틴(Subroutine)과 달리 진입점(Entry Point)이 여러 개일 수 있습니다. 쉽게 이야기하면 실행을 멈췄다가(Suspend) 재개(Resume)할 수 있다는 점인데요. 이 특성을 살리면 우리가 익히 아는 스레드(Thread)처럼 쓸 수 있게 됩니다. 다만 스레드와 달리 코루틴은 비선점적(Non-Preemptive)이기때문에 코드의 흐름을 전적으로 사용자가 제어할 수 있습니다.하지만 불행히도 모든 언어에서 이런 코루틴이 지원되진 않습니다. greenlet은 이런 코루틴을 CPython에서 지원하기 위해 작성된 라이브러리입니다.eventlet코루틴을 통해 스레드를 대체할 수 있다는 점에 주목한 사람들은 greenlet을 통해 유용한 네트워크 라이브러리를 만들어냈습니다. eventlet도 그 중 하나죠. 잠시 eventlet의 소갯글을 봅시다.Eventlet is a concurrent networking library for Python that allows you to change how you run your code, not how you write it.위에서 볼 수 있듯이 eventlet은 사용성에 중점을 두었습니다. 기존의 블로킹 I/O 스타일의 프로그래밍에 익숙한 개발자들도 쉽게 비동기 I/O의 장점을 얻을 수 있게끔 하는 게 목적이죠.특히 저희가 주목한 점은 eventlet의 멍키패치 기능입니다. 멍키패치는 본래 동적 언어에서 런타임에 코드를 고쳐서 별도의 파일 변경 없이 본래 소스의 기능을 변경하는 것을 말합니다. eventlet은 eventlet.monkey_patch 메서드를 통해 표준 라이브러리의 I/O 라이브러리를 논블러킹으로 동작하게끔 변경해서 코루틴에 적합하게 만듭니다.How앞서 소개한 eventlet.monkey_patch를 이용하면 실제로 고칠 부분은 정말로 적어집니다. 다음 코드가 eventlet을 이용해 변경한 전부입니다.import eventleteventlet.monkey_patch() #표준 라이브러리를 변환# 여러가지 import를 하고...pool = eventlet.GreenPool()# 페이스북 계정들을 가져와서 반복하면서for account in FacebookAccount.query:    # 코루틴들에게 떠넘기자.    pool.spawn_n(FacebookAccount.update, account)        pool.waitall()view rawgistfile1.py hosted with ❤ by GitHub정말 적죠? 조금만 구체적으로 살펴보죠. 우선 eventlet.monkey_patch는 socket이나 select등의 Python 표준 라이브러리를 eventlet.green 패키지안에 정의된 코루틴 친화적인 모듈들로 바꿔치기 합니다.# from eventlet/pathcer.pydef monkey_patch(**on):    """Globally patches certain system modules to be greenthread-friendly.    The keyword arguments afford some control over which modules are patched.    If no keyword arguments are supplied, all possible modules are patched.    If keywords are set to True, only the specified modules are patched.  E.g.,    ``monkey_patch(socket=True, select=True)`` patches only the select and     socket modules.  Most arguments patch the single module of the same name     (os, time, select).  The exceptions are socket, which also patches the ssl     module if present; and thread, which patches thread, threading, and Queue.    It's safe to call monkey_patch multiple times.    """        accepted_args = set(('os', 'select', 'socket',                          'thread', 'time', 'psycopg', 'MySQLdb'))    default_on = on.pop("all",None)    for k in on.iterkeys():        if k not in accepted_args:            raise TypeError("monkey_patch() got an unexpected "\                                "keyword argument %r" % k)    if default_on is None:        default_on = not (True in on.values())    for modname in accepted_args:        if modname == 'MySQLdb':            # MySQLdb is only on when explicitly patched for the moment            on.setdefault(modname, False)        on.setdefault(modname, default_on)            modules_to_patch = []    patched_thread = False    if on['os'] and not already_patched.get('os'):        modules_to_patch += _green_os_modules()        already_patched['os'] = True    if on['select'] and not already_patched.get('select'):        modules_to_patch += _green_select_modules()        already_patched['select'] = True    if on['socket'] and not already_patched.get('socket'):        modules_to_patch += _green_socket_modules()        already_patched['socket'] = True    if on['thread'] and not already_patched.get('thread'):        patched_thread = True        modules_to_patch += _green_thread_modules()        already_patched['thread'] = True    if on['time'] and not already_patched.get('time'):        modules_to_patch += _green_time_modules()        already_patched['time'] = True    if on.get('MySQLdb') and not already_patched.get('MySQLdb'):        modules_to_patch += _green_MySQLdb()        already_patched['MySQLdb'] = True    if on['psycopg'] and not already_patched.get('psycopg'):        try:            from eventlet.support import psycopg2_patcher            psycopg2_patcher.make_psycopg_green()            already_patched['psycopg'] = True        except ImportError:            # note that if we get an importerror from trying to            # monkeypatch psycopg, we will continually retry it            # whenever monkey_patch is called; this should not be a            # performance problem but it allows is_monkey_patched to            # tell us whether or not we succeeded            pass    imp.acquire_lock()    try:        for name, mod in modules_to_patch:            orig_mod = sys.modules.get(name)            if orig_mod is None:                orig_mod = __import__(name)            for attr_name in mod.__patched__:                patched_attr = getattr(mod, attr_name, None)                if patched_attr is not None:                    setattr(orig_mod, attr_name, patched_attr)        # hacks ahead; this is necessary to prevent a KeyError on program exit        if patched_thread:            _patch_main_thread(sys.modules['threading'])    finally:        imp.release_lock()view rawgistfile1.py hosted with ❤ by GitHub이렇게 바꿔치기된 eventlet.green안의 모듈들은 I/O에 의해 블럭되는 경우 다른 코루틴에 제어권을 넘기는 식으로 지연을 방지합니다.다른 대안들사실 이러한 목적으로 사용되는 라이브러리는 eventlet만 있는 것은 아닙니다. gevent는 eventlet에서 영향을 받았지만, libevent를 기반으로 하여 더욱 나은 성능과 성숙한 인터페이스를 갖추고 있습니다. 저희처럼 libevent의 설치에 제한이 있는 환경이 아니라면 이쪽을 살펴보셔도 좋습니다.만약 이벤트 주도적 프로그래밍(Event-Driven Programming)에 흥미가 있으신 분은 Twisted역시 좋은 대안이 될 수 있습니다.#스포카 #개발 #개발자 #인사이트 #꿀팁
조회수 1832

Mong 3.0과 프론트엔드개발자 쿤!, 반응형 웹에 도전하다!!

안녕하세요 크몽 개발팀입니다.작년 12월 크몽파티때 기억나시나요? 프론트엔드개발자인 저 쿤이 그날 반응형웹을 1~2월달까지 시전하겠다! 라고 호언장담했었는데요.. 저도 그때당시에는 무조건 해보자라는 생각으로 얘길했던건데.. 팀원들의 반응이...이랬었더랬죠... 그때의 저의 심정은 가슴이 바운스바운스 두근대~... 넵 그랬었습니다...하지만!!! 1월달에 잠시했던 공부와 2월달에 잠시얻은 잉여로움을 발판삼아 전부는 아니지만 메인페이지만 해내었습니다. 처음의 도전은 험난하디 험난했습니다.여러 문서들을 보던가운데 반응형웹을 잘 소화하고 계시는 기업블로그의 포스팅을보게 되었는데요..출처: S사 기업블로그한마디로 이해가 쏙쏙되는 포스팅이었습니다.여기에 감명받은 저 쿤은 바로 연습에 들어갔더랬죠..하지만.. 각각 디바이스에대해 설정값을 넣어줘야하는반응형 웹은 쉽게 다가갈 수 없는 미저리같은 그런 녀석이었습니다아..그래도.. 다시 심기일전하는 마음으로 처음부터 모크업을 진행을 하였답니다. 처음 모크업은 이러하였어요...메인화면 소개를 거치면 짠하고 크몽홈페이지가뜨는!!!!그런 이미지였답니다. 하지만 여러분들도 알다시피 계획한일들이 안될경우도 있잖아유....저도 그러하였어요..물론 처음시작할때에만 하더라두 이것들을 다끝내겠어란활활 불타오르는 열정으로 시작했었죠!!처음작업을해서 뽑아낸 아이들의 사진이에요. 상단바를 각 디바이스크기에 맞게 하는 작업을 먼저 했었는데요..이 녀석이 은근 골치 아픈 녀석이었답니다.각 위치마다 고정폭이 정해져있어고 그녀석들을 반응형에 맞출려고 얼마나 고생했는지.. 가뜩이나 수학도 못하는데 퍼센트 계산만 했엇답니다.. 저에게 퍼센트도 이러했답니다.. 하.....수학공부를 열심히해야겠어요..그래도 꿋꿋이 계산하고 넣어보고 계산하고 넣어보고 계산하고 넣어보고 즐기고~그러다 보니 점점 하나하나씩 되기 시작했어요!!머리는 점점 잘 돌아가고 재능목록들이 자기자리로 돌아가고!!!!노력의 기적이 어떤것인지 보았습니다.. 이리하여 결국에는..이러한 결과를 낳았더랬죠!! 실은 작업한지 꽤나됬고 릴리즈된지도 꽤나되었지만..아마두.. 모르시는 분들이 많을거에요지금 여러분들이 가지고 계신 폰으로 크몽의 반응형메인을 만나실 수 있답니다~!!한번 보시고 따끔한 충고를 답글에 남겨주세요. 따끔하게 맞고 고칠 수 있는부분은 한번씩 잉여로울때 작업을 하도록하겠습니다. -----------------------------------------------------------------------그럼 지금부터는..제가 이번작업을 하면서 느꼇던 몇가지를 적어볼까합니다.바로바로바로 당신이 반응형웹을 하고싶다면!!  따단!!그 첫번째 규칙!! 절대 고정폭을 주지말아라-이것이 반응형웹할때는 가장 중요한 거십니다.반응형웹이라도 픽셀은 PC와 노트북에서 여러분의 눈에 보이는것과 마찬가지로 적용된다는점!!!만약에 고정폭으로 1200px를 주게되었다면 데스크탑이나 노트북에서는 보기좋게 보이지만모바일환경에서는 엄청확대되어보인다는 사실 아셨나요??! 그럼 "고정폭대신 CSS에 뭘 줘야되는건가요?"라고 묻는 당신께 퍼센트(%)를 바칩니다.. CSS에 픽셀(px)대신 퍼센트(%)를 넣으면 여러분이 브라우저크기를 낮출때마다화면이 가변적으로 늘어난답니다. 물론 퍼센트는 백분율이라 화면의 크기에 맞게크기를 지정해주면 된답니다.그 두번째 규칙!! 미디어쿼리를 활용하랏!!!-미디어쿼리... 과연 그거슨 무엇인것인가!!!쉽게 설명해드리겠습니다. 미디어쿼리란 여러분의 브라우저크기를 컴퓨터가 인식해그 크기에맡게 보여주는 그런 녀석입니다.여러분들이 딱히 할게 별거없어요..그냥 미디어쿼리를 CSS에 설정해주고 그 크기에맡게 어떻게 보여줄것인가에 대해작성해주시면 되는겁니다. 참 쉽죠오?? 으앗!!음.. 일단 자세한 내용은 저의 스승블로그의 포스팅을 보시면 쉬울거에요..http://readme.skplanet.com/?p=9739#s5반응형 웹 기술 이해 | READMEreadme.skplanet.com그 세번째 규칙!! 같은줄에 있는 컨텐츠가 다들어가기엔 모바일화면이 너무작다면 밑으로 내리여!!!-분명 여러분들의 홈페이지를 작업할때에 보면 PC사항에서 잘 자리잡혀 있던것이 모바일환경에선 왠지 좁아 터질 것같다라고생각이 드실수 있습니다. 그렇다면.. 밑쪽으로 내리는 것을 저는 추우천을 드립니다!!그렇담 그 컨텐츠가 내려간다면 배치는 어떻게 해야 이쁜가에대한 저의 답변은 "그건 디자이너님 너의 맘이야 God bless you"입니다. 그 네번째 규칙!! 부트스트랩 같은 녀석들을 사용하랏~!!!!-아마 직접 CSS와 js를 조작하라고해도 못하시는 분들이 있으실거에요..그런분들을 위해 태나났습니다아~!!!! 바로바로바로 부트스트랩과같은 것들인데요.이 녀석들은 자기들이 설정해놓은 CSS집단인 컴포넌트로 웹개발자들을위협(?)하는 그런 녀석이랍니다.이 뇬석들을 사용하면 반응형웹이고뭐고 멋진표던뭐던 다 뚝딱뚝딱 만들어내죠..저도 애용하고있는 아이들이랍니다.(실은.. 상단바작업은 제가 CSS로했고 컨텐츠들은 부트스트랩이란 도구로 작업을 하였는데요.. 그시간차이가 우와 할정도에요..)그 정도로 좋은 녀석이랍니다. 그 녀석을 찾으실려면 구글검색창에 "부트스트랩"이라고 쳐보세요.CSS무지식개발자라도 쓰실수있게 패키지가 구성되어있답니다. 아무 클래스나 골라담아요 골라담아~!!-----------------------------------------------------------------------음음.. 뭐 별거없었지만 제가 올린 포스팅글 잘보셨는지 궁금하네요..꼭 반응형웹에 도전하시는 분들이 봤을때 좋은 내용이었으면 좋겠다는 작은 바램이 생기네요그럼 저는 크몽에서 프론트엔드 개발자를 맡고있는 Kun이었구요.다음번에 더 좋은 포스팅으로 만나뵈요. 제발~#크몽 #개발자 #개발팀 #팀원소개 #인사이트 #스택도입 #일지
조회수 4071

크몽 개발팀 문화와 구조 이야기

안녕하세요. 크몽 개발자들과 함께하고 있는 크레이그(a.k.a. 크알)입니다.크몽 개발자 그룹은 1년 내 그 규모가 3배로 커지고, Data Science, Growth Hacking 조직이 만들어지는 등 질적, 양적으로 급성장하고 있는 팀입니다.크몽 개발 부서에 계신 분들은 크몽에 대해 이렇게 이야기 합니다.(참고 : 크몽 개발팀원 더팀스 인터뷰 - '신뢰할 수 있는 동료와 함께 초고속 성장을 만들어가는 크몽 팀' )"제가 크몽에서 전반적으로 느낀 인상은 능동적인 분들이 많다는 거예요. 수동적인 업무를 책임감 있게 하는 것도 중요하지만 문제를 스스로 찾고, 동료들에게 제기하고, 문제를 해결했을 때 진심으로 기뻐하면서 행복감을 느끼시는 분들이 많아요. 그게 큰 조직에 있다가 온 저에게는 정말 많은 자극이 되었어요. "- 데이터분석 KM님"크몽이 저의 개발자 커리어에서 마지막 회사였으면 좋겠다고 생각해요. 실은 진심이고요. 그동안 회사의 성장을 지켜봤고 개발적으로도 많은 변화를 경험했어요"- BackEnd Sean님이렇게 개발자들이 행복하게 개발할 수 있는 환경을 우선시하고 있습니다. 그리고 크몽의 오픈 커뮤니케이션 문화를 지향함과 동시에 ‘Work Happy’와 'Freedom with Responsibility’ 란 가치 아래 최대한 자율성을 보장된 실무자 중심의 개발 문화를 추구합니다.크몽 개발 조직 구조위 핵심 가치 아래 크몽 개발 조직 구조는 크게 ‘Go’와 ‘Chapter’로 구성되어 있습니다.Go  ; 고우선 ‘Go’는 프로젝트 개발 팀 단위로 크몽 서비스를 개선하기 위한 목표 중심의 조직입니다. 다른 회사에서는 ‘Silo’, ‘Team'로 명칭 하기도 합니다. 물리적으로 한 공간에서 스크럼을 이루어 일할 수 있도록 자원을 갖추고 있습니다. Go 안에는 Go Leader(GL) 가 있어 팀 업무 관리 및 우선순위를 정합니다.현재 크몽 개발 파트의 Go는 아래와 같이 구성되어 있습니다.UX-Go크몽 서비스 UX를 개선하기 위한 목표로 데이터를 기반으로한 UX Iteration & Growth Mission 을 수행하는 팀Data-Go데이터 파이프라인을 구축, 활용하여 조직 내 필요한 데이터 자료를 공급하고, 크몽 서비스안에 머신러닝/딥러닝 등의 인공지능 기술 영역을 담당하는 팀Dasi-Go서비스 안정적인 운영 및 릴리즈,  CRM 기술 지원을 담당하는 팀Mobile-Go검색 서비스, 서비스 카테고리 개선 등 크몽 서비스 향상을 위한 모듈 개발팀크몽 라운지Chapter  ; 챕터'Chapter'는 직군별 조직 단위로 주 1회 정도의 커뮤니케이션 타임을 통해 업무 및 기술 동향을 교환합니다. 더불어 챕터 안에서 필요한 스터디, 외부 교육 등의 직군별 자기 능력 향상을 도모하고, 회사에선 이를 적극 지원합니다. 그리고 챕터 내 프로젝트를 통해 서비스 개선에 기여하기도 합니다.크몽 개발 파트는 아래와 같은 챕터가 있습니다.(참고 : 웹 프로트엔드 챕터의 'gulp 개선기' -  https://brunch.co.kr/@kmongdev/5 )**챕터 프로젝트는 챕터 내에서 개발자분들이 스스로 필요하다는 판단 하에 빌딩 된 프로젝트입니다. 챕터 내에는 CL(Chapter Leader)가 존재하며, Chapter 구성원 관리 및 의견을 모아 조직에 전파하는 역할을 담당합니다.Guild  ; 길드개발 파트 안에서의 'Guild'는 토이 프로젝트 같은 성격의 공통 관심 분야를 지닌 프로젝트 팀이라고 볼 수 있습니다. 길드 기획 단계에서 회사 전사적으로 적용되면서, 동호회 성격으로 피보팅(Pivoting) 되어 있지만, 기본적으로 공통의 관심 분야를 같이 학습하고 프로젝트에 적용하는 팀입니다. 매주 수요일 오후 2~3시 사이의 시간은 챕터(Chapter), 고(Go)를 떠나 본인이 원하는 길드에 들어가서 새로운 영역을 탐색하고 연구하는 시간입니다.크몽 개발 파트는 아래와 같은 길드가 있습니다.(참고 : 코틀린 길드의 코틀린 리서치 이야기  https://brunch.co.kr/@kmongdev/9 )정리모든 개발 조직은 '성과 중심' 또는 '성장 중심'의 문화를 가지고 있습니다. 균형을 꾀하는 게 이상적이긴 하지만 스타트업에선 쉽지 않은 일입니다.하지만 크몽 개발 부서에선 인적 성장 중심 문화를 고민하고, 끊임없이 시도하고 있습니다. 이를 위해 여러 전문 교육 기관과 협약을 맺고 교육 지원을 하고 있으며, 국내 정상급 권위자 분들로 구성된 외부 컨설턴트 그룹을 구성해 개발자 분들께 배움과 성장의 기회를 부여하려고 노력하고 있습니다. 1년의 기간 동안 이직률3%의 수치를 기록하고 있는 크몽 개발 파트에선 신규 인력 채용 시 제 1의 인사 기준은 '높은 학력'도, '화려한 커리어'도 아닌우리와 '오랫동안' 함께 '성장'할 수 있는가?입니다. 이를 위해선 개발자 성장을 돕기 위한 환경 구축 및 관리가 필수이고,  그것이 궁극적으로는 회사 및 팀원에게도 장기적인 발전을 가져올 꺼란 굳은 믿음이 있습니다.크몽 개발 그룹CTO#크몽 #개발팀 #개발자 #사내복지 #기업문화 #조직문화 #사내스터디 #CTO
조회수 1562

독서모임 스타트업에 개발자나 디자이너가 필요한가요?

트레바리는 독서모임을 운영하는 회사다. 멤버들이 책을 읽고, 독후감을 쓰고, 아지트에서 여러 사람들과 다양한 대화를 나눌 수 있도록 만들어준다. 아날로그적이려면 한없이 아날로그 할 수 있는 회사가 바로 트레바리다. 그러다 보니 트레바리의 첫 빌트인(?) 개발자 겸 디자이너인 나는 가끔 이런 질문을 받기도 한다. "트레바리에 개발자나 디자이너가 필요한가요?" 작년 11월과 12월, 개발과 디자인을 총동원해서 멤버십 신청 페이지의 UI/UX 개선 작업을 진행했다. 원래의 홈페이지보다 편하게 신청하도록 토스 결제를 연동하는 등 프로세스를 재편하였고, 판매할 프로덕트가 의도대로 보이도록 레이아웃을 다시 구성하였다. 컨텐츠의 가독성을 위해 컴포넌트들의 디자인도 깔끔하게 변경했다. 개선된 프로세스와 인터페이스라면 멤버십에 신청하는 사람들이 늘어날 거라고 확신했다. 홈페이지를 방문만 하고 멤버십에 신청하지 않은 이유는 '홈페이지가 불편하고 안 예뻐서'라고 생각했기 때문이었다.결론부터 말하자면 내 가설은 완전히 틀렸다. 개선된 홈페이지를 런칭했지만 방문 유저 대비 신청한 유저의 비율에는 큰 변화가 없었다. 다급히 주변에 조언을 구하기 시작했고 마켓컬리의 이지훈 님이 해주신 조언이 한참을 머릿속에 멤돌았다. "트레바리는 오프라인 경험이 메인이므로 홈페이지의 변화가 큰 효과가 없을 수 있음을 인정하고 시작해야 해요. 홈페이지는 광고를 보고 온 유저들이 독서모임에 가기 전까지 거쳐 가는 곳이에요."그렇다. 트레바리 홈페이지는 오프라인 독서모임에 참여하기 위한 건널목일 뿐이였다. 건널목이 아무리 좋다 한들 목적지가 탐탁지 않으면 사람들이 건너가지 않을 것이었다. 마찬가지로 홈페이지가 아무리 편하고 예뻐도 아지트에 와서 나누는 대화가 무의미하고 재미없다면 사람들이 트레바리를 찾지 않을 것이다.덕분에 트레바리 특성상 홈페이지를 위한 개발자나 디자이너 크루(=직원)가 필요한지 자문하게 되었다. 건널목 역할을 수행하는 홈페이지가 필요한 것이라면 이미 충분하다고 생각했다. 추가로 필요한 기능이 있다면 그때그때 적당한 프리랜서를 고용하는 게 합리적일 수도 있었다. 그렇다면 맨 위의 질문에 대한 대답은 "아니요. 필요 없어요. 프리랜서면 충분해요."가 되는 것이었다.내가 크루로서 잘 쓰일 수 있는 일은 무엇일까?얼핏 생각하기에 프리랜서면 충분해 보이지만 분명 내가 크루로서 잘 쓰일 수 있는 일이 있을 거라 생각했다. 그리고 그것을 오프라인 트레바리와 온라인 트레바리 사이에 간극이 있다는 점에서 찾았다.오프라인 트레바리는 꽤나 매력적이다. 한 시즌을 경험한 두 명의 멤버 중 한 명은 다음 시즌에도 멤버십을 신청한다. 물론 나머지 한 명까지 신청하게 만들게끔 개선할 부분들이 남아있지만 그래도 60%가 넘는 리텐션은 트레바리가 다시 올 만한 서비스라고 말해준다.온라인 트레바리는 사정이 다르다. 많은 사람이 방문하지만 금세 나가버린다. 지금의 트레바리 홈페이지는 트레바리가 뭐 하는 곳인지, 트레바리를 하면 어떤 사람이 될 수 있는지, 트레바리에서는 어떤 사람들을 만날 수 있는지 잘 알려주고 있지 않다. 미리 지인이나 미디어를 통해 트레바리의 매력을 알고 온 사람들만이 홈페이지를 샅샅이 뒤져본 후에나 어떤 곳인지를 엿볼 수 있다.이 불협화음을 잘 조율하는 일을 내가 잘 할 수 있겠다는 생각이 들었다. 나는 원래 작년 초까지 멤버였다가 트레바리 매력에 빠져 입사까지 하게 된 진성 유저였다. 덕분에 트레바리가 얼마나 좋은지, 어떻게 트레바리를 통해 예전보다 멋진 사람이 될 수 있는지, 트레바리에서 얼마나 좋은 사람들을 만날 수 있는지를 알고 있었다. 그리고 이것들을 동네방네 열심히 소문을 내고 싶은 사람이었다.트레바리 홈페이지가 오프라인 트레바리에 오기 위한 건널목이라면 건널목 입구에 삐까뻔쩍한 간판도 크게 달고, 안내판도 만들어 건널목 너머에 얼마나 멋진 곳이 있는지 넘어오고 싶게끔 기대감을 심어주고 싶다. 우리의 비전인 '세상을 더 지적으로 사람들을 더 친하게'처럼 내가 트레바리에 온다면 더 지적이고 멋진 사람이 될 수 있고, 사람들과 더 친하게 지낼 수 있음을 잘 설명해주고 싶었다. 사람은 자기가 정말 좋아하는 것을 설명할 때 지치지 않고 그 어느때보다 열심히 목소리를 높일 수 있다고 생각한다.물론 나 혼자서는 힘들 것이다. 그래서 다른 크루들과 같이 어떻게 잘 전달할 수 있을지 치열하게 고민해보기로 했다. 그 일례로 최근에 영훈님과 같이 사내 스터디를 시작했다. 이런 점들이 단순히 시키는 일만 해내는 프리랜서보다 훨씬 더 잘 쓰일 수 있는 크루로 만들어 줄 것이라고 믿는다. 아직 '그래서 구체적으로 어떻게?'까지는 고민을 끝마치지 못했지만, 드디어 어떤 방향으로 무슨 역할을 하는 사람인지를 결정하였다. 이 결정을 시작으로 올해는 '회사에 더 많은 기여를 할 수 있는 크루가 될 수 있지 않을까'하는 설렘과 '그러려면 훨씬 더 잘해야겠다'는 부담이 가득한 채로 일 년을 맞이하게 되었다.올 한 해 '세상을 더 지적으로 사람들을 더 친하게' 만들어줄 우리 크루들!#트레바리 #기업문화 #조직문화 #스타트업 #인사이트

기업문화 엿볼 때, 더팀스

로그인

/