스토리 홈

인터뷰

피드

뉴스

조회수 1736

변화하는 웹 플랫폼 따라가기

플랫폼이 어떻게 변화해가는지를 살펴보면, 직접 경험해보지 않더라도 사람들의 문제의식이 어디에 있는지, 어떤 문제들이 언제부터 풀리게 되는지를 파악할 수 있습니다.이 글에서는 이런 변화들을 살펴볼 수 있는 몇가지 유용한 링크들을 소개하려고 합니다.어떤 논의들이 어디서 오가고 있을까WICG discoursehttps://discourse.wicg.io/WICG는 웹 인큐베이터 커뮤니티 그룹이라고 해서, 웹 표준에 기여해본 사람이 아니라도 토론을 통해 아이디어를 발전시켜서 사람들이 실제로 겪는 문제를 W3C 표준까지 끌어올리는 것을 목표로 하는 커뮤니티입니다.이 곳에서는 주로 CSS, DOM API에 대한 아이디어가 올라옵니다.ES-Discusshttps://esdiscuss.org/ES-Discuss는 WICG와 비슷하게 ECMAScript 스펙에 대해서 논의하는 메일링 리스트입니다. 위 링크는 메일링 리스트에서 오간 이야기를 쉽게 조회할 수 있도록 아카이빙 해놓은 사이트입니다.논의된 아이디어는 어디서 표준으로 다듬어지고 있을까HTML: https://github.com/w3c/html 또는 https://github.com/whatwg/htmlCSS: https://github.com/w3c/csswg-draftsJS: https://github.com/tc39/ecma262HTML은 W3C의 WebPlat WG와 WHATWG에서, CSS는 W3C의 CSSWG에서, JS는 ECMA의 TC39에서 표준을 이끌고 있습니다.위 저장소들에 공개된 초안은 표준이 되기까지 여러 단계를 거치게 되는데, 여기서 다루지는 않겠습니다. (2018-01-25 수정) 이에 대한 내용은 다음의 블로그 포스트에서 자세히 설명하고 있습니다.W3C 표준화 제정 단계ECMAScript와 TC39다듬어진 표준은 어떤 브라우저에서 얼마나 구현되고 있을까다음의 링크에서 각 주제에 대해 브라우저들이 현재 어디까지 구현을 했는지 파악할 수 있습니다.Chrome: https://www.chromestatus.com/featuresFirefox: https://platform-status.mozilla.org/Edge: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/Safari: https://webkit.org/status/크롬 플랫폼 사이트의 경우 각 주제들이 어떤 버전에 반영되었는지 같이 확인할 수 있어서 편리합니다.나머지 브라우저들의 버전별 구현 상태는 https://caniuse.com/에서 주제를 검색하여 참고할 수 있습니다.구현된 기능들은 언제부터 사용할 수 있었고, 언제부터 사용할 수 있게 될까크롬과 파이어폭스는 릴리즈 캘린더를 공개적으로 관리하고 있습니다. 위에서 확인한 기능들을 담고있는 안정 버전이 언제쯤 릴리즈 될 지 다음의 링크를 보고 대략적으로 예상할 수 있습니다.Chrome: https://www.chromium.org/developers/calendarFirefox: https://wiki.mozilla.org/RapidRelease/Calendar크롬과 관련된 플랫폼 따라가기특정 크롬 버전이 어떤 V8 버전을 사용하고 있는지는 https://omahaproxy.appspot.com/에서 확인할 수 있습니다.Node.jsNode.js의 릴리즈 스케쥴은 https://github.com/nodejs/Release에서 확인할 수 있습니다.어떤 Node.js 버전이 어떤 V8 버전을 사용하고 있는지는 https://nodejs.org/en/download/releases/에서 확인할 수 있습니다.Electronhttps://electronjs.org/에서 일렉트론 최신버전이 어떤 노드, 크로미움, V8 버전을 사용하고 있는지 확인할 수 있습니다.일렉트론의 크로미움 팔로업은 깃헙 일렉트론 저장소의 Projects에서 확인할 수 있습니다: https://github.com/electron/electron/projects따라가는데 도움이 되는 블로그브라우저 벤더들이 직접 운영하는 블로그를 구독하면 웹 플랫폼의 소식을 가장 빠르게 접할 수 있습니다.ChromeChromium Blog: https://blog.chromium.org/V8 Blog: https://v8project.blogspot.kr/FirefoxMozilla Hacks: https://hacks.mozilla.org/SafariWebKit Blog: https://webkit.org/blog/EdgeMicrosoft Edge Dev Blog: https://blogs.windows.com/msedgedev/(2018-01-25 수정): @SaschaNaz님 제보로 Webkit status 사이트와 Edge 블로그 추가#스포카 #개발팀 #개발자 #인사이트 #업무일지 #후기
조회수 669

오픈서베이가 구성원과 함께하는 방식, 병특 Z세대에게 묻다

끊임없는 자기 계발과 성장 욕구는 Z세대의 특징이라고들 합니다. 약관 20세에 병역특례로 입사해 2년째 오픈서베이의 Z세대를 대표하는 김승엽 웹 프론트엔드 개발자(이하 레드)도 그렇습니다. ‘나이에 비해 잘한다’는 ‘아직 잘 못 한다’는 뜻이라며, 달콤한 퇴근 후 시간을 방통대 강의와 과제에 투자하고 있죠.  원동력이 무엇인지 물으니, 그도 얼마 전까지는 게으른 집고양이처럼 사는 게 꿈이었다고 합니다. 직원들을 진정으로 위하는 회사의 모습과 형·누나·아빠뻘의 구성원과 일하며 받은 좋은 자극 덕에 향상심이 자라났다고 하죠. Z세대의 마음을 울린 회사의 모습은 무엇일까요?       오픈서베이 김승엽(레드) 웹 프론트엔드 개발자   레드, 안녕하세요!  안녕하세요. 오픈서베이 웹 프론트엔드 개발을 담당하는 레드입니다. 오픈서베이 DIY 리뉴얼, 랜딩페이지 등 오픈서베이의 각종 웹페이지 개발을 맡고 있습니다. 오픈서베이에서 병역특례 복무 중이기도 하고요(웃음).   2년 전 스무살 나이로 입사했는데, 실은 오픈서베이도 2번째 회사라면서요. 맞아요. 고등학생 때 바로 취업을 했거든요. 특성화 고등학교에 다니면서 프로그래밍을 배웠어요. 배우다 보니 재미가 붙어서 친구들이랑 프로젝트도 해보고 교내 대회에도 나갔고요. 그때 대학교에 진학하기 보다는 빨리 취업해서 실무에서 배우고 성장하는 게 더 좋을 것 같다고 생각했던 것 같아요. 또 저희 학교 특성 상 졸업 전에 다양한 회사에서 구인 행사를 하러 와요. 전 그때 한 스타트업에서 병역특례 지원 해준다는 말만 듣고 멋모르고 첫 취업을 했어요. 아직 병특 지정 업체도 아니었는데, 입사만 하면 병특 업체 지원 해준다는 말만 믿고 순진했었죠.  그렇게 멋모르고 1년 정도 다녔더니 대표님이 병특 업체 선정 안 됐는데 더 신청한다고 될지 모르겠다고 하더라고요. 군대는 각자 일이니 스스로 해결 방법을 찾으라면서요. 그때 회사가 말하는 성장에 대한 비전이나 직원과의 약속이 현실성 없는 허황된 말이라고 생각했던 것 같아요. 그렇게 첫 회사에 실망해서 이직한 곳이 오픈서베이입니다.    첫 회사에서의 경험으로 이직 시 고려요소가 좀 달라졌나요? 조건이 까다로워졌다기보다는 회사에 바라는 게 줄었어요. 그냥 내가 다니는 동안 배울 게 있는 회사였으면 좋겠다는 생각만 있었어요. 병특 지원이 급했을 때라 더 그랬던 것도 같아요(웃음). 그런데 오픈서베이를 다니면서는 좋은 회사에 대한 생각이 또 조금씩 달라졌어요. 예전에는 천국 같은 회사에 대한 환상이 있었는데, 지금은 회사는 천국일 수 없다고 생각하는 편이거든요. 일을 하는 곳이 천국 같을 순 없으니까요.   그럼 정말 현실적으로 좋은 회사가 뭘까 생각해보게 되겠군요. 맞아요. 저는 열심히 살아야겠다는 생각이 들게 하는 회사가 좋은 회사라고 생각해요. 그런 면에서 오픈서베이는 정말 좋은 회사 같아요. 제가 계속 더 잘해야겠다는 자극을 받게 하거든요. 특히 함께 일하는 팀원들에게 긍정적인 자극을 많이 받는 편인 것 같아요.  조셉(김경만 안드로이드 개발자 겸 오베이 PM)이 입사하신 지 얼마 안 돼서 개발팀 세미나를 했을 때가 처음으로 충격을 받았어요. 저는 주제와 내용 자체가 어려워서 이해하기 힘들었는데 그걸 다 소화해서 발표하는 모습을 보면서 경각심이 생기더라고요.   조셉은 어떤 주제로 개발팀 세미나를 했을까요? (클릭)   아무래도 완전 경력자보다는 비슷한 또래나 경력을 가진 분들에게서 더 자극을 받나 보군요. 저는 그런 것 같아요. 그래서 로빈(권장호 개발자)이 입사했을 때는 진짜 충격이었어요. 저보다 어리고 경력도 짧은데 일을 대하는 태도나 적극성이 저랑 많이 달랐어요. 일하는 시간 외에도 시간 내서 꾸준히 개발 공부나 블로그를 하는 모습을 보면서, 저도 열심히 해야겠다는 생각이 들더라고요.  그전까지는 좀 안주하려는 면이 있었어요. 왜 그러냐면 저는 저보다 나이나 경력이 많은 분들이랑만 일해왔잖아요. 그러다 보니 칭찬도 “나이에 비해 잘한다”는 말을 주로 들었어요. 사실 그게 “아직 잘은 못한다”는 뜻이잖아요. 그걸 모르고 그냥 내가 잘하고 있구나 하면서 안도해왔던 것 같아요.  그런데 아직 어리다는 장점은 시간이 지날수록 약해지잖아요. 이른 나이에 빠르게 일을 시작했다는 저만의 장점을 계속 가지고 있으려면 지금 상황에 만족하는 게 아니라 계속 노력해야 한다는 걸 깨달은 것 같아요. 개발자를 하루 이틀 하다가 때려치울 것도 아니고 남들보다 빨리 실전에 뛰어든 만큼 이론적으로 부족한 것도 많으니 더 공부해야 한다는 거죠.    “일을 일찍 시작했다는 장점을 유지하려면  지금 상황에 만족하지 않고 계속 노력해야 돼요”   그런데 열심히 해보려고 해도 뭘 해야 할지, 어떤 공부를 어떻게 하면 좋을지 막막할 때도 있잖아요. 전 직장이었다면 그랬을 것 같아요. 그런데 개발팀원은 모두 저보다 개발 경력이나 사회 경험도 많고 언제든 조언해줄 마음이 열려있는 분들이라 도움을 받고 있어요. 특히 폴(이건노 CTO)은 주니어 개발자들과 1:1 미팅을 자주 가지면서 도움 되는 조언을 많이 해줘요.  한번은 폴이 제 개발자 커리어에 대한 조언을 해주셨어요. 저는 프론트엔드 개발자라면 프론트엔드만 전문적으로 파면된다고 생각했거든요. 그런데 백엔드 등 다른 개발 분야도 1단계 정도는 공부를 해둬야 지반이 탄탄한 프론트엔드 개발자가 될 수 있다는 조언을 해주셨어요. 그 조언이 지금도 기억에 많이 남아요. 왜냐면 지금 당장 해야 하는 프로젝트 단위가 아니라 제 인생 관점에서 조언을 해주신 거잖아요. 사실 폴은 CTO고 저는 직원이니까 조언도 업무 코치 위주로만 해줄 수도 있는 건데요. 이렇게 저보다 10, 20년 넘는 경력을 가진 분이 제 개발자 인생에 대해 해주는 조언은 어디서도 듣기 힘들잖아요.    그렇죠. 멘토가 중요하다고는 하는데, 20대 초반의 멘토는 보통 책이나 TV같이 멀리서만 접할 수 있는 인물이잖아요. 좋은 멘토는 많지만 나를 위한 조언이 아닐 때는 공허하게 들리기도 하고요.  맞아요. 저도 지금 이 시기에 바로 옆에서 조언해줄 수 있는 분이 있다는 건 정말 좋은 것 같아요. 그런 폴 덕에 개발팀은 시켜서 하기보다 자기 주도적으로 일할 수 있는 환경과 문화가 잘 갖춰진 것 같아요.  매주 진행하는 개발팀 업무 공유 회의 때도 단계나 일정에 대한 틀을 잡아주는 역할에 집중하는 편이세요. 위에서 “이거 해, 저거 해”라고 콕 집어서 마이크로 매니징을 하는 게 아니라, 프로젝트 단위로 자발적으로 구성원이 꾸려져서 진행해 나가는 게 오픈서베이의 업무 문화인 것 같아요.  그런 문화다 보니까 저도 시키는 일만 하는데 그치지 않고 다양한 시각에서 프로젝트를 바라보면서 의견도 많이 낼 수 있는 것 같아요. 구성원들이 제 의견을 경청해주고 수용해주면 ‘내가 프로젝트에 직접적으로 기여하고 있구나’란 생각이 들면 책임감도 더 생기는 것 같아요.    “내가 프로젝트에 기여하고 있다는 생각이 들면 더 책임감을 가지면서 일할 수 있어요”   그런 긍정적인 자극이 실제 업무 능력 향상으로도 이어지는 편인가요?  네. 저는 기술적인 면에서도 많이 성장하고 있다고 생각해요. 유지보수하기 수월한 깔끔한 코드를 짜는 능력도 예전보다 많이 향상됐고, 주어진 시간 내 일을 더 빨리 효율적으로 마칠 수 있는 생산성도 많이 올랐다고 생각해요. 저는 야근 없이 깔끔하게 일을 끝내는 게 일을 잘하는 거라고 생각해서요(웃음).   와! 그럼 레드가 배운 일 잘하는 방법 하나만 알려주세요.  저는 ‘똑똑하게 질문하기’라고 생각해요. 질문사항에 대해 충분히 고민해본 뒤 물어봐야 한다는 걸 알았어요. 사실 주니어 때 가장 많이 하는 고민이 ‘어떻게 해야 좋은 질문을 할 수 있을까’ 잖아요. 회사에서는 모르면 물어보라고 하는데 그냥 물어보면 혼날 때도 있으니까요. 그런데 질문거리에 대해 제가 충분히 소화를 못 하면 어디에서 어려움을 겪고 있고 그래서 어떤 도움이 필요한지 질문을 받은 분도 몰라요. 질문이란 건 제 업무를 위해 다른 분의 업무 시간을 빌리는 건데, 정확히 질문하지 못하면 질문한 사람이나 받은 사람의 시간을 그만큼 허비하는 거니까요.  이걸 알고 난 뒤 충분히 고민하고 물어보기 시작했더니 신기하게도 질문을 받은 분의 답변도 달라졌어요. 제가 테리(이한별 개발자)에게 질문을 많이 하는 편인데, “이렇게 해라, 저렇게 해라”는 단편적인 답변이 아니라 “이건 이래서 이렇고, 저건 저래서 저렇다. 그래서 이럴 땐 이걸 써야 하고, 저럴 땐 저걸 써야 한다”는 맥락적인 답변을 해줘요.  테리가 좋은 분이라 답변을 잘 해주시는 것도 있지만 제가 질문거리에 대해 충분히 고민해서 알고 있으니까 구체적으로 대답해줄 수 있는 거라고 생각해요. 이런 좋은 답변으로 과정을 충분히 알면 질문을 반복하거나, 다른 분의 질문에 불필요한 시간 낭비를 하지 않고 답할 수 있게 되는 것 같아요. 나중에 비슷한 상황이 오면 제가 스스로 문제를 해결할 수 있게 되고요.   주니어에게 꼭 필요한 팁이네요! 고맙습니다. 최근에는 방송통신대학교에 진학했다고 들었어요.  맞아요(웃음). 사실 방통대 진학도 로빈의 영향이 컸어요. 안 그래도 최근에 개발 이론 공부를 따로 해보자고 생각하던 차였어요. 그런데 로빈이 방통대 진학을 하면서 같이 해보자고 해서 이참에 도전했죠. 마음만 먹고 있다가 로빈 덕에 실행할 수 있었던 거에요. 요즘은 일을 마치면 방통대 강의를 듣거나 과제를 하는 데 시간을 보내고 있어요.     “이론 공부는 마음만 먹고 있다가 로빈 덕에 실행할 수 있었어요” (레드 옆에 노란옷을 입고 앉아 있는 분이 로빈입니다)   와.. 그럼 일과가 어떻게 되는 거예요?  오픈서베이 병특은 출퇴근 시간이 기본 10시 출근-7시 퇴근인데, 경우에 따라 신청해서 9시-6시로 변경할 수 있어요. 저는 방통대 다니면서부터 9시로 출근 시간을 조정했어요. 출근이 늦으면 그만큼 퇴근도 늦어지니 저녁 시간을 충분히 활용하지 못하겠더라고요.  하루일과는 9시까지 출근해서 우다다 일하고 점심 먹고 일하다가 6시에 칼같이 퇴근해요. 집에 가서는 씻고 밥 먹고 강의를 듣거나 과제를 하죠. 최근에는 저녁 필라테스를 시작해서 평일 저녁 중 이틀은 필라테스를 하러 가요. 주말에 좀 쉬고요(웃음).   조바심이 든다고 다 열심히 할 수 있는 건 아닌데, 남다른 원동력의 배경이 궁금하네요.  저도 진짜 빡센 것 같고 가끔 힘도 들어요. 그런데 다른 회사에서 병특 중인 주변분들 보면 운영보수 위주의 반복적인 업무만 하거나, 병특이라 쉽게 이직할 수 없으니 업무를 과다하게 몰아주는 경우도 보곤 해요.  제가 주어진 업무 시간에만 집중하고 퇴근 후 시간을 자기 계발을 위해 쓸 수 있다는 건 쉽게 얻기 힘든 기회일 수도 있는 거죠. 성장을 위한 중요한 시기에 주어진 기회라고 생각하면 열심히 할 수 있게 되는 것 같아요. 저보다 더 열심히 하는 다른 구성원을 보면서 자극을 받는 것도 물론 있고요.   산업기능·전문연구요원으로  오픈서베이에 지원하고 싶다면? (클릭)   자기개발에 매진하면 회사 생활에 소홀해질 것도 같은데.  음. 회사에서 성취가 없다는 생각이 들면 그럴 수 있겠네요. 그런데 오픈서베이는 반기마다 전사 회의를 통해 하이(황희영 대표이사)가 회사 성장에 대해 공유해주잖아요. 이 시간은 단순히 오픈서베이 매출 성장 공유가 아니라 제 기여가 회사에 어떤 도움이 됐는지, 이를 바탕으로 회사가 얼마나 성장하고 있는지를 점검하는 과정이라고 생각해요.  개인적으로는 투자 받은 돈 까먹는 스타트업이 아니라 우리 서비스와 구성원의 노력으로 흑자를 기록하고 매번 매출 성장을 하고 있다는 점도 저한테는 큰 보람이고 성취거든요. 실질적인 매출이 있고, 고객사가 계속 늘고, 매출 성장도 계속 일어난다는 이야기를 들으면 진짜 회사다운 회사라는 생각이 들고 성취감이 느껴져요.   6월에 강남역 1분 컷 초역세권 사무실로 이사도 가고! (웃음) 그것도 좋은데 사실 저는 하와이 간다고 했을 때 진짜 신났어요(웃음).  사실 전사 하와이 워크샵은 18년 목표 공약이라서 가는 거잖아요. 회사가 진짜 할 수 있는 목표를 잡아서 노력하고 목표 달성을 했을 때 약속을 지키는 모습을 보면서 되게 멋지다는 생각을 했어요. 좋은 회사와 좋은 어른의 모습은 이런 건가 싶고, 이런 모습을 보면서 저도 더 성장해야겠다고 생각하는 것 같아요.      “레드와 함께 일하고 싶으시다면 지금 바로 오픈서베이 입사 지원을 해보세요”
조회수 2980

JANDI CONNECT 개발기

지난 1월 말, 새해를 맞아 잔디에 새로운 기능이 업데이트되었습니다. 바로 잔디 커넥트에 관한 내용인데요, 협업에서 많이 쓰이는 몇 가지 외부 서비스를 잔디와 쉽게 연동해서 더욱 효율적인 업무 커뮤니케이션을 할 수 있게 되었습니다. 많은 고객분들이 이번 업데이트를 기다려주신 만큼, 저희 개발팀 또한 기대에 보답하고자 지난 몇 주의 스프린트 동안 열심히 준비했습니다. 이번 글에서는 커넥트 동작 방식을 설명하고 그 개발 과정에서 저희가 겪은 시행착오를 비롯한 여러 값진 경험들을 공유하고자 합니다.Integration? Webhook!연동: [기계] 기계나 장치 따위에서, 한 부분이 움직이면 다른 부분도 함께 잇따라 움직임.앞서 말한 대로 잔디 커넥트는 여러 웹 서비스들과 잔디를 연동할 수 있는 기능입니다. 서로 다른 웹 서비스를 연동하기 위해선 한 서비스 내에서 특정 이벤트가 발생 했을 때 다른 서비스로 해당 이벤트를 알려주는 연결 고리가 필요합니다. 이때 해당 연결 고리 역할을 위해 대표적으로 사용되는 기법이 웹훅(WebHook) 입니다. 웹훅은 user-defined HTTP callbacks, reverse APIs 등으로 불리는데, 간단히 설명하자면 웹 서비스에서 공개한 API가 아닌 사용자가 직접 지정한 주소(URL)로 특정 이벤트가 발생 시 HTTP Request를 보내주는 기법입니다. 예를 들어,새로운 일정이 등록된 경우(Google Calender)요청한 Pull Request가 Merge된 경우(GitHub)카드에 새로운 코멘트가 작성된 경우(Trello)이러한 이벤트가 발생했을 때 사용자가 매번 이벤트가 발생했는지 확인하지 않아도 서비스가 먼저 알려줄 수 있도록 일종의 알림을 등록하는 것이죠. 잔디 커넥트는 이와 같은 특징을 이용해서 각각의 웹 서비스에서 제공하는 웹훅을 잔디의 메시지 형태로 전달하는 기능입니다.일반적으로 웹훅은 이벤트에 대한 알림을 외부로 전달하는 것을 말합니다. 이 부분에서 중요한 것은 전달 방향인데, 서비스 내부에서 외부로 전달하기 때문에 이를 Outgoing Webhook으로 부르기도 합니다1. 같은 맥락에서 반대로 생각해보면 외부에서 서비스 내부로 특정 데이터를 전달하는 경우이니 Incoming Webhook이 됩니다. 앞서 웹훅을 reverse API라고 했는데 이를 다시 뒤집으니 결국 서비스 내부로 통신하는 제한적인 API와 같은 역할을 합니다. 굳이 용어를 구분한 이유는 API와 달리 접근하려는 서비스의 별도 인증 절차를 거치지 않고도 사용자가 생성한 웹훅의 URL을 인증 토큰으로 사용하며 약속된 Request Body 포맷만 알고 있다면 자유롭게 사용할 수 있기 때문입니다.개념 설명이 다소 길어졌지만, 이번 잔디 커넥트 기능에 대해 용어나 개념이 낯설다는 피드백이 생각보다 많았기 때문에 이번 글을 통해 더 많은 분들이 웹훅을 이해하는 데 도움이 될 수 있으면 좋겠습니다.구현에 앞서서비스를 운영한지 1년 정도 지난 시점에서 저희 내부적으로는 백엔드의 기술 스택 변경 및 각 서비스 분리에 대한 갈증이 있었습니다. 하지만 이미 서비스를 운영 중이기 때문에 안정성이 최우선시 되는 만큼 꽤 부담스러운 숙제로 미뤄둘 수밖에 없었고요. 때마침 커넥트 기능은 숙제를 시험해볼 만한 좋은 기회임에는 분명했지만, 새로운 기술 스택을 바로 서비스에 적용하기엔 오히려 개발 효율이 떨어질 것이라는 판단하에 일단 서비스 분리에만 집중하기로 했습니다.기본적으로 API와 DB를 기존 서버와 분리하고 웹훅 데이터를 저장하기 위한 큐와 해당 데이터를 처리하는 배치 서버 또한 모두 기존 서비스와 분리해서 최대한 결합도를 제거했습니다. 이런 설계 덕분에 추후 사업 전략이나 각 국가의 특성에 맞춰 커넥트 기능을 어렵지 않게 포함하거나 제외할 수 있게 되었습니다. 전반적인 저희 잔디 백엔드 아키텍쳐에 대해서는 아직 한 번도 소개 해드린 적이 없으니 다음에 따로 주제로 선정해 집중적으로 다뤄보도록 하겠습니다.동작 방식잔디 커넥트가 동작하는 방식은 기본적으로 다음과 같습니다.Incoming Webhook URL 생성 - 외부 서비스 웹훅 등록 - 웹훅 수신 - 메시지 작성 연동 대상 서비스마다 조금씩 차이가 있지만, 기본적으로 모두 위와 같은 방식으로 동작하기 때문에 단계마다 나누어 설명하겠습니다.1. Webhook URL 생성Webhook URL은 https://wh.jandi.com/connect-api/webhook/{teamId}/{webhook-token}와 같은 형태로 생성됩니다. hostname을 별도로 설정함으로써 기존 API 서버와의 분리는 물론이고, nginx의 Limiting the Request Rate 설정을 이용해서 호출되는 웹훅 요청 수를 효과적으로 제한할 수 있었습니다. webhook-token은 중복을 피하면서 각 웹훅에 대한 유효성을 검증할 수 있도록 여러 키를 조합한 md5 hash 값을 이용했습니다.이렇게 생성된 URL은 Incoming Webhook 뿐만 아니라 Google Calendar 등의 서비스에 등록하는 콜백 URL로 사용합니다.2. 외부 서비스 웹훅 등록웹훅을 등록하는 방법은 서비스에 따라 API를 이용하거나 수동으로 직접 등록할 수 있습니다. 사용자가 직접 웹훅을 등록하는 방법은 웹훅 URL만 생성해서 전달하면 등록 과정의 추가 처리가 필요 없어서 간단하지만, 서비스마다 등록하는 방법이 조금씩 다르고 다소 복잡하게 느껴지는 문제가 있습니다. 반대로 각 서비스에서 제공하는 API를 이용해 웹훅을 등록하면 사용자의 부담을 많이 줄일 수 있지만, 그만큼 내부적으로 처리해야 할 작업이 많아집니다. 그래서 구현 초기에 꽤 많은 시간을 투자할 수밖에 없었고 그 과정에서 아래와 같은 어려움을 겪었습니다.웹훅 관련 API를 사용하려면 먼저 인증을 받아야 하는데 서비스마다 제공하는 인증 방식이 조금씩 달라서 이를 통합하는 모델을 만들기가 쉽지 않았습니다. 요약하자면 기본적으로 accessToken을 사용하지만, 인증 방식에 따라 부가적으로 필요한 데이터가 서로 조금씩 다른것이죠. 가령, 구글캘린더는 만료 일시와 토큰 갱신을 위한 refreshToken 값을 별도로 갖고 있어야 합니다. 또 한가지 놓치기 쉬운 부분은 인증 폐기(revoked) 관련한 데이터 처리인데 저희가 경험한 바로는 인증이 폐기되었을 때 별도로 웹훅 알림을 주지 않기 때문에 반드시 인증의 유효성을 확인하는 추가 로직이 필요합니다.대부분의 사무실이 그렇듯이 저희 또한 공유기를 이용해 내부 네트워크를 구성하고 있습니다. 게다가 백엔드 파트는 개개인의 로컬 가상 서버에 동일한 환경을 설정해놓고 개발을 하므로2보통 경우엔 외부(public network)에서 들어오는 요청을 받을 수 없습니다. 그렇다고 매번 외부 네트워크에 있는 서버에 배포 후 테스트하기가 어려우니, 저희는 각 로컬 서버마다 고유 포트 번호를 나눠 갖고 WAN이 물린 공유기의 포트 포워딩을 알맞게 설정한 뒤에 네트워크 터널링 유틸리티인 ngrok을 이용해 내부와 연결되는 public 주소를 생성해서 외부 서비스와 문제없이 통신할 수 있었습니다.3. 웹훅 수신웹훅을 통해 들어오는 Request는 일단 정상 응답을 하는 게 좋습니다. 서비스마다 최초 웹훅 등록 시 유효한 URL인지 확인하는 테스트 요청을 하는데 이때 정상 응답을 하지 못하면 아예 등록조차 처리되지 않습니다. 또한, 정상적으로 등록된 이후 특정 이벤트에 해당하는 웹훅 요청에 대한 응답에도 주의할 필요가 있는데, 만약 에러 응답이 반복되면 일정 시간 동안 각 서비스에서 아예 해당 웹훅을 발송하지 않도록 제한이 걸려 더 이상 테스트를 진행할 수 없는 경우도 있었습니다.따라서 일단 웹훅 요청이 들어오면 teamId와 webhook-token 값으로 올바른 웹훅인지 검증한 후 서비스별 큐에 Request header와 body를 포함한 데이터를 전달한 뒤 바로 응답하고, 큐에 쌓인 데이터는 커넥트 종류별로 배치 서버가 돌면서 처리하게 됩니다. SQS를 사용함으로써 늘어나는 데이터에 대한 안정성을 확보하고 각각의 배치 서버를 독립적으로 분리해서 구현함으로써 자연스레 확장성(scalability)도 보장할 수 있게 되었습니다.4. 메시지 작성웹훅 데이터를 잔디의 메시지로 변환하는 역할은 배치 서버가 담당합니다. 서비스별로 데이터 포맷이 다르므로 해당 데이터를 파싱 및 처리하는 Worker 또한 각각 구현했습니다. 사실 커넥트 기능에서 가장 핵심적인 역할을 하는 부분인 만큼 가장 많은 공수가 드는 작업이였던 것 같습니다.서비스마다 정해놓은 웹훅 이벤트와 잔디 커넥트에서 제공하고자 하는 알림이 서로 완전히 일치하지 않아서 이를 서로 연결하는 작업연동 서비스의 문서가 잘 정리되어 있지 않아서 일일이 필요한 동작을 취하고 그에 따라 들어오는 데이터를 정리하는 작업잔디 계정 언어에 따라 메시지 L10N3을 적용하는 작업커넥트 메시지를 전달하기 위해 기존 멤버와 다른 커넥트 봇을 구현하는 작업등 요약하기 어려울 정도로 크고 작은 이슈들이 많았습니다. 그 내용이 너무 다양해서 모두 상세히 기록하긴 어렵지만, 개중에 도움이 될만한 내용을 추려서 아래 따로 정리했으니 관심 있으신 분들은 참고하시면 좋을 것 같습니다.서비스별 집중 탐구커넥트 구현 일정을 최대한 앞당기기 위해 저희는 개발자들끼리 각각의 커넥트 종류 별로 전담해서 작업하는 전략을 취했습니다. 제가 대표로 글을 작성하기는 하지만 보다 정확하고 구체적인 정보를 전달하는 것이 좋겠다는 생각에 개발을 담당하신 분들과의 짧은 인터뷰 형식을 빌려 공유하겠습니다.- Google CalendarQ. 기술적으로 난이도가 높았던 작업을 소개해달라.전반적으로 어려운 작업이 있었다기보단, 캘린더 특성상 세세하게 처리할 부분들이 많아 설계와 구현이 어쩔 수 없이 복잡해졌다. 가장 골치 아팠던 작업은 일정 알림을 타임존(Time Zone)에 따라 각각 알맞은 시간에 전달하는 작업인데, “잔디 계정의 타임존”, “구글 캘린더의 타임존”, “개별 일정의 타임존” 이렇게 3가지를 모두 고려해서 경우마다 기준이 되는 타임존을 결정하는게 엄청 까다로웠다. 심지어 구현 후 테스트를 하는 과정에서도 출력된 시간이 올바로 표시된 것인지조차 헷갈려서 디버깅하는데 한참 고생할 수 밖에 없었다.웹훅을 등록하고 관리하는 부분도 꽤 복잡했는데, 구글 답게(?) 웹훅에도 만료 기간이 존재한다는 것이 포인트다. 때문에 만료되기 전에 반드시 재등록 및 과거 웹훅 삭제 작업을 하는데, 효과적으로 처리하기 위해 “웹훅을 받을 때마다 만료 기간을 확인”, “등록된 일정이 많지 않아 웹훅을 받지 못하는 경우도 있으니 별도의 배치서버가 하루 단위로 확인” 이렇게 두 가지 로직을 넣어서 자동으로 웹훅을 유지하도록 구현했다.또한, 다른 연동 서비스와 달리 구글은 웹훅 콜백으로 들어오는 요청에 해당 이벤트에 대한 데이터를 직접 담아주지 않기 때문에 key를 가지고 한 번 더 API 호출을 통해 필요한 데이터를 가져와야 한다는 점도 주의해야 한다. 요청해야 할 API 문서는 비교적 잘 정리된 편이지만, 같은 요청에 대해서도 인자를 어떻게 보내는지에 따라 그 응답이 제각각이기 때문에 응답 값에 대해 무조건 신뢰하고 처리해서는 안 된다. 당연히 존재할 것으로 생각한 필드 값에 빈 배열이 들어와서 일정 관련된 데이터를 일부 날리고 나서야 깨달았다.. -_-Q. 가장 처리해야 할 이슈가 많았다고 알고 있는데, 그중에서도 기억에 남는 이슈가 있을 것 같다.너무 많은 이슈를 동시에 처리하다 보니 특별히 기억에 남는 이슈는 없다. 다만 아직도 왜 그랬는지 확실한 이유는 알 수 없지만, 언젠가 한 번 구글에서 웹훅을 아예 전달해주지 않았던 경우가 있었다. 과도한 요청으로 limit이 걸린 것도 아니었는데, 갑자기 웹훅이 안들어오니깐 우리로서는 어떻게 풀어볼 방법이 없었다. 그러다 나중에 확인해보니 대략 12시간쯤 지나고 나서 그동안 밀려있던 웹훅 데이터가 한 번에 밀려서 들어와 있더라. 다행히 그 이후로 지금까지 한 번도 재현되지 않는걸 보니, 혹 동일한 증상을 겪는다면 당황하지 말고 기다려 보시라.반복 일정을 다루는 것도 꽤 골치 아픈 이슈인데, 왜냐하면 일정이 있을 때 마다 웹훅 알림을 주지 않고 처음 등록된 시점에서 한 번만 정보를 알려주기 때문에 등록된 시점 이후의 일정은 내부적으로 계속 등록해줘야 한다. 기본적으로 구글 캘린더는 RFC-55454 표준을 따르지만, 실제 전달되는 데이터 중 일부는 표준과 조금 다른 부분이 있었다. 특히 반복 일정(recurrence) 관련 데이터 포맷이 조금 다르므로 캘린더 데이터를 파싱하기 위해 만약 외부 library를 사용한다면 별도의 예외처리가 필요하다. 더욱 더 까다로운 건 사실 등록된 반복 일정이 수정되거나 삭제되는 경우인데, 이때 “특정 일정만 삭제”, “지금 시점 이후의 일정 모두 수정” 등 워낙 케이스도 많고 각각을 테스트 하는 것도 쉽지 않기 때문에 작업 시간이 꽤 오래 걸렸다. (심지어 아직 확인하지 못한 드문 케이스에서는 잠재된 버그가 있을 수도…)Q. 그 밖의 도움이 될만한 노하우나 꿀팁이 있다면?구글 캘린더 API는 Webhook 보단 Push Notification 키워드를 많이 사용한다. 푸시 노티라는 게 좀 다른 카테고리에서 많이 쓰이는 용어이기도 하다 보니 코드 리뷰 등의 커뮤니케이션을 할 때 혼동이 좀 있었던 것 같다.물론 서비스 요구사항마다 다르겠지만, 잔디 같은 경우엔 요구사항에 맞춰 계속 설계를 변경 및 개선하다 보니 결과적으로 너무 복잡해져 효율이 떨어지는 코드를 작성할 수밖에 없었다. 처음부터 연동을 생각하기보다는 아예 캘린더 자체 기능을 베이스로 설계하고 데이터만 구글에서 가져온다 생각했다면 개발 생산성이 더욱 좋았을 것 같다.- TrelloQ. 기능을 구현하면서 느낀 아쉬웠던 점과 좋았던 점을 짚어달라.트렐로 공식 API 문서가 더 명확했다면 좀 더 개발이 수월했을 것이다. 문서가 RESTful하게 end-point path는 간결하게 잘 정돈되어 있지만, 각 요청 parameter에 대한 설명이나 response 데이터 등이 명확하게 정리되지 않아서 적합한 API를 찾거나 불명확함을 걷어내기 위한 테스트를 하다 보니 전반적으로 시간이 길어지고 비효율적이었던것 같다.그에 반해 트렐로에서 웹훅 이벤트를 발생시키기 위한 유저 액션들이 비교적 간단하고, 그에 따른 콜백 리퀘스트 또한 누락 없이 빠르게 잘 들어와서 그나마 쉽게 테스트를 할 수 있었다.Q. 기능 구현을 위해선 반드시 알아야 할 웹훅 이벤트 종류 및 데이터에 대한 문서는 정리가 전혀 안 되어있다고 하던데 정말인가?그렇다. 처음엔 좀 당황했지만, 그래도 방법이 없으니 일일이 경우마다 테스트해보면서 직접 정리를 하려고 했다. 하지만 각 웹훅마다 큰 구분만 있고 세세한 데이터는 너무 다양해서 깔끔하게 정리하기가 어려워 따로 공유를 위한 문서를 만들지는 못했다. 예를 들자면 트렐로에서 updateCard 라는 action type의 웹훅 데이터를 보내주는데, 그 데이터만 보고 “Card Archive”, “Description 수정/삭제”, “Due date 등록/수정”, “카드 이동” 등의 여러 가지 서로 다른 이벤트를 구분해야 한다. 근데 그 구분하는 방법이 특정 flag가 있는 게 아니라서 각 data를 모아놓고 역으로 분리하다 보니 코드를 깔끔하게 작성하기가 어려움은 물론, 추후 트렐로 측 데이터의 변동이 있을 때의 품질을 보장할 수 없는 리스크를 안고 구현할 수밖에 없었다.Q. 그 밖의 도움이 될만한 노하우나 꿀팁이 있다면?만약 트렐로와 어떤 형태로든 연동하려고 한다면, 설계 전에 모든 API에 대해 꼼꼼히 살펴보고 웹훅 이벤트 또한 직접 테스트해서 일단 전체적으로 리스트업을 정리하는 게 보다 생산성에 도움이 될 것이다. 트렐로를 잘 알고 있더라도 서비스 내부에서 “보드”, “리스트”, “카드”가 어떤 상관관계를 가지는지 미리 정리해보는 것도 좋다.사소하지만 좀 특이했던 점은 웹훅을 처음 등록할 때 해당 URL로 확인 요청을 한번 하는데, 이때 요청은 HTTP method가 POST가 아닌 HEAD로 들어온다. 그래서 반드시 동일한 URL의 HEAD 요청에 대해서도 정상 응답을 할 수 있도록 구현해야 한다.마무리잔디 커넥트를 구현하면서 특히 서비스 품질과 개발 속도 간의 밸런스에 대한 고민을 많이 했습니다. 초반에 서비스 종류별로 작업을 분리하고 각각의 방식으로 설계한 뒤 나중에 정리하는 전략이다 보니 공통으로 가져갈 수 있는 DB 모델이나 서비스 로직이 많아서 이를 통합하기 위해 반복 작업을 할 수밖에 없었는데 이 부분이 저희 내부적으로 느낀 가장 아쉬운 부분이 아니었나 생각합니다. 기능 중 많은 부분이 외부 서비스에 의존적이다 보니 생각하지도 못한 크고 작은 이슈들이 발생해서 일정 산출에도 꽤 어려움을 겪었습니다.커넥트 기능을 출시한 이후로 꽤 시간이 지났음에도 불구하고 이슈 백로그(Backlog)를 보니 아직도 개선할 부분이 많이 남아있는 듯 합니다. 그렇지만 이번에 기반이 되는 작업을 최대한 튼튼히 하기 위한 많은 시행착오를 거쳤기에, 추후 연동되는 커넥트 종류를 늘려나가는 시점5에 보다 효과적으로 개발할 수 있을 것이라 기대하면서 이번 글을 마치겠습니다.Slack API 문서 참고 ↩vagrant의 box로 서로의 로컬 개발 환경을 동일하게 유지하고 있습니다. 참고로, 현재 저희 서버 환경은 Local - Dev - Staging - Production으로 구성되어 단계별로 상황에 알맞게 배포하고 있습니다. ↩Localization의 약어. 잔디는 아시아 시장에 최적화된 서비스를 제공하고자 한국어, 일본어, 중국어 간체자(중국), 번체자(대만/홍콩), 영어 총 5가지 언어를 지원합니다. ↩아이캘린더(iCalendar)로 불리는 인터넷 캘린더의 데이터 포맷에 관한 표준. IETF 문서참고 ↩구체적인 시점은 말씀드리기 어렵지만, 더욱 좋은 사용성을 제공하고자 유저분들의 설문조사를 진행하고 있으니 많은 참여 부탁드립니다. ↩#토스랩 #잔디 #JANDI #개발후기 #일지 #인사이트
조회수 4054

제니퍼5 기능 소개

JENNIFER 5의  유용한 기능을 소개해 드리는 시간. 새로운 기능을 검토해 보시고, 지금 바로 사용해 보세요. 제니퍼소프트는 제품을 사용하는 고객이 최적의 환경에서 모니터링 할 수 있도록 하는데 최고의 가치를 두고 모든 제품을 개발합니다.제니퍼(JENNIFER)는 웹 애플리케이션 (Java EE, .NET, PHP) 시스템 모니터링을 위한 APM(Application Performance Monitoring) 솔루션입니다. 제니퍼는 경량화(Light-Weight), 실시간(Real-Time), 그리고 개별 트랜잭션 모니터링(Individual Transaction Monitoring) 등 기술기반의 '직관적인 통합 성능관리 솔루션'으로 이미 국내외 1200여 개 고객사를 통해 검증된 바 있습니다.  또한, 시대의 요구 사항인 모바일, 클라우드, 그리고 빅 데이터 시장의 온전한 모니터링 체계를 위하여, 웹 서비스 사용자 모니터링 (Web Service Real-User Monitoring), 웹 서비스 중심의 토폴로지 뷰(Web Service Topology View), 클라우드(대규모 시스템) 환경을 고려한 아키텍처, HMTL 5기반의 N스크린(N-Screen)까지도  지원하는 APM 제품입니다.웹 서비스 중심 토폴로지 뷰 (Web Service Topology View)제니퍼 토폴로지 뷰(Topology View)는 기업의 웹 서비스를 중심으로 연결된 서비스에 대한 가시성(Visibility)을 확보하는 것이 핵심 기능입니다. WAS를 중심으로 연결된 서비스(DB, 외부 연계 서비스, HTTP 등) 사이에 발생하는 트랜잭션, 즉, 구간에서 처리되는 트랜잭션까지 실시간으로 모니터링 할 수 있습니다.구간 엑티브 서비스 모니터링: 구간에서 처리되고 있는 엑티브 서비스를 실시간 모니터링 하여 병목 지점과 그 원인을 분석할 수 있습니다.X-view 연계 분석: 병목 지점이 되는 구간에서 처리되는 모든 트랜잭션에 대한 분석을 할 수 있습니다.구간 실시간 모니터링: 실시간 차트를 통해 원하는 구간에 대한 모니터링이 가능합니다.웹 서비스 사용자 응답시간 모니터링(RUM)클라이언트 구현 기술의 발전과 모바일 기기의 대중화로 인해 기업의 서비스는 복잡해졌습니다. 사용자는 모바일을 통해 언제, 어디서든 기업의 서비스를 이용할 수 있게 되었고, APM은 이제 서버사이드 영역의 모니터링뿐만 아니라, 실제 사용자의 만족도를 높일 수 있는 사용자 중심의 성능 모니터링 기능을 요구하고 있습니다. 이러한 변화에 따라 제니퍼는 프런트 앤드(Front-End) 영역의 모니터링을 위한 실제 사용자 모니터링, 즉 RUM(Real User Monitoring)을 지원합니다.이 기능은 웹 표준 조직 기구인 W3C의 Timing Navigation API를 사용하여 별도의 모듈 설치 없이 브라우저에서부터 서버사이드까지 전 영역에 대한 응답시간 측정이 가능하며, 서버와 네트워크로 이어지는 애플리케이션 수행 경로를 시각적으로 표시하여 애플리케이션 성능에 대한 깊이 있는 분석이 가능합니다.제니퍼 for CLOUD최근 IT 흐름의 큰 변화 중 하나는 클라우드(대용량 시스템)입니다. 클라우드 환경의 큰 특징은 트랜잭션의 양에 따라 하드웨어의 제약을 받지 않고 필요에 따라 서버 수를 조절하며 운영할 수 있어야 한다는 것입니다. 제니퍼는 자동감지(Agent Auto Detection), 일괄 설정(Central Configuration), 일괄 배포 (Central Deployment) 기능을 통하여 클라우드 환경에서의 애플리케이션 성능 모니터링을 지원합니다.스마트 프로파일링(Smart Profiling)제니퍼의 개별 트랜잭션의 응답시간을 활용한 엑스 뷰 기반의 분석은 이미 수많은 고객사에서 검증된 트랜잭션 모니터링 기법입니다. 하지만, 프로파일링 분석은 개발자 혹은 성능 튜닝의 전문가가 아니면 어려움을 겪는 것이 사실이었습니다. 이에 제니퍼는 누구나 쉽게 프로파일링 데이터를 분석 할 수 있는 스마트 프로파일링(Smart Profiling)을 제공합니다. 이 기능을 통해 사용자는 Method, SQL, 외부 서비스 중 응답시간이 느린 구간을 선택하여 해당 시점의 프로파일을 쉽게 분석할 수 있습니다.실시간 Connection Pool 모니터링실시간 Connection Pool 모니터링은 Instance별 Connection Pool을 실시간으로 모니터링하는 기능입니다. 이 기능은 액티브 서비스 차트와 함께 모니터링하며 액티브 서비스 점유가 주로 DB Connection에 있을 경우 Connection Pool 설정을 조정할 수 있어 서비스 성능을 개선할 수 있다는 것입니다. 다른 측면에서 DB Connection을 특정 애플리케이션에서 많이 사용할 경우 현재 Connection을 사용하고 있는 Active Service를 찾아내어 원인을 분석할 수 있습니다.실시간 애플리케이션 변경 이력 모니터링기업에서 운영하는 서비스는 수많은 고객의 요구사항을 반영하고 서비스 개선을 위해 하루에도 여러 번 애플리케이션을 변경합니다. 모니터링 관점에서 애플리케이션 변경 시점은 곧 서비스 장애가 일어날 가능성이 가장 많은 시점입니다. 그렇기에 모니터링이 가장 필요한 시점이기도 합니다. 애플리케이션 변경 감지 기능은 변경 전후의 성능 변화를 실시간으로 모니터링하고, 변경 시점에 변경된 소스코드를 추적하여 어떤 소스코드가 변경되었는지 추적할 수 있습니다.  이를 통해 개발자와 운영자 모두가 쉽고 빠르게 서비스의 변화를 감지하고 대응할 수 있는 장점이 있습니다. 엑스 뷰(X-View) > 애플리케이션 연계 분석애플리케이션 연계 분석은 검색한 X-View의 개별 트랜잭션을 기반으로 애플리케이션 단위 호출건수, 평균 응답 시간, 최대 응답 시간, 평균 SQL 시간, 평균 CPU 시간을 함께 분석할 수 있는 기능입니다.  또한 이러한 성능 수치들이 높은 애플리케이션의 개별 트랜잭션을 분석하여 성능에 영향을 미치는 애플리케이션을 튜닝 할 수 있습니다.엑스 뷰(X-View) > 연계 트랜잭션 분석제니퍼는 하나의 요청으로부터 시작된 다수의 트랜잭션 간의 상관 관계를 모니터링하거나 분석할 수 있습니다. 하나의 서버에서 처리된 서로 다른 업무 트랜잭션들을 연계할 수 있으며, 다른 서버에서 발생된 트랜잭션을 연계할 수 있습니다. 프로토콜 후킹 방식(HTTP, RMI)과 GUID를 활용한 연계방식을 지원합니다.엑스 뷰(X-View) > 자동 스택트레이스제니퍼를 포함한 대부분의 APM은 트랜잭션이 느린 원인을 분석하기 위해 매서드 프로파일링 기능을 제공합니다. 하지만 매서드 프로파일링 기능은 잘못된 설정으로 성능에 영향을 주거나 실제 느린 매서드를 찾지 못할 경우가 많습니다. 또한, 로직을 잘 알아야 하므로 성능 전문가가 아닌 이상 사용이 매우 어려운 단점이 있습니다.제니퍼는 이런 제약사항을 없애고 좀 더 쉽게 사용하기 위해 자동 스택트레이스 기능을 제공합니다.  이 기능은 성능 전문가가 아니더라도 느린 트랜잭션이 발생했을 때 해당 시점에 자동적으로 스텍트레이스(Stacktrace)를 남겨서 원인을 쉽고 빠르게 분석할 수 있습니다. 제니퍼소프트는 항상 제니퍼 고객의 모니터링 환경을 좀 더 쉽고 유용하게 할 수 있도록 고민하고 있습니다.  더 좋은 기능을 제공할 수 있도록 노력하겠습니다. 
조회수 2070

외부 서비스 이용을 장려해서 개발력을 아끼자.

2017년 목표 중 하나인 Product Management에 관한 weekly 포스팅의 네번째 포스팅입니다. 원래는 weekly 포스팅이었는데..어느덧 biweekly 포스팅이 되고 있습니다. 이번에는 제가 Product Manager로서 “팀 내부 직접 개발 vs 외부 서비스 이용”에 대해서 어떻게 생각하는지에 대해서 정리할까 합니다. 이번에도 confidential한 내용은 생략했습니다.이거 한 달이면 만들어요.제품 개발을 하다보면 Core feature는 아니지만 더 나은 사용자 경험을 위해 필요한 기능을 추가해야 하는 경우가 있습니다. 그리고 이 feature가 개발하기에 쉽지 않다고 예상되는 경우가 있습니다. 이런 상황이 오면 PM, 제품 담당자(혹은 기획자, 대표)은 내부에서 개발할지 아니면 외주를 줄 지, 아니면 외부 서비스를 이용할 지 등을 고민합니다. 그리고 판단을 돕기 위해 기획자/개발자가 모여서 이런 대화를 나눕니다.이거 다 만드는데 얼마나 걸릴 것 같아요?이거 한 달이면 만들어요.그렇습니다. 저 대화가 바로 나중에 개발자가 “내가 이걸 왜 하고 있죠?”라고 얘기하는 그 순간의 시초입니다.하지만 기간은 두 배가 걸린다.하지만 직접 개발에 들어가면 기간(UX, UI디자인 포함해서)은 점점 늘어집니다. 십중팔구 안 됩니다. 되는게 더 이상한 법이에요.헛된 꿈을 꾸었다기간이 두 배가 되는 이유는 딱 하나입니다.  우리에겐 그 분야의 전문성이 없기 때문입니다. 물론 그런 일을 한 경험이 있는 사람들은 좀 더 낫습니다. 하지만 이 사람이 파편적인 경험(혹은 기억)만 가진 경우에는 똑같습니다. 별 차이가 안 나요.-_-;일단 제품의 개발 범위 결정이 안 됩니다. 이게 가장 크리티컬한 이유입니다. 처음에는 앞단에 보이는 것만 생각하고 시작하면서 역기획으로 풀어냅니다. 하지만 기획 단계에서 고려해야 할 요소들은 점점 추가되고 이 중에서 뭘 버리고, 뭘 해야 하는지 정확한 판단이 안 됩니다. 그럴 수 있는 데이터도 적고요.  거기에 디테일하게 개발하는 과정에서 고려해야 할 요소들이 빠지는 경우도 비일비재 합니다. 추가로 각종 정책 결정 이슈도 존재합니다. 이런저런 일들이 계속 추가되고, 해보지 않은 일을 하면서 업무 효율도 떨어집니다. 그러면서 기간은 계속 늘어납니다.결국 사람은 지치고, 일은 계속 늘고, 시간을 쓰게 됩니다. 그리고 그 과정에서 진짜로 에너지를 써야 할 일에 집중을 못 하게 됩니다.그냥 외부 서비스 쓰자!푸른밤의 PM으로서 저 스스로 가지고 있는 원칙이 있습니다.(사실 이건 예전에 프라이베리 때도 지키려고 했던 노력입니다.)기회를 놓치지 않는다.팀의 시간을 헛되이 쓰지 않는다.사람들의 에너지가 낭비되게 하지 않는다.좋은 역량을 가진 사람들은 제품의 core feature에만 집중한다.기회, 시간, 사람, 돈 중에서 가장 가치 없는 것은 돈이다.위 5가지 원칙을 준수하고자 하면, 대부분의 경우 그냥 외부 서비스를 이용하게 됩니다. 예를 들어서 서버 쪽에서 약간 낭비되는 코드가 있더라도 어떤 순간에는 그냥 돈을 더 써서 서버를 늘리는 것을 선택합니다. 메일 서버를 직접 구축해서 각종 마케팅용 메일을 직접 하는 것도 좋지만 그냥 메일침프를 씁니다. 요근래 저와 대표가 함께 부산에 미팅을 다녀왔는데..이것도 비슷한 맥락입니다. 제품 내에 꽤 중요하지만 서비스의 Major급 feature라고 하긴 좀 애매한 기능을 붙여야 하는 상황이었습니다. 개발팀에서는 1개월 정도면 될 것 같다고 했지만 그것보다는 전문적으로 이 일만 하는 곳의 제품을 이용하는 것이 좋다고 판단해서 부산에서 관련 사업을 하는 팀을 찾아갔습니다.“어설프게 우리가 하는 것보다, 인생을 건 사람들의 제품을 쓰는 것이 훨씬 좋다.”는 생각을 가지고 있습니다. 특히 제가 관리하는 제품들도 이런 생각을 가진 사람들이 돈을 쓰기 때문에 운영될 수 있는 제품이라서 다른 사람들보다 거부감이 낮을 수도 있습니다.외부 서비스 선택의 기준추가로 외부 서비스를 선택할 때는 이런 기준을 가지고 판단합니다.우리가 원하는 것이 어느 수준 정도로 충족되는가: 이게 제일 중요합니다. 원하는 것이 안 채워지는데도 돈을 쓸 필요는 없습니다.ㅠ어느 정도 커스텀이 가능하고, API가 제공 범위는 어떻게 되는가: 기존 시스템과 붙이기 얼마나 편하고, 우리 개발팀이 에너지를 어느 정도로 써야 하는지를 판단하기 위해 필요합니다. 덕분에 요즘은 API 문서 읽는 것이 일입니다.-_-;;(마케터, 운영팀 등이 쓰는 경우)개발자/디자이너가 꼭 붙지 않아도 사용할 수 있는가: 전 푸른밤의 모든 사람들이 코딩을 기초적인 수준으로는 했으면 합니다만 (진짜 잘하면 SQL까지도.) 그렇지 못 한 경우가 더 많고 그 과정에 역시 에너지/기회/시간 낭비가 좀 있다고도 생각합니다. 그래서 위 조건도 꽤 중요하게 봅니다.우리가 지금 쓰고 있는 다른 외부 서비스들과 연동이 어느 정도 되는가? 직접 연동이 안 되더라도 다른 방식으로 연동할 수 있는가: 가장 중요합니다. 세상 제일 중요합니다. 저희 같이 외부 서비스 연동을 하나씩 하나씩 하다보면 어느 순간부터 매월 SaaS 툴에만 $1000 넘게 쓰게 됩니다.(정말이에요.) 일단 가장 중요한 데이터 분석 툴과 연동되는지를 봅니다. 그리고 각 부분에서 core한 툴과 연결되는지 봅니다. 예를 들어서 마케팅 오토메이션 단계에서는 유입 관련 데이터 분석 툴과 연결되는 것이 핵심입니다. 제품 관련해서 외부 서비스 쓸 때도 메인 분석툴인 GA와 어떻게 붙는지가 핵심입니다.유기적인 연결이런 복잡한 기준을 잡으면서 외부 서비스 선택을 합니다.우리가 새로 만들자.하지만 이런 힘든 과정 거쳐서 외부 서비스 선택해서 잘 사용하다가 다시 직접 개발하게 될 때도 있습니다. 커스텀의 한계가 오거나, 외부 서비스 회사가 망하거나(ㅠㅠ), 서비스의 오픈 API 범위나 정책이 바뀌거나, 의외로 이 feature의 중요도가 크거나 하면 이런 의사결정을 할 수 있지 않을까 싶습니다. 하지만 아직 제가 이런 경험을 한 적은 없어서..향후에 이런 일이 발생하면 꼭 공유하겠습니다.정리하며스타트업에서 가장 부족한 것이 뭐냐는 질문을 하면 대체로 돈과 사람이라고 답할 것 같은데요. 여기에 기회, 시간이라는 것도 변수로 추가하길 권합니다. 그러면 어떤 경우에도 내 사업의 core가 되는 일들, 내 사업의 core랑 직결되는 제품 관련 과업들, 디자인/개발 관련 과업들만 생각하게 되고 여기에만 집중하게 됩니다.물론 돈이 부족한 것도 알고 있습니다만..정말 인생을 걸고 하는 사업에서 가장 아쉬운 것은 기회와 시간이라고 생각해서 외부 서비스 주구장창 이용하는 PM 안창영이었습니다.푸른밤 안창영#푸른밤 #알밤 #개발 #운영 #개발자 #PM #업무프로세스 #인사이트 #일지 #경험공유
조회수 2868

웹 플러그인 개발기 - iframe의 재발견

채널 웹 플러그인을 개발하며 겪은 문제들과 우리 팀의 해결책을 소개합니다. 채널 웹 플러그인은 SDK의 형태로 고객사 웹사이트에 붙어서 고객이 매니저와 대화할 수 있는 인터페이스를 제공합니다. 이 글을 쓰고 있는 당시 약 2300개의 채널이 개설되었고, 하루 약 180만 명의 일반 유저가 웹사이트에 붙은 저희 플러그인을 보고 있습니다.플러그인은 고객사 웹사이트 (이하 호스트 웹사이트라고 함) 의 HTML 도큐멘트에 붙어서 실행됩니다. 이 말은 실행 환경 (자바스크립트, CSS, DOM 환경 등) 을 우리가 컨트롤하지 못한다는 것을 의미합니다. 이것이 일반적인 웹서비스와 플러그인 개발의 가장 큰 차이점이고 사실상 많은 이슈들은 이 차이로부터 기인합니다. 또 이것에 대응하기 위해 프레임워크의 선택부터 개발, 배포에 이르기까지 훨씬 신경 써야할 부분이 많았습니다. 이 글에서는 그 중 호스트 웹사이트와의 실행 환경 공유에 따른 문제들을 자바스크립트와 CSS로 나누어 나열하고 iframe 을 이용하여 해결한 과정에 대해 설명하겠습니다.채널 홈페이지에 웹 플러그인이 붙은 모습1. 자바스크립트와 관련된 이슈1-1. 네임스페이스 공유에 따른 충돌 문제브라우저에서 자바스크립트는 글로벌 네임스페이스를 공유합니다. 이 속성 때문에 플러그인에서 window 를 접근해서 수정한다던가 글로벌로 객체를 정의해서 사용하면 호스트 웹사이트에 영향을 미칠 수 있습니다. 이 문제는 코딩할 때 아래 항목을 주의하는 정도로 큰 비용 없이 방지할 수 있습니다.플러그인의 최상위 네임스페이스를 만든다.(ex. window.CHPlugin)플러그인에서 사용하는 모든 객체는 최상위 네임스페이스 아래에 정의되도록 한다.(ex. window.CHPlugin.outObject)window 객체에 접근할 때는 수정하거나 추가하는 부분이 없도록 주의한다.(ex. [removed] = function(){}와 같은 코드는 사용하면 안 됨. 기존에 [removed] 이벤트가 날아감)사용하는 라이브러리들 중에 window에 바인딩하는 것이 없는지 체크하고 있으면 직접 수정하여 모듈화한다. (ex. lodash는 기본적으로 window 에 _ 객체를 생성함)이건 사실 플러그인이 아니더라도 주의해야하는 거죠..1-2. 에러로 인한 오동작 가능성더 어려운 문제는 바로 예측하기 어려운 오동작의 가능성이 있다는 것입니다. 호스트 웹사이트에서 동작하는 자바스크립트에서 에러가 날 경우 플러그인의 동작에도 영향을 미칠 수 있으며, 반대로 플러그인에서 에러가 발생해서 호스트 웹사이트의 코드 실행을 멈출 수 있다는 것입니다. 양방향으로 영향을 미칠 수 있는 것이죠. 특히 후자의 경우는 우리의 실수로 고객사의 서비스에 피해를 끼칠 수 있으니 쉽게 넘길 문제는 아닙니다.아이디어 1: try/catch를 적절히 처리한다?이를 해결하기 위해 가장 쉽게 생각할 수 있는 방법으로는 호스트 웹사이트 쪽에서 try/catch를 적절하게 처리하도록 가이드를 하는 방법입니다. 예를 들어 플러그인 코드의 바깥 쪽에 try/catch처리를 하고 호스트 웹사이트의 자바스크립트에도 적당하게 처리를 하면 되지만 이 방법은 현실적으로 어려움이 있습니다. 우리의 타겟 고객사들은 일반 쇼핑몰들이고 이들은 대부분 개발자가 없거나 쇼핑몰 빌더를 이용해 만들어진 사이트들이기 때문에 개발력이 없는 경우가 많습니다. 또 설사 개발력이 있다 하더라도 플러그인을 붙이기 위해 가이드할 것이 너무 늘어나는 문제가 있죠.아이디어 2: 자바스크립트 실행 순서를 강제한다?생각해볼 수 있는 또 다른 방법은 호스트 웹사이트의 코드와 플러그인 코드의 실행 순서를 명확히 정해서 한 방향의 영향이라도 차단하는 것입니다. 예를 들어 플러그인 코드가 호스트 웹사이트의 코드보다 항상 먼저 실행되도록 고객사에게 가이드한다면 우리의 코드는 항상 문제 없이 실행될 것이고 호스트 웹사이트에서 에러가 발생하더라도 영향을 받지 않을 것입니다. 하지만 이 방법 역시 마음에 들지 않았는데요 양방향의 영향을 모두 차단하지는 못하기 때문입니다. 그리고 더욱 큰 문제는 플러그인은 한 번 실행되고 끝나는 단순한 스크립트가 아니라 계속해서 실행이 되는 애플리케이션이기 때문에 사실상 소용이 없습니다.2. CSS와 관련된 이슈채널 웹 플러그인은 UI도 포함합니다. 플러그인의 DOM이 호스트 웹사이트에 붙어있기 때문에 플러그인의 스타일을 정의하는 CSS도 호스트 웹사이트에 Inject 되어야합니다. 호스트 웹사이트의 CSS와 플러그인의 CSS가 같은 스코프에 존재하기 때문에 우리가 의도한 스타일이 제대로 표현되지 않을 가능성이 있습니다. 실제로 이 문제는 런칭 초기에 우리를 가장 괴롭혔던 문제입니다. 쉽게 생각해볼 수 있는 방법은 아래와 같습니다.플러그인의 CSS에 네임스페이스를 둔다.(플러그인 CSS가 호스트 웹사이트 CSS에 주는 영향을 차단함)CSS 의 우선순위를 이해하고 플러그인 CSS의 우선순위가 항상 높도록 처리한다. (CSS Specificity 링크 참조)하지만 위처럼 처리하더라도 모든 경우에 대해 해결이 되는 것은 아닙니다. 주된 이유는 우리가 개발을 할 때 모든 CSS 속성을 정의하지 않기 때문입니다. 플러그인에서 정의하지 않은 속성을 호스트 웹사이트에서 사용한다면 호스트 웹사이트의 스타일이 적용될 것입니다. 또 특수한 경우이긴 하지만 만약 호스트 웹사이트에 !important 가 적용되어 있다면 그 속성이 덮어씌워지게 됩니다.!important는 사용하지 맙시다..ㅜ아이디어: 스타일 Normalizing?여기에서 의미하는 Normalizing은 모든 DOM 엘리먼트에 가능한 모든 CSS 속성의 기본값을 정의하는 것을 의미합니다. 크로미움을 기준으로 모든 CSS 속성 목록은 이 곳을 참조하시면 됩니다. 이것을 바탕으로 normalize.css를 만들어 적용했습니다.이 방법을 적용한 이후로는 스타일이 오버라이딩되는 문제는 어느 정도 해결되었습니다. 물론 !important에 대한 대응은 여전히 되지 않지만요. 그런데 예상하지 못한 부작용이 발생했는데 첫번째는 디버깅할 때 크롬 인스펙터가 도저히 사용하지 못할 정도로 느리다는 것입니다. 두번째는 CSS가 inheritance 가 안 되고 기본 엘리먼트 셀렉터의 우선순위가 높아서 직접 코딩해야하는 CSS가 2~3배는 길어지는 불편함입니다. 위 두 이유로 개발 피로도가 상당히 높아져서 머지 않아 다른 방법을 알아보게 되었습니다.3. iframe 도입위에 나열한 문제들을 해결할 수 있는 아이디어로 iframe을 리서치하게 되었습니다. 사실 iframe은 최근 웹서비스에서는 잘 사용하지 않기도 하고, 보통은 사용하지 않는 것을 권장하기도 하죠. 따라서 저희 팀에서도 처음에는 고려사항이 아니었는데요 우리와 유사하게 채팅 인터페이스를 제공하는 인터콤에서 iframe 을 적용한 것으로부터 아이디어를 얻어왔습니다.원래 목적에 맞게 사용하지 않으면 독이 됩니다.iframe은 HTML 도큐멘트 안에서 또 다른 도큐멘트를 임베드합니다. iframe 내에 있는 도큐멘트는 호스트 도큐멘트와 자바스크립트 스코프가 분리되어 있고, CSS가 적용되는 스코프 역시 분리되어 있습니다.이런 속성 때문에 위에 나열한 문제들을 원천 차단할 수 있습니다. 자바스크립트 스코프가 분리되어 있기 때문에 글로벌 네임스페이스에 접근해도 호스트 웹사이트에는 전혀 영향이 없고, 자바스크립트의 에러로 인해 다른 쪽 자바스크립트까지 실행을 멈추는 오동작을 막을 수 있습니다. CSS 역시 Normalizing 을 하지 않더라도 호스트 웹사이트와 플러그인은 완벽히 분리가 됩니다.4. iframe 의 단점iframe을 도입하여 1, 2번에 나열한 문제들은 해결했지만 그에 따른 작은 문제들도 발생했습니다. 첫번째는 iframe도입 시 가장 먼저 고민해야할 부분인데 바로 3rd-party cookie 문제입니다. iframe 안에서 로드되는 도큐멘트는 3rd-party 컨텐츠로 인식합니다. IE에서는 기본 설정이 3rd-party cookie 허용을 하지 않기 때문에 쿠키를 사용해서 인증을 구현한 경우 문제가 될 수 있습니다.두번째는 도큐멘트가 분리됨에 따라 발생하는 코딩상의 여러 불편함들입니다. 여기에서는 범위를 벗어나 더 자세하게는 설명하지 않겠지만 도큐멘트가 분리되니 조금 더 신경써야할 것들이 있었습니다.저희 팀의 경우 쿠키 인증 방식이 아닌 토큰 형태의 인증도 지원을 하고 있었기 때문에 첫번째는 크게 문제되지 않았고 두번째 문제도 얻는 이득에 비하면 불편함을 감수하는 편이 훨씬 좋다는 판단이 들어서 도입을 결정했습니다.마무리플러그인 개발을 시작할 당시에 우리 팀은 웹 SDK 형태의 프로젝트 개발 경험이 없었습니다. 리서치를 해도 플러그인 개발과 관련된 아티클이나 리소스 그리고 보일러플레이트 프로젝트도 많지 않았습니다. 프레임워크, 아키텍쳐를 선택하는 것부터 프로젝트를 구성하는 것부터 개발, 배포 및 운영에 이르기까지 일반적인 웹서비스를 개발할 때와 조금 다른 고민들을 해왔던 것 같습니다. 앞으로 저희가 해 온 고민을 공유하려고 합니다. 저희와 같은 플러그인, SDK 형태의 제품을 개발하고 계신 분들에게 도움이 되었으면 좋겠습니다.#조이코퍼레이션 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 1066

[인터뷰] Clara의 인턴 직무 인터뷰 제3화 _iOS developer 민트를 만나다

안녕하세요:)인턴들의 하루하루를 전해드리는 클라라입니다오늘은 저번 시간에 말씀드렸던 Tech unit의  미녀 인턴과의 인터뷰를 진행했습니다!그녀의 이름은 상쾌한 Mint!본명에 '박하'가 들어가서 민트라는 이름을 지었다고 하네요~센스 만점이죠?이름처럼 상큼한 민트와의 인터뷰바로 만나보시죠!고고고☞Q. 안녕하세요 민트, 간단한 자기소개와 요즘 어떤 일을 하시는지 소개해주세요~M.네! 안녕하세요~ 저는 iOS 개발을 하고 있는 개발자입니다. 많은 분이 개발자가 코딩을 하고 이런 것들은 어렴풋이 알고 계실 텐데, 지금 저는 iOS 앱에서 개선할 부분을 조사하고 더 잘 구현하고자 열심히 개발하고 있습니다. 아직은 주로 UX/UI 의 개선에 집중하고 있고, 하는 일보다 배우는 일이 더 많은 것 같네요!M.네! 안녕하세요~ 저는 iOS 개발을 하고 있는 개발자입니다. 많은 분이 개발자가 코딩을 하고 이런 것들은 어렴풋이 알고 계실 텐데, 지금 저는 iOS 앱에서 개선할 부분을 조사하고 더 잘 구현하고자 열심히 개발하고 있습니다. 아직은 주로 UX/UI 의 개선에 집중하고 있고, 하는 일보다 배우는 일이 더 많은 것 같네요!Q. 개발자는 그 안에서도 하는 일이 다양하다고 들었어요. 요즘 민트의 주 업무에 대해 더 자세하게 설명해주실 수 있을까요?M.그럼요~지금 저는 아이폰의 OS인 iOS에 특화된 방식으로 개발하는 네이티브 방식을 활용하고 있어요. 네이티브 방식이란 안드로이드나 iOS와 같은 특정 OS에 최적화된 방식으로 앱을 개발하고 있다는 뜻입니다. 그렇지 않은 개발 방식도 있거든요! 모바일 웹페이지를 앱처럼 꾸며서 보여주는 등 여러 방식이 있습니다.M.그럼요~지금 저는 아이폰의 OS인 iOS에 특화된 방식으로 개발하는 네이티브 방식을 활용하고 있어요. 네이티브 방식이란 안드로이드나 iOS와 같은 특정 OS에 최적화된 방식으로 앱을 개발하고 있다는 뜻입니다. 그렇지 않은 개발 방식도 있거든요! 모바일 웹페이지를 앱처럼 꾸며서 보여주는 등 여러 방식이 있습니다.iOS개발은 안드로이드 앱 개발과 비교했을 때 제약 조건도 많고, 생소한 스타일의 개발 언어를 써야 하는 게 어려워요. 하지만 동시에 iOS 특유의 사용감과 안정성이 매력이에요. 그리고 아까 UX/UI라는 용어를 사용했는데 이는 User Experience와 User interface의 약자, 즉 사용자 경험을 의미합니다. 저희는 사용자 경험을 더욱 편리하게 하는 쪽으로 앱을 유지 보수하는 일을 하고 있는 거예요. 미미박스는 고객을 소중히 여기기 때문에 이런 UX/UI에 있어서도 많은 신경 쓰고 있습니다.Q. 그럼 개발자로서 미미박스는 어떤 장점을 가지고 있나요? 저희 회사 자랑 좀 해주세요!!!M. Q. 그럼 개발자로서 미미박스는 어떤 장점을 가지고 있나요? 저희 회사 자랑 좀 해주세요!!!M. 음, 저는 미미박스가 개발자의 의견을 듣고 반영하고자 하는 회사임을 가장 말씀드리고 싶어요! 미미박스 개발팀에서는 디자인팀+앱 개발팀+PM 팀, 세 팀이 모여서 정기적으로 회의를 하고 있습니다. 이 회의를 스크럼이라고 하는데, 프로젝트와 관련된 모든 사람들이 모여서 계획하고 피드백하는 것이죠.이걸 하면 좋은 이유는 개발을 담당하는 사람이 직접 기획에도 참여할 수 있다는 거예요. 보통 한국에서 개발 직무는 보통 상명하달식으로 이루어진다고 해요. 위에서 개발이라는 직무를 이해하지 않고 일방적으로 일정을 정해서 던져주는 거죠. 그런데 미미박스는 그렇지 않고 자신의 생각을 내고 반영할 수 있어서 좋아요.   Q. 오오오~ 그렇군요! 민트와 저는 자리가 멀잖아요. 업무적인 것과 별개로, Tech 유닛의 분위기는 어떤가요??? M.저희 유닛 분위기 완전 좋아요! 그리고 저는 사수 분들이 똑똑하셔서 배울 점이 많다는 생각으로 회사를 다니고 있어요. 서로 돕고 정보를 공유하는 분위기여서 무려 시니어 분들이 제게 본인의 코드를 다 오픈해주세요. 근데 그 코드가 다 샘플 코드의 수준이고요!(샘플 코드란 일종의 '교과서'같은 존재로, 코딩의 수준이 아주 높다는 뜻입니다.)iOS 직무는 신입의 진입장벽이 높거든요. 사전 지식 없이는 독학으로 따라잡을 수 없는 부분이 많기 때문에 코드와 그에 대한 설명을 들을 수 있다는 건 엄청난 거죠. 마치 최고의 영업사원이 자신의 영업 비밀을 공개해주는 그런 경우라고 할까요? 애플 워치의 코드까지 알려주는 회사, 흔치 않습니다! (엄지 척)  민트에게 몰려든 고양이들~Q. 와우! 애플 워치도 코딩을 하는 거군요. 제겐 너무나 신세계인데요...!  이제 마지막 질문입니다. 여성 개발자로서 강점은 무엇일까요?M.저는 사실 특정 산업 군이나 성별에 구애받지 않는 작업을 한다고 생각해요. 그럼에도 화장품을 온라인으로 사 본 개발자과 그렇지 않은 개발자는 차이가 있다고 생각해요. 여성이 주 고객층인 뷰티 쇼핑몰에 대한 경험이 쌓이면 새롭고 좋은 UX에 대한 아이디어도 더 잘 나오지 않을까 싶네요.  민트와의 인터뷰 어떠셨나요?저 클라라처럼 컴알못이거나개발자의 하루가 궁금하셨던 분들은 이번 인터뷰가 큰 도움이 되셨으면 좋겠습니다.민트를 마지막으로 인턴의 생활을 엿볼 수 있는 클라라의 인터뷰가 마무리 되는데요 :)미미박서의 일과 삶에 대해서 조금이나마 더 알아가셨다면,그래서 '미미박스에서 일해보고 싶다'는 마음이 스멀스멀 생기셨다면!클라라는 그것만으로도 보람찰 것 같습니다.그럼 또 미미박스의 소식으로 찾아올게요~
조회수 2838

Radix? Redis!

얼마전부터 antirez twitter에서 radix tree 관련 트윗이 올라왔습니다. 얼마 지나지 않아 antirez가 radix tree를 구현한 rax 프로젝트를 공개하고 redis의 cluster hash_slot의 저장구조를 radix tree로 수정 되는것을 보았습니다.그동안 antirez의 코드 읽으면서 배우는 게 많았고, 자료구조에 관심이 많아서 살펴보기 시작했습니다. radix tree를 왜 구현 했는지, 어떻게 구현쟀는지 알아보고 radix tree를 redis에 어떻게 적용하였는지도 알아보겠습니다.antirez는 redis의 hash-slot -> key 구조에서 중복으로 인한 메모리 사용을 줄이기 위해 radix tree 를 만들었다고 합니다. 이 포스트에선 rax를 적용시킨 redis cluster로 이야기를 진행 하겠습니다.“현재는 hash-slot -> key에만 사용되지만 추후에는 다양한 곳에 사용 예정”이라는 트윗redis cluster?redis에는 cluster 기능이 있습니다.6대 이상의 redis 노드를 cluster 구성하면(최소 leader 3대, follower 3대 구성해야 cluster 가능) 16384개의 hash_slot이 노드 갯수에 맞게 분배가 됩니다. 즉 3대의 leader로 cluster 구성하면 각각의 leader는 0 ~ 5460, 5461 ~ 10922, 10923 ~ 16383 hash_slot을 나눠 가집니다.cluster 구성 후 client가 데이터 저장/삭제/조회 명령어를 redis server에 전송할 때 마다 key의 hash값을 구하고 어떤 leader hash_slot에 포함되는지 찾습니다.# example 127.0.0.1:7000> set hello world # hash_slot = crc16("hello") & 0x3FFF 계산된 값이 현재 접속한 leader의 hash_slot 범위에 있다면 그대로 실행 되지만 다른 leader의 hash_slot 이라면 에러를 발생하고 다른 leader로 이동하라고 힌트를 줍니다.cluster 구성 후에 노드를 추가 하거나 제거 할 경우 각 leader의 hash_slot을 재분배 하고, hash_slot에 맞게 key도 재분배 되어야 합니다. 단순하게 생각하면 leader의 hash_slot 재분배한 후 모든 key를 재계산하고 hash_slot에 맞는 leader에 할당 하는 겁니다.[현재까지 저장된 keys].forEach(v => { hash_slot = crc16(v) & 0x3FFF // leader에 할당된 hash_slot에 맞게 분배 }) 하지만 antirez는 redis Sorted set 데이터 타입의 구현체인 skiplist 을 이용하여 문제를 풀었습니다. skiplist는 member와 score를 저장하고, score를 기준으로 정렬합니다. skiplist의 member에는 key를 저장하고 score에는 key의 hash_slot을 저장합니다.(변수명 slots_to_keys)slots_to_keys 정보는 cluster 구성된 모든 노드가 저장합니다. 이후 재분배가 필요해지면 16384개 hash_slot을 leader 갯수에 맞게 재분배 하고 slots_to_keys에 저장된 “key:hash_slot” 정보를 가지고 해당 hash_slot의 key를 조회 및 재분배 합니다. 즉 slots_to_keys에 이용하여 재분배시 발생하는 계산을 없앤것입니다.잘 했구만 뭐가 문제냐?redis에 key가 추가/삭제 될때마다 slots_to_keys에 데이터가 저장되고 지워집니다. redis에 저장되는 key 갯수가 증가 할수록 slots_to_keys의 크기도 커짐을 의미 합니다.(※ 메모리 사용량)또한 leader 갯수에 맞게 16384개 hash_slot을 leader에 재분배하고, 각 hash_slot에 맞는 key를 찾고 할당 합니다. 예를들어 slots_to_keys에서 score 0인(hash_slot 0을 의미) member를 조회해서 0번 hash_slot에 할당, score 1인 member를 조회해서 1번 hash_slot에 할당 하는 방식으로 0 ~ 16383 hash_slot을 진행합니다.앞에서 말한 hash_slot에 속한 key를 조회 하는 GETKEYSINSLOT 명령어가 있는데 여기에 이슈가 있습니다.cluster GETKEYSINSLOT slot count # slot: hash_slot 번호 # count: 특정 hash_slot에서 조회할 key 갯수 # example 127.0.0.1:7000> cluster GETKEYSINSLOT 0 3 # 0번 hash_slot의 key를 3개 조회한다. "47344|273766|70329104160040|key_39015" "47344|273766|70329104160040|key_89793" "47344|273766|70329104160040|key_92937" 사용자가 특정 hash_slot에 몇개의 key가 저장 되었는지 모르기때문에 count에 Integer.MAX 를 대입하는데, redis는 hash_slot에 실제로 저장된 key 갯수와는 상관없이 client가 전달한 count만큼의 메모리를 할당합니다.} else if (!strcasecmp(c->argv[1]->ptr,"getkeysinslot") && c->argc == 4) { /* cluster GETKEYSINSLOT */ long long maxkeys, slot; unsigned int numkeys, j; robj **keys; // ... 명령어의 4번째 인자를 maxkeys에 할당, 즉 사용자가 입력한 count if (getLongLongFromObjectOrReply(c,c->argv[3],&maxkeys,NULL) != C_OK) return; // ... keys = zmalloc(sizeof(robj*)*maxkeys); numkeys = getKeysInSlot(slot, keys, maxkeys); addReplyMultiBulkLen(c,numkeys); for (j = 0; j < numkeys>zmalloc maxkeyscluster GETKEYSINSLOT unnecessarily allocates memory그래서 메모리도 적게 차지하면서(압축 가능) key와 key의 hashslot을 효율적으로 저장 및 조회가 가능한 자료구조가 필요했고 antirez는 radix tree를 선택합니다.※ 뜬금 없는데 2012년, redis 자료형에 Trie를 추가한 P/R이 생각났습니다.radix tree 구현한 rax 알아보기시작하기전 radix tree (Wikipedia) 위키 페이지의 그림을 보고 감을 잡은 후에 아래를 보시면 잘 읽힙니다.자! 이제부터 rax의 주석과 코드를 보면서 어떻게 구현됐는지 알아보겠습니다.Noderax의 노드 구성은 다음과 같습니다.typedef struct raxNode { uint32_t iskey:1; /* Does this node contain a key? */ uint32_t isnull:1; /* Associated value is NULL (don't store it). */ uint32_t iscompr:1; /* Node is compressed. */ uint32_t size:29; /* Number of children, or compressed string len. */ unsigned char data[]; } raxNode; 노드의 정보를 담고있는 32 bit(iskey, isnull, iscompr, size)와 key/value 그리고 자식 노드의 포인터를 저장하는 unsigned char data[]가 있습니다. 특이한 점은 key/value를 동일한 노드에 저장 하지 않고 key가 저장된 노드의 자식 노드에 value를 저장합니다.※ 사진 출처위 그림을 예로 32 bit 정보가 어떤걸 의미하는지 알아보겠습니다.iskey는 노드가 key의 종착역(iskey:1)인지 중간역(iskey:0)인지 나타내는 flag입니다. 1, 3 노드는 iskey:0 이고 2, 4, 5, 6, 7 노드는 iskey:1이 됩니다.isnull은 value의 null 여부를 표시합니다. unsigned char data[]에 key/value 그리고 자식 노드의 포인터를 저장하므로 value를 찾으려면 계산이 들어갑니다. 불필요한 연산을 줄이기 위해 만든 필드 같습니다.Trie는 각 노드에 한글자씩 표현 하지만 Radix는 압축을 통해 한 노드에 여러 글자 표현이 가능합니다. 이를 나태내는 플래그 iscompr 입니다. 노드가 압축된 노드(iscompr:1)인지 아닌지(iscompr:0)를 나타냅니다.size는 iscompr 값에 따라 의미가 다릅니다. iscompr이 1이면 저장된 key의 길이를 의미하고 iscompr이 0이면 자식노드의 갯수(저장된 key의 갯수)를 의미합니다.위 4개 정보를 이용해서 한 노드의 크기를 구하는 코드는 아래와 같습니다.#define raxNodeCurrentLength(n) ( \ sizeof(raxNode)+(n)->size+ \ ((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \ (((n)->iskey && !(n)->isnull)*sizeof(void*)) \ ) ※ 노드에 value 주소를 저장하거나, 마지막 자식 노드 포인터를 알고 싶을때 사용합니다.FindraxLowWalk 함수를 이용해 key가 존재 하는지 판단합니다.size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode **stopnode, raxNode ***plink, int *splitpos, raxStack *ts) rax에 “ANNIBALE” -> “SCO” -> [] 로 저장 되어있을때 어떤 값을 리턴하는지 알아보겠습니다.*s 가 “ANNIBALESCO”이고 len이 11 인 경우# splitpos: 0, return value: 11 "ANNIBALE" -> "SCO" -> [] ^ | *stopnode *s가 “ANNIBALETCO”이고 len이 11인 경우# splitpos: 0, return value: 9 "ANNIBALE" -> "SCO" -> [] ^ | *stopnode *s의 길이 len과 return value가 같다면 rax에 key가 존재하는 것입니다. *s의 길이 len과 return value가 다른 경우 어디까지 매칭됐는지 보여주는 return value와 어떤 노드에 어디까지 일치했는지 표현하는 *stopnode, splitpos를 통해 추가 정보를 얻을수 있습니다.InsertraxLowWalk 함수를 이용해서 저장할 위치를 찾습니다. (*stopnode, splitpos, return value)1번에서 구해진 데이터를 이용해서 새로운 노드 생성 및 링크를 연결합니다.rax에 “ANNIBALE” -> “SCO” -> [] 상태에서 “ANNIENTARE”를 저장하는 과정입니다.1. raxLowWalk 함수를 이용하여 저장할 위치 탐색 splitpos: 4, return value: 4 "ANNIBALE" -> "SCO" -> [] ^ | *stopnode 2. *stopnode, splitpos 데이터를 이용하여 노드 분리 "ANNI" -> "B" -> "ALE" -> [] 3. iscompr: 0인 노드 "B"를 기준으로 새로운 key 저장 ("B"와 "E"는 같은 노드) |B| -> "ALE" -> [] "ANNI" -> |-| |E| -> "NTARE" -> [] RemoveraxLowWalk 함수를 이용해서 저장할 위치를 찾습니다. (*stopnode, splitpos, return value)1번에서 구해진 데이터를 이용해서 노드 제거 및 compress가 가능다면2가지 경우가 있습니다.마지막 노드만 iskey: 1이고, 연속으로 iscompr:1인 노드가 된 경우마지막 노드만 iskey: 1이고, iscompr:1 -> iscomplr:0 -> iscomplr:1 노드 구조가 된 경우입니다.첫번째 경우를 알아 보겠습니다. rax에 “FOO” -> “BAR” -> [] 상태에서 “FOO”를 지우는 과정입니다.1. raxLowWalk 함수를 이용하여 저장할 위치 탐색 splitpos: 3, return value: 3 "FOO" -> "BAR" -> [] ^ | *stopnode 2. 해당 key 삭제, 여기서는 자식노드가 있으므로 노드 삭제는 하지 않고 노드의 iskey: 0으로 세팅 "FOO" -> "BAR" -> [] 3. compress가 가능한 경우 진행 "FOOBAR" -> [] 두번째 경우를 알아 보겠습니다.0. "FOOBAR"와 "FOOTER"가 저장된 상황입니다. FOOTER를 지우는 경우입니다. |B| -> "AR" -> [] "FOO" -> |-| |T| -> "ER" -> [] 1. raxLowWalk 함수를 이용하여 저장할 위치 탐색 splitpos: 0, return value: 6 |B| -> "AR" -> [] "FOO" -> |-| |T| -> "ER" -> [] ^ | *stopnode 2. 해당 key 삭제 "FOO" -> "B" -> "AR" -> [] 3. compress가 가능한 경우 진행 "FOOBAR" -> [] cluster 정보는 어떻게 저장되나?기존 skiplist 자료구조를 이용했던게 어떻게 변경 되었는지 알아보겠습니다.server.cluster->slots_keys_count[hashslot] += add ? 1 : -1; if (keylen+2 > 64) indexed = zmalloc(keylen+2); indexed[0] = (hashslot >> 8) & 0xff; indexed[1] = hashslot & 0xff; memcpy(indexed+2,key->ptr,keylen); if (add) { raxInsert(server.cluster->slots_to_keys,indexed,keylen+2,NULL,NULL); } else { raxRemove(server.cluster->slots_to_keys,indexed,keylen+2,NULL); } 먼저 slots_keys_count 변수를 이용하여 각 hash_slot의 key 갯수를 저장합니다.그리고 key는 hash_slot(2 byte) + key, value는 NULL로 rax에 저장하여 특정 hash_slot에 속한 key 조회를 쉽게 만들었습니다.마치며rax 구현과 rax가 어떻게 redis에 적용됐는지 보면서 오랜만에 재밌게 코드를 읽은것 같습니다. 개인적으로 데이터 관련 유용한 무언가를 만드는게 목표인데, 이런 좋은 코드들을 하나 둘씩 제것으로 만드는것도 과정이라 생각하며 진행했습니다.앞으로 rax가 redis에서 어떻게 쓰일지 흥미롭고, Redis를 Saas 형태로 제공하는 업체들이 언제 적용할지도 궁금합니다.긴 글 읽어주셔서 감사합니다.cluster, rax 관련 antirez twitterRedis cluster Insertion cluster Issuesame amount data hash table vs radix treehashset + ziplist -> radix tree + listpack 1/5replace Hashset with Radix treeraxNode에서 사용한 flexible memberflexible memberrax 를 이용한 Redis Streams(2017.12.17일 업데이트)Redis Stream#잔디 #토스랩 #JANDI #기술스택 #도입후기 #Redis #인사이트
조회수 3353

개발자 커리어 전환기 2 | 3시간 만의 퇴사 결정, 비전공자로 개발에 뛰어들다.

Q) 안녕하세요 Juan Carlos(환 까를로스)님 자기소개 부탁드려요.네 안녕하세요. 지금 immersive 6기에서 개발자가 되기 위해 열심히 공부하고 있는 환 까를로스라고 합니다. 어쩌다보니 immersive 6기에서 전문 네비게이터로 생활하고 있어요.(웃음) 네비게이터는 페어프로그래밍을 할 때 드라이버가 코딩을 할 수 있도록 큰 그림을 그려주는 거라고 생각하시면 되요. 페어와 같이 코딩을 하면서 Immersive를 헤쳐나가고 있습니다.Q) 코드스테이츠 오시기 전에는 어떤 일을 하셨었나요?해외영업을 했습니다. 이 일을 선택한 이유는 조금 특별해요. 제가 취준생이었을 때 회사를 여러 곳을 지원을 했었습니다. 지원한 기업에서 합격 통보를 받았죠. 근데 막상 그 기업에 입사하려고 보니까 지방에서 근무를 해야 하는 거예요. 그전까지는 이런 것들을 생각도 안 하고 있다가, 막상 닥치니까 곰곰이 생각하게 되었어요.'내가 서울을 떠나서 잘 살 수 있을까?' 지방에서 산다는 거에 대해서 크게 생각하고 있지 않았었는데, 막상 닥치니까 고민이 많이 되더라구요. 제가 서울 토박이인데, 고향을 떠나서 사는 거는 제가 너무 힘들 것 같아서 포기하고 지금 현 직장(지금은 퇴사를 했죠)에 다니게 된 거예요. 그리고 제가 공대 출신인데 공대 출신이 서울에서 직장을 잡으려면 영업 밖에 없더라구요. 그래서 영업직을 선택했었습니다.Q) 그럼 직장을 나오게 된 계기가 있으신가요?새로운 것을 수용할 생각이 없는 경직된 조직문화가 너무 안 맞았어요. 저는 신입을 뽑는 이유는 조직이 시장의 흐름이나 세대의 변화에 맞춰 변하기 위해서라고 생각해요. 근데, 전에 팀은 변할 생각을 안 하더라고요. 야근까지 해가면서 업무개선을 해도 기존 방식을 고수하자는 피드백이 계속되니 열정이 사라지는 것을 느꼈죠. 제가 4년 정도 다녔는데, 퇴사를 고민하고 3시간 만에 결정하고 사표를 내고 나왔어요.저는 뭔가 다양한 경험을 하고 제 스스로가 발전하는 걸 좋아하는데, 발전한다는 느낌이 없었죠. 부서를 여러 곳으로 옮긴 이유도 제가 정확히 뭘 좋아하는지 모르니까 이것저것 해보면 알지 않을까 생각했어요. 영업 파트에서 일하면서도 기획부터 경영지원까지 다양한 일을 맡았었죠.Q) 3시간이면 정말 짧네요! 보통은 여러 번 고민하기 마련인데요. 그럼 퇴사하시고 나서는 무엇을 하셨나요?음... 사실 퇴사하고 나서 제가 맡았던 고객들이 경쟁사로 이직할 수 있게 도와주겠다고 하셔서 고민을 많이 했어요.  근데, 이왕 퇴사했는데 새로운 걸 해보고 싶었어요. 한 군데 계속 있으면 뭐랄까.. 나태해지는 것 같아서요.- 다른 분야의 직장을 잡으신 건가요?일단은 여행 가야지라고 생각해서, 스페인으로 떠났어요.  첫 번째로는 스페인의 순례길을 가기로 했죠. 1000km 정도 되는 길을 걸었던 것 같아요. 순례길을 걸으면서 다양한 사람들을 만나고 생각도 정리도 좀 하고 그랬어요. 거기에는 전 세계 퇴사한 사람이 다 모이는 것 같아요. 숙소에서 만난 친구들에게 물어보면 죄다 회사를 퇴사하고 왔다고 하더라구요(웃음) 그리고 그곳에서 개발자가 돼야겠다는 마음을 먹었습니다.Q) 어떤 경험을 하셨길래 그곳에서 개발자가 돼야겠단 마음을 먹으셨나요?먼저 이 얘기를 해야 하겠네요. 사실 제가 여행경비가 이렇게 많이 들지 몰랐어요. 순례길을 여행하다가 돈도 떨어져 가는데 직업이 있는 채로 순례길을 도는 사람들을 만나게 된 거예요. 세 명을 만났는데, 세 명 다 소프트웨어 엔지니어였습니다. 처음에는 브라질 개발자를 만났어요. 그때까지만 해도 별생각이 없었죠. 다음으로는 러시아 개발자를 만났습니다. 러시아 개발자 친구를 보면서 아 이런 게 디지털 노마드구나라는 생각을 갖게 되었죠. 그리고 마지막으로 스페인 개발자 친구를 만나니까 정말 개발자라는 직업이 부럽게 느껴지더라구요. Q) 디지털 노마드를 보고 개발자가 돼야겠단 결정을 하신 거네요! 그럼 코드스테이츠를 선택해주신 이유가 있으신가요? 아까 제가 생각보다 여행 경비가 많이 드는지 몰랐다고 했잖아요. 순례길만 여행하는데도 여행 경비가 다 떨어진거에요(웃음) 그래서 어쩔 수 없이 세계 여행의 꿈을 접고 한국으로 오게 되었죠. 그리고 한국으로 돌아오는 비행기 안에서 개발자가 되기로 결심을 했습니다. 내가 여행을 다니고 하고 싶은 것을 하면서도 일도 하고 그게 너무 좋아 보이는 거에요. 물론 한국의 현실은 많이 다르겠지만 그래도 개발자라면 가능하지 않을까라고 생각을 했습니다. 그리고 그 비행기에서 핸드폰으로 코딩 관련해서 검색을 하다가 코드스테이츠를 알게 되었어요. 알아보니까 교육철학도 좋고 저에게도 괜찮은 방식을 것 같아서 그 비행기 안에서 바로 결정을 하게 되었습니다. 퇴사할 때와 마찬가지로 일사천리로 결정을 했습니다.- 비행기 안에서 모든 결정이 이루어졌네요! 3시간 만에 퇴사를 결정하신 것 같이요!뭐 망설일 이유가 있나요. 자신감과 결단력 그게 제 장점이니까요(웃음)Q) 그럼 이제 Immersive 얘기를 해볼게요. Immersive에서의 생활은 어떠세요?생각했던 것보다 여유가 있어서 좋아요. 그전에는 되게 불안하고 빡빡하고 그럴 것 같은데 막상 해보니까 할만하더라고요. 그리고 일단 사람들이 너무 좋아요. 같이 지내는 사람들이 좋으니까 Immersive도 할만한 것 같아요.Q) 그러면 지금 Immersive에서는 어떤 것을 배우고 있나요?서버를 배우고 있어요. 프론트 쪽 하구요. 프로젝트를 하고 적용을 해봐야 완전히 내 것으로 만들 수 있을 것 같아요. 역시 직접 적용을 해봐야 정확히 알 수 있을 것 같습니다.서버를 배우고 있어요. 프론트 쪽 하구요. 자바스크립트라는 언어의 다양한 문법을 매일 체험해보고 있어서, 매일매일이 새롭습니다. (뭔가 이해할 만 하면 다른걸 배워서..) 프로젝트를 해봐야 완전히 내것으로 만들 수 있을 것 같아요.Q) 앞으로 어떤 개발자가 되고 싶으세요?거창하게 세상을 바꾸는 개발자! 이런 건 제 스타일은 아니에요(웃음) 저는 제가 하고 싶은 것을 하는 개발자. 만들고 싶은 것을 만드는 개발자가 되고 싶어요. 세상을 바꾸는 개발자도 내가 좋아하는 것, 내가 하고 싶은 것, 내가 만들고 싶은 것을 만드는 개발자가 되었을 때 가능하지 않을까요?Q) 프로젝트를 곧 하게 될 텐데 어떤 프로젝트를 하고 싶으신가요?제 경험에 기반한 프로젝트에요. 우리는 회사에서 주는 돈 그냥 받잖아요. 제가 회사를 나오고 받았던 돈들을 확인해보니 제대로 받지 못했다는 것을 알았어요. 그래서 사람들이 노동의 정당한 보상을 알고 받을 수 있도록 도와주는 프로그램을 만들고 싶어요. 주변만 봐도 대부분의 사람들이 이런 문제로 인해 문제를 가지고 있다고 생각해요.Q) 1년 후에 개발자가 되었다고 생각하면 어떤 모습일까요?개발자가 될 수 있을까요?(웃음) 아마 1년 후엔 야근에 쩔어있지 않을까요? 저는 이게 내 일이다라는 생각을 하면 엄청 파고드는 스타일이거든요. 개발자로 처음 들어간 직장에 남아 있거나 이직을 하고 있을 것 같아요. 사실 저는 계획을 잘 안 세우거든요. 그러니까 아무 준비 없이 퇴사하고 개발을 배우고 있죠. 설마 굶어죽기야 하겠어요?Q) 마지막으로 하고 싶은 말이 있나요?제가 퇴사하면서 방 정리도 같이 하게 됐어요. 정리를 하다 보니까 우연찮게 제 학창시절 생활기록부를 보게 되었습니다. 생활기록부에 장래희망을 적는 칸이 있잖아요. 근데 제가 깜짝 놀란 게 거기에 중학교 때부터 고등학교 때까지 줄곧 프로그래머로 적혀있던 거에요. 그동안 까맣게 잊고 살았는데 신기했어요.그리고 또 생각을 해보니까 대학교 때도 제가 컴공과는 아니지만 공대라서 C++을 해야했는데 그 과목에서 처음으로 A+을 받은 기억이 나더라구요. 이런 생각이 들면서 결국 나는 프로그래머를 선택할 운명이었나? 이런 생각도 들고. 결국에는 돌아돌아 이 길로 온 것 같아요. 그래도 돌아왔다고 해서 늦었다거나 아쉽지는 않아요. 제가 지금까지 걸어온 길이 분명히 프로그래밍을 하는데 도움이 된다고 생각하고 있으니까요.네 지금까지 환 까를로스님과의 인터뷰를 진행했었는데요. 정말 비하인드스토리가 엄청나네요. Immersive 성공적으로 수료하시고 원하시는 개발자가 되기를 바랍니다. 앞으로도 다양한 스토리를 가진 Immersive 수강생분들의 이야기로 찾아뵙겠습니다.
조회수 1916

Docker, NodeJS, Nginx! 너로 정했다!

편집자 주아래와 같이 용어를 표기하기로 저자와 협의함Docker, NodeJS, NginxOverview안녕하세요. 칼 같은 들여쓰기에 희열을 느끼는 브랜디 개발자 강원우입니다! 서버를 운영해본 개발자라면 Fatal 에러, 아웃오브메모리 에러, 또는 전날 흡수한 알코올로 인해 손을 떨다가 한 번쯤 서버를 요단강 너머로 보내봤을 겁니다. 만약 테스트 서버였다면 잠시 마음을 가다듬으면 되지만, 현재 상용 서비스 중인 서버라면 얘기는 달라집니다.님아, 그 강을 건너지 마오!이런 간담이 서늘해지는 경험은 저 하나로 족합니다. 그래서 고군분투했던 지난 날을 되돌아보면서 빠르고 안정적이며, 죽어도 죽지 않는 좀비 같은 서버 구축 방법을 쓰려고 합니다.준비물서비스를 운영할 때 가장 중요하게 여겨야 하는 건 역시 안정성입니다. 이번 글에서는 오래 전부터 개발 세계의 뜨거운 감자였던 Docker와, 단일 스레드와 이벤트 루프로 태생적으로 심플하고 민첩한 NodeJS, 마지막으로 고성능을 목표로 개발된 Nginx를 활용하겠습니다.1. DockerDocker는 컨테이너 기반의 오픈소스 가상화 플랫폼입니다. 대표적으로 LXC(Linux Container)가 있습니다. 화물 컨테이너처럼 어떠한 일련의 기능을 완전히 격리된 소프트웨어 환경에서 작동하게 만드는 기술을 말합니다.OS 가상화와 별반 다를 게 없는 것 같지만 소프트웨어적으로 작동한다는 차이가 있습니다. 다시 말해, 현재 OS의 자원을 그대로 사용하기 때문에 하이퍼 바이저가 가상환경을 위해 가상의 커널을 만드는 오버헤드가 거의 없다는 것이죠.이미지와 속도도 차이를 보입니다. 완벽하게 구성한 세팅을 그대로 이미지화할 수 있고, 해당 이미지는 Docker 위에서 완벽히 동일하게 동작하는 걸 보장합니다. 해당 이미지로 컨테이너를 제작할 땐 1~2초면 새로운 컨테이너가 생겨날 정도로 엄청나게 빠른 속도도 자랑합니다. 1)또한 Docker는 자주 사용되는 다양한 이미지를 퍼블릭 레포지토리에 공유해 사용할 수 있기도 합니다. 양파도 아닌데 특징이 계속 나오죠? 다음 글에서 Docker의 특징을 더 자세히 다루겠습니다.Docker는 리눅스만 지원했었지만, 요즘은 Docker for Windows와 Docker for Mac으로 거의 모든 OS에서 사용할 수 있습니다. 2) Docker 설치 링크는 윈도우와 맥으로 나뉘어져 있습니다. 리눅스는 아래를 참고하세요.curl -fsSL https://get.docker.com/ | sudo sh 2. NodeJSNodeJS는 구글이 구글 크롬에 사용하려고 제작한 V8 오픈소스 자바스크립트 엔진을 기반으로 제작된 자바스크립트 런타임입니다. NodeJS에는 몇 가지 특징이 있습니다.단일 스레드입니다.비동기 방식입니다.이벤트 루프를 사용합니다NPM이라는 끝내주는 동반자가 있습니다.비유하자면 예전엔 낡은 곡괭이로 큰 돌을 캐내려고 수십 명의 인부가 달라 붙었는데, 지금은 육중한 포크래인으로 거대한 돌을 쑥! 뽑아버리는 것과 비슷합니다. 굉장히 효율적이죠. NodeJS는 단일 스레드의 장점을 극대화하려고 이벤트 루프를 통해 모든 처리를 비동기로 수행합니다. 서버 사이드의 묵직한 CPU들이 빠르게 일을 처리하고 이벤트 루프에 등록된 일을 감지해 다음 작업을 빠르게 수행하는 방식입니다.마지막으로 NPM(Node Package Manager)은 NodeJS에서 사용할 수 있는 다양한 모듈을 관리해주는 프로그램입니다. 도커와 상당히 유사합니다. NodeJS에서는 무언가 기능을 만들기 전에 NPM을 먼저 뒤져보라는 말이 있을 정도로 풍부한 모듈 생태계가 구성되어 있습니다. 이는 로깅이나 날짜 계산 등 생각보다 까다로운 것들을 가져다 사용할 수 있게 도와주기 때문에 개발이 빨라집니다. NodeJS 설치링크는 여기를 클릭하세요. 이 글의 예제에서는 NodeJS의 현재시점 LTS인 codename Carbon버젼을 사용합니다!8.x 버젼이 Active LTS 상태입니다.LTS은 Long Term Support의 약자로 가장 오랜기간 지원하는 버전입니다.우선 서비스 구성을 위해 간단한 NodeJS 어플리케이션을 작성해보겠습니다.첫째, packge.json를 작성합시다.{   "name": "nodejs_tutorial_server",   "version": "0.0.0",  "private": true,   "scripts": {     "start": "node nodejs_tutorial_server.js"   },   "description": "NodeJS Tutorial Server",   "author": {     "name": "WonwooKang"   },   "dependencies": {     "express": "^4.16.3",     "uuid": "^3.2.1"   } } nodejs_tutorial_server.js 파일을 메인으로 실행합니다. HTTP Request를 처리하려면 express를 사용해야 하며, 서버를 구분하려면 uuid모듈이 필요합니다.둘째, package.json의 의존 파일들을 설치합시다.npm install npm install 전npm install 후셋째, 간단한 웹 어플리케이션을 작성합시다.var express = require('express'); var app = express(); const port = 3000;  var server = app.listen(port, function () {     console.log("Express server has started on port : "+port);  });  app.get('/', function (req, res) {     res.send('Hello?');  }); 넷째, package.json의 script start 구문을 실행하여 서버를 로드합시다.npm start 3000번 포트로 서버가 시작되었습니다!접속해볼까요?잘 접속됩니다.그런데 수정할 때마다 서버를 매번 다시 띄우면 귀찮을 겁니다. 이럴 땐 nodemon 모듈을 사용합시다. nodemon은 Nodejs의 파일이 수정되는 걸 감지해 자동으로 리로드해주는 편리한 도구입니다.nodemon설치npm install nodemon -g package.json script 변경"scripts": {     "start": "nodemon nodejs_tutorial_server.js"   }, nodemon 실행확인을 위해 약갼의 수정//nodejs_tutorial_server.js 수정 app.get('/', function(req, res) {     res.send('Hello Nodemon');  }); nodemon을 통해 어플리케이션이 실행된 모습파일수정 후 저장했을 때 자동 감지한 모습서버 잘 떴습니다!성공적으로 단 하나의 GET 요청을 처리할 수 있는 심플한 NodeJS 기반 웹 어플리케이션을 완성했습니다. 이제 웹 어플리케이션을 Docker Container위에서 구동해봅시다!3. Docker로 NodeJS Express 서버 구동하기이제 Docker Container위에서 NodeJS서버를 구동할 건데요. 그러려면 우선 Dockerfile을 작성해야 합니다. 물론 Docker의 이미지를 당겨 받고, 컨테이너를 생성하고, 또 컨테이너를 실행해서 Attach하고, 필요한 파일들을 밀어넣는 등 귀찮은 방법도 있습니다. 하지만 개발자에게 이것은 힘든 작업이므로 Dockerfile을 적극 활용합시다. (Dockerfile의 D는 대문자여야 합니다! 꼭이요)Node 도커 이미지에 어플리케이션 파일을 추가해 실행하는 Dockerfile 작성하기FROM node:carbon MAINTAINER Wonwoo Kang [email protected] #app 폴더 만들기 - NodeJS 어플리케이션 폴더 RUN mkdir -p /app #winston 등을 사용할떄엔 log 폴더도 생성 #어플리케이션 폴더를 Workdir로 지정 - 서버가동용 WORKDIR /app #서버 파일 복사 ADD [어플리케이션파일 위치] [컨테이너내부의 어플리케이션 파일위치] #저는 Dockerfile과 서버파일이 같은위치에 있어서 ./입니다 ADD ./ /app #패키지파일들 받기 RUN npm install #배포버젼으로 설정 - 이 설정으로 환경을 나눌 수 있습니다. ENV NODE_ENV=production #서버실행 CMD node nodejs_tutorial_server.js Dockerfile 내용은 node:carbon에서 :carbon이 NodeJS의 이미지 버전 Tag 입니다.Dockerfile을 통해 docker image 빌드하기docker build –tag 레포지토리명: 태그 Dockerfile 경로docker build --tag node_server:0.0.1 [Dockerfile이 위치하는 경로] 호오... 게이지가 마구마구 차오르는군요?build가 완료된 화면입니다. Dockerfile의 내용 순서가 각 Step별로 진행된 것을 알 수 있습니다.빌드 결과 생성된 이미지 확인하기docker images 빌드 명령어에서 입력했던 버전 태그까지 잘 입력된 것을 알 수 있습니다.NodeJS Carbon 이미지를 기반으로 한 node_server 이미지를 제작했습니다. 사이즈는 둘이 합쳐 1Gb가 넘을 것 같지만 실제로는 변경된 부분만 저장됩니다. 그러므로 node_server 이미지의 크기는 6~10Mb 정도입니다.생성된 이미지로 컨테이너 만들기컨테이너 생성 명령어는 아래와 같습니다.docker create --name [서버명] -p [외부 포트:컨테이너 내부포트] [이미지명:버전태그] 주의할 점이 있습니다. 포트번호 바인딩 중 왼쪽은 우리가 접속할 실제 포트이고, 오른쪽은 컨테이너 내부의 NodeJS서버 할당 포트가 된다는 것입니다. 공유기의 포트포워딩 설정과 같습니다.docker create --name NODE_SERVER_0 -p 3000:3000 node_server:0.0.1 알 수 없는 코드가 생성되었습니다. 응?컨테이너 확인하기생성한 컨테이너를 확인해볼까요?docker ps 어.. 없잖아?옵션을 추가합니다.docker ps -a 나타났다!docker ps 명령어는 현재 실행 중(STATUS:Up)인 컨테이너의 목록을 보여줍니다. -a 옵션은 실행하지 않는 모든 컨테이너를 보여줍니다. 위의 이미지에서 node_server:0.0.1이미지로부터 NODE_SERVER_0 이라는 이름으로 2분 전에 생성되었다는 걸 알 수 있습니다. 3)컨테이너 실행하기docker start NODE_SERVER_0 다시 확인하기docker ps 19초 전에 Up상태가 되었다는 걸 알 수 있다.외부 3000번 포트 -> 내부 3000번 포트로 연결되었습니다. 서버도 실행되었고요! 이제 접속해볼까요?내용도 안 바꾸고 새로고침도 빨라서 뜬 건지 잘 모르겠군요. 내용을 수정해서 다시 확인하겠습니다.//nodejs_tutorial_server.js 수정 app.get('/', function (req, res) {     res.send('Hello I\'m In Docker Container Now!');  }); 파일 변경해서 다시 확인하기//버전 태그도 0.0.2로 업해주고 docker build --tag node_server:0.0.2 [Dockerfile위치] 잘 생성되었습니다.//이미지가 잘 생성되었는지 확인하고 docker images 0.0.2가 나타났습니다.//기존 컨테이너를 삭제합니다. -f 옵션은 실행중인 컨테이너도 강제로 삭제하겠다는 뜻입니다.  docker rm -f NODE_SERVER_0 // 잘지워졌나 확인하고  docker ps -a 잘 지워집니다.//0.0.2 버젼 이미지로 컨테이너를 다시 생성합니다.  docker create --name NODE_SERVER_0 -p 3000:3000 node_server:0.0.2   //서버를 실행합니다. docker start NODE_SERVER_0 잘 실행됩니다.이제 다시 접속해봅시다.안녕! 나 지금 Docker 안에 있어!이제 Docker로 여러 개의 서버를 띄우겠습니다. NodeJS는 싱글 스레드이기 때문에 하나의 CPU를 여럿이 나눠 갖는 건 비효율적입니다. 따라서 CPU 숫자에 맞춰서 서버를 띄워보겠습니다.제 맥북엔 CPU가 4개뿐입니다.CPU수에 맞춰 추가로 생성하기추가로 컨테이너를 생성하고, 서버를 실행합니다. 서버 목록도 확인해야겠죠.서버 생성서버 실행서버 목록 확인포트번호는 같은 포트를 쓸 수 없기 때문에 3001, 3002, 3003으로 매핑합니다. 브라우저로 접속해서 확인해보겠습니다.각 포트별 접속 화면미리 만들어둔 이미지 덕분에 서버 3대를 띄우는 데에 5분도 안 걸렸습니다. 하지만 Docker 서버를 여러 개 띄워도 결국 사람의 손이 닿아야 합니다. 따라서 이번에는 NodeJS의 Cluster를 활용해 적은 수의 Docker Container를 이용하면서도 다수의 CPU를 사용하겠습니다. 또 죽은 워커를 다시 살려 서버가 다운되는 것을 막아 안정적인 서비스도 구축해보겠습니다.4. 멀티코어대응 NodeJS Cluster 구성2컨테이너용 NodeJS Cluster서버 어플리케이션 작성하기var cluster = require('cluster'); var os = require('os'); var uuid = require('uuid'); const port = 3000; //키생성 - 서버 확인용 var instance_id = uuid.v4();  /**  * 워커 생성  */ var cpuCount = os.cpus().length; //CPU 수 var workerCount = cpuCount/2; //2개의 컨테이너에 돌릴 예정 CPU수 / 2  //마스터일 경우 if (cluster.isMaster) {     console.log('서버 ID : '+instance_id);     console.log('서버 CPU 수 : ' + cpuCount);     console.log('생성할 워커 수 : ' + workerCount);     console.log(workerCount + '개의 워커가 생성됩니다\n');        //CPU 수 만큼 워커 생성     for (var i = 0; i < workerCount>         console.log("워커 생성 [" + (i + 1) + "/" + workerCount + "]");         var worker = cluster.fork();     }        //워커가 online상태가 되었을때     cluster.on('online', function(worker) {         console.log('워커 온라인 - 워커 ID : [' + worker.process.pid + ']');     });        //워커가 죽었을 경우 다시 살림     cluster.on('exit', function(worker) {         console.log('워커 사망 - 사망한 워커 ID : [' + worker.process.pid + ']');         console.log('다른 워커를 생성합니다.');                 var worker = cluster.fork();     });  //워커일 경우 } else if(cluster.isWorker) {     var express = require('express');     var app = express();     var worker_id = cluster.worker.id;         var server = app.listen(port, function () {         console.log("Express 서버가 " + server.address().port + "번 포트에서 Listen중입니다.");     });        app.get('/', function (req, res) {         res.send('안녕하세요 저는 워커 ['+ cluster.worker.id+'] 입니다.');     });  } CPU 숫자를 받아 CPU 수(4)를 컨테이너 수(2) 로 나눠 워커를 생성하는 NodeJS 클러스터 구성입니다. 이렇게만 해도 운영에는 무리가 없지만 컨테이너 2개의 구분이 안 되서 확인할 수가 없습니다.그러므로 마스터와 워커의 통신을 이용해 마스터의 uuid를 얻겠습니다. (워커와 마스터 간의 데이터 이동은 통신 말고는 메모리DB 등의 데이터 저장소밖에 없습니다)마스터의 아이디를 알아오는 로직이 추가된 어플리케이션 작성var cluster = require('cluster'); var os = require('os'); var uuid = require('uuid'); const port = 3000; //키생성 - 서버 확인용 var instance_id = uuid.v4();  /**  * 워커 생성  */ var cpuCount = os.cpus().length; //CPU 수 var workerCount = cpuCount/2; //2개의 컨테이너에 돌릴 예정 CPU수 / 2  //마스터일 경우 if (cluster.isMaster) {     console.log('서버 ID : '+instance_id);     console.log('서버 CPU 수 : ' + cpuCount);     console.log('생성할 워커 수 : ' + workerCount);     console.log(workerCount + '개의 워커가 생성됩니다\n');         //워커 메시지 리스너     var workerMsgListener = function(msg){                    var worker_id = msg.worker_id;             //마스터 아이디 요청             if (msg.cmd === 'MASTER_ID') {                 cluster.workers[worker_id].send({cmd:'MASTER_ID',master_id: instance_id});            }      }        //CPU 수 만큼 워커 생성     for (var i = 0; i < workerCount>         console.log("워커 생성 [" + (i + 1) + "/" + workerCount + "]");         var worker = cluster.fork();                //워커의 요청메시지 리스너         worker.on('message', workerMsgListener);     }        //워커가 online상태가 되었을때     cluster.on('online', function(worker) {         console.log('워커 온라인 - 워커 ID : [' + worker.process.pid + ']');     });        //워커가 죽었을 경우 다시 살림     cluster.on('exit', function(worker) {         console.log('워커 사망 - 사망한 워커 ID : [' + worker.process.pid + ']');         console.log('다른 워커를 생성합니다.');                 var worker = cluster.fork();         //워커의 요청메시지 리스너         worker.on('message', workerMsgListener);     });  //워커일 경우 } else if(cluster.isWorker) {     var express = require('express');     var app = express();     var worker_id = cluster.worker.id;     var master_id;        var server = app.listen(port, function () {        console.log("Express 서버가 " + server.address().port + "번 포트에서 Listen중입니다.");     });        //마스터에게 master_id 요청     process.send({worker_id: worker_id, cmd:'MASTER_ID'});     process.on('message', function (msg){         if (msg.cmd === 'MASTER_ID') {             master_id = msg.master_id;         }     });        app.get('/', function (req, res) {         res.send('안녕하세요 저는 ['+master_id+']서버의 워커 ['+ cluster.worker.id+'] 입니다.');    });  } Docker Container에 올리기 전 로컬 테스트를 먼저 진행합니다. 서버 구동!두 개의 워커가 실행되었습니다.똑같은 localhost:3000번 접속이지만 워커의 번호가 다릅니다.이제 워커로 CPU 수만큼 워커를 생성할 수 있게 되었습니다. 이제 워커가 어떻게 안정적으로 서비스되는지 테스트하겠습니다. 워커 킬링 테스트하기워커 킬러 로직 작성//워커 킬링 테스트     app.get("/workerKiller", function (req, res) {         cluster.worker.kill();         res.send('워커킬러 호출됨');     }); 실험에 앞서 똑같은 상황 재연 마스터 아이디를 유심히 봐주세요. 워커 킬러를 실행하겠습니다.워커 킬러 호출아래는 호출된 결과입니다. 하나의 워커가 죽자마자 곧장 다른 워커가 태어나(?) 3000번을 Listen하기 시작했습니다. 워커 킬러가 호출된 화면이제 워커 킬러를 여러 번 호출해보겠습니다. CMD+R을 꾸욱 눌러 연속으로 킬링해봤는데 아래 화면처럼 바로 살아납니다.접속해서 현재 워커를 확인합니다.위의 화면처럼 마스터의 UUID가 그대로인데 워커만 교체되었습니다. 준비는 끝났습니다. 이제 Docker를 이용해 2명의 워커를 가진 2개의 NodeJS서버를 실행하고, 4개의 귀여운 CPU를 불살라봅시다! 5. Docker로 NodeJS Cluster 서버 실행하기docker build --tag node_server:0.0.3 /Users/kww/eclipse-workspace/nodejs-for-article docker create --name NODE_SERVER_0 -p 3000:3000 node_server:0.0.3 docker create --name NODE_SERVER_1 -p 3001:3000 node_server:0.0.3 docker start NODE_SERVER_0 docker start NODE_SERVER_1 cluster가 적용된 2개의 컨테이너 start0.0.3번 이미지로 생성된 2개의 컨테이너 서버가 무사히 로드되었습니다. 이제 접속해서 확인해볼까요?cluster가 적용된 2컨테이너 4서버 구동화면WOW! 2개의 URL, 2개의 UUID, 각 2명의 워커까지. 완벽한 2.2.2입니다. 마치 홍진호를 보는 듯한 서버 현황입니다. 이제 워커 킬러로 습격해보겠습니다.워커 킬러 습격 후위의 이미지를 보면 3000번 포트서버에서 13명, 3001번 포트서버에서 22명의 워커가 사망했습니다. UUID를 통해 2개의 서버에서 일정량의 워커가 매우 안정적으로 서버를 지키고 있는 걸 알 수 있었습니다.지금까지 2개의 컨테이너로 4개의 서버를 구성해보았습니다. CPU 숫자와 나눠지는 수에 따라 컨테이너의 수, NodeJS 클러스터 서버의 수를 유동적으로 조정할 수 있습니다. 전에 운영하던 API서버는 16코어 서버였고, 로드벨런서 및 기타 작업용 1코어의 여분을 남기고 15코어 / 3 으로 5개의 워커를 가진 3개의 NodeJS서버를 도커 컨테이너로 운영했었습니다.여기서 문제점이 생깁니다. 우리는 어떤 서비스를 할 때 하나의 도메인을 쓰는데 포트번호가 2개죠? 어떻게 해야 할까요. 여기서 바로 한참을 기다렸던 불곰국의 Nginx가 등장합니다.6. Nginx로 로드밸런싱 하기Nginx은 “더 적은 자원으로 더 빠르게”를 지향합니다. 러시아의 이고르 시쇼브(Игорь Сысоев)는 Apache에서 10,000개의 접속을 동시에 다루기 힘든 걸 해결하려고 Nginx를 개발합니다.Nginx는 NodeJS와 유사하게 싱글 스레드 방식에 이벤트 드리븐 구조 사용하는 오픈소스 HTTP서버로 최근 아파치의 점유율을 상당히 뺏고 있는 서버입니다. 다운로드 링크를 아래에 써두었습니다.Nginx 설치WindowNginx 다운로드Macbrew install nginx Linuxapt-get install nginx or yum install nginx Nginx 설치 성공Nginx 기본 접속 화면서버 조작방법서버 시작 : nginx 서버 중지 : nginx -s stop 서버 재시작 : nginx -r reload (맥에선 이건 안되는듯?) 기본 설정은 8080포트로 되어있습니다. 원하는 포트르 로드벨런싱 설정을 해보겠습니다. Nginx 로드밸런싱 설정아래는 Nginx의 로드밸런싱입니다.#http블럭 내부에 추가     #NodeJS 서버 로드밸런싱     upstream nodejs_server {         #least_conn;         #ip_hash;         server localhost:3000 weight=10 max_fails=3 fail_timeout=10s;         server localhost:3001 weight=10 max_fails=3 fail_timeout=10s;     }        #3333번 포트 NodeJS 서버로 연결     server{         listen               3333;         server_name  localhost;                location / {             proxy_pass http://nodejs_server;         }     } 로드밸런싱이 잘 적용되었는지 확인해보겠습니다. 로드밸런싱 적용 이후모든 브라우저에서 3333번으로 접속했는데 서로 다른 2개의 서버가 번갈아 접속되고, 워커가 가끔 바뀌는 걸 확인할 수 있습니다. 이번엔 로드밸런서로 워커 킬러를 호출하겠습니다.로드밸런싱 포트인 3333번 포트로 여러 번 호출결과 확인Nginx 로드밸런서가 확실하게 작동하는 걸 확인할 수 있었습니다. 위의 이미지에서 서버가 자꾸 바뀌는 모습을 볼 수 있는데, 이는 세션이 유지되지 않기 때문입니다. 실제 서비스에서는 세션의 유지를 위해 ip_hash 옵션이 꼭 필요합니다.ip_hash : 동일한 IP의 접속은 같은 서버로 접속하도록 하는 옵션입니다.  least_conn : 가장 접속이 적은 서버로 접속을 유도하는 옵션으로 ip_hash와 같이쓰입니다. Conclusion자, 고생하셨습니다. 여기까지 Docker와 NodeJS, Nginx를 이용해 관리하기 쉽고, 일부러 죽여도 죽지 않는 안정적인 서비스 환경을 구축해봤습니다. 한 가지 주의할 점이 있습니다. NodeJS의 Cluster는 죽은 워커를 바로 살리는데 싱글스레드여서 그런지 그 속도가 정말 어마어마합니다. 따라서 NodeJS Cluster를 사용할 땐 여러 핸들링에 신중하세요. 모든 promise에 반드시 catch를 달아 핸들링하고, 오류가 날 것 같은 로직엔 반드시 try - catch를 달아 핸들링을 해야 합니다. 그렇지 않으면 다시 살아나는 워커에 의해 서버의 자원이 고갈될 수 있습니다.예전에 16코어 서버를 운영할 땐 서버 자원에 비해 사용자가 적어서..(눈물) 5워커 2개의 서버만 구동하고 여유를 두었습니다. 그리고 서버 패치가 있을 때 3번째 서버를 대기시켰습니다. 앱에서 업데이트가 완료되는 시점에 Docker Container를 바꿔치기 하는 방식으로 Non-Stop서비스를 운영했죠. 혹시 코어가 빵빵한 여유 서버가 있는데 재빠르고 좀비 같은 서비스를 구성해야 한다면 위와 같은 환경 구축을 강력히 추천합니다. 지금까지 긴 글을 읽어주셔서 감사합니다.ps. 글 쓰다 보니 해가 떴네요. 하하.참고1) 가상 머신은 작은 이미지라도 기가바이트 단위의 사이즈와 Load되기까지 상당한 시간이 소요된다.2) 그러나 Windows의 경우, Hiper-v위에 리눅스를 띄워 도커를 구동한다. Mac에서도 가상 머신 위에서 구동된다. 따라서 성능적인 강점은 리눅스에만 적용된다.3) 도커에서는 NAME 속성을 지어주지 않으면 알아서 이름을 지어주는데 romantic한 단어가 많다.글강원우 과장 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발팀 #개발자 #개발환경 #업무환경 #인사이트 #경험공유
조회수 1678

자바스크립트에대해서

안녕하세요. 크몽 개발팀입니다.오늘 저는 좀 심오한 주제를 다뤄보려고합니다.이번에 제가 다룰 주제는 ‘자바스크립트’라는 언어입니다.자바스크립트라는 언어와 자바라는 언어에 대해서 혼동을 하는 경우가 많은데요,자바와 자바스크립트는 완전히 다른언어입니다. 쉽게말해서 자바는 서버를 구축하는 부분을 주를 담당하고,자바스크립트는 화면을 구성하는 부분에서 사용되는 프로그래밍 언어라고 보시면 될 것 같습니다.(자바스크립트의 이름을 만들 당시에 자바라는 언어가 유행을 해서 자바스크립트라고 이름을 지었다고 하네요.원래 이 언어의 이름은 라이브 스크립트입니다.)물론 자바스크립트로 서버를 구축을 할 수도 있습니다.(node.js라고하는 플랫폼입니다. 자바스크립트를 이용하여 서버를 구축할 수있는 플랫폼입니다.자세한 사항은 책이나 위키피디아를 참고하시면 좋을 듯 합니다.)각설하고,아무튼 자바스크립트라는 언어에 대해서 좀더 자세히 알아보겠습니다.자바스크립트라는 언어를 알기 위해서는 일단 스크립트라는 것이 무엇인지에 대해 좀더 알아야 합니다.위키피디아에 따르면 스크립트 언어란,'응용프로그램과 독립하여 사용되고 일반적으로 응용프로그램의 언어와 다른 언어로 사용되어최종사용자가 응용프로그램의 동작을 사용자의 요구에 맞게 수행할 수 있도록 해준다.' 라고 정의하고 있습니다. 어렵지요? 쉽게 말하면 연극에서 ‘스크립트’라는 것에 서 유래 되었다고 하고, 그뜻이 연극에서의 시나리오, 각본을 의미합니다. 그 의미를 그대로 적용하면 ‘대본, 시나리오만 제공하면 알아서 작동한다.' 는 그런 뜻이지요.대충 감이 잡히셨나요?자바스크립트는 TIOBE 라는 소프트웨어 회사에서 발표한 2014년 프로그래밍 언어순위에서도 상당히 상위권에 차지를 하고있습니다.그만큼 많이 사용이 된다는 의미겠지요.매우 좋은 것처럼 보이지만 자바스크립트가 만만한 언어는 아닙니다.제가 듣기로는 ‘자바스크립트는 악마의 언어’라고 불린다고 들었습니다.그렇게 불리는 이유는 그만큼 언어가 유연하기때문입니다.조금전에 언급했듯이 연극에서의 대본과 시나리오를 프로그래머가 직접 만들어야한다는 것입니다.그만큼 프로그래밍하기 쉽지 않다는 것이겠지요.자바스크립트는 단점이 바로 장점입니다.'유연하다는 것' 때문에 사람들의 입맛에 맞게 커스터마이징을 할 수있다는 것이고웹상에 이미 프로그래머들이 만들어 놓은 많은 라이브러리가 있습니다.우리는 이걸 잘 이용하면 되겠지요?좀 더 자바스크립에 대해서 자세히 알고싶다면 ‘javascript inside’라는 책을 참고하여 공부해보시면 좋을 듯 하네요. ---------------------------------------------------------------------------------------------------저는 크몽 개발팀의 Sean이었습니다. #크몽 #개발자 #개발팀 #팀원소개 #기업문화
조회수 1316

VCNC가 Hadoop대신 Spark를 선택한 이유 - VCNC Engineering Blog

요즘은 데이터 분석이 스타트업, 대기업 가릴 것 없이 유행입니다. VCNC도 비트윈 출시 때부터 지금까지 데이터 분석을 해오고 있고, 데이터 기반의 의사결정을 내리고 있습니다.데이터 분석을 하는데 처음부터 복잡한 기술이 필요한 것은 아닙니다. Flurry, Google Analytics 등의 훌륭한 무료 툴들이 있습니다. 하지만 이러한 범용 툴에서 제공하는 것 이상의 특수하고 자세한 분석을 하고 싶을 때 직접 많은 데이터를 다루는 빅데이터 분석을 하게 됩니다. VCNC에서도 비트윈의 복잡한 회원 가입 프로세스나, 채팅, 모멘츠 등 다양한 기능에 대해 심층적인 분석을 위해 직접 데이터를 분석하고 있습니다.빅데이터 분석 기술큰 데이터를 다룰 때 가장 많이 쓰는 기술은 Hadoop MapReduce와 연관 기술인 Hive입니다. 구글의 논문으로부터 영감을 받아 이를 구현한 오픈소스 프로젝트인 Hadoop은 클러스터 컴퓨팅 프레임웍으로 비싼 슈퍼컴퓨터를 사지 않아도, 컴퓨터를 여러 대 연결하면 대수에 따라서 데이터 처리 성능이 스케일되는 기술입니다. 세상에 나온지 10년이 넘었지만 아직도 잘 쓰이고 있으며 데이터가 많아지고 컴퓨터가 저렴해지면서 점점 더 많이 쓰이고 있습니다. VCNC도 작년까지는 데이터 분석을 하는데 MapReduce를 많이 사용했습니다.주스를 만드는 과정에 빗대어 MapReduce를 설명한 그림. 함수형 프로그래밍의 기본 개념인 Map, Reduce라는 프레임을 활용하여 여러 가지 문제를 병렬적으로 처리할 수 있다. MapReduce slideshare 참조MapReduce는 슈퍼컴퓨터 없이도 저렴한 서버를 여러 대 연결하여 빅데이터 분석을 가능하게 해 준 혁신적인 기술이지만 10년이 지나니 여러 가지 단점들이 보이게 되었습니다. 우선 과도하게 복잡한 코드를 짜야합니다. 아래는 간단한 Word Count 예제를 MapReduce로 구현한 것인데 매우 어렵고 복잡합니다.MapReduce로 단어 갯수를 카운트하는 간단한 예제 (Java). 많은 코드를 작성해야 한다.이의 대안으로 SQL을 MapReduce로 변환해주는 Hive 프로젝트가 있어 많은 사람이 잘 사용하고 있지만, 쿼리를 최적화하기가 어렵고 속도가 더 느려지는 경우가 많다는 어려움이 있습니다.MapReduce의 대안으로 최근 아주 뜨거운 기술이 있는데 바로 Apache Spark입니다. Spark는 Hadoop MapReduce와 비슷한 목적을 해결하기 위한 클러스터 컴퓨팅 프레임웍으로, 메모리를 활용한 아주 빠른 데이터 처리가 특징입니다. 또한, 함수형 프로그래밍이 가능한 언어인 Scala를 사용하여 코드가 매우 간단하며, interactive shell을 사용할 수 있습니다.Spark으로 단어 개수를 카운트하는 간단한 예제 (Scala). MapReduce에 비해 훨씬 간단하다.Spark과 MapReduce의 성능 비교. I/O intensive 한 작업은 성능이 극적으로 향상되며, CPU intensive 한 작업의 경우에도 효율이 더 높다. (자료: RDD 논문)Apache Spark는 미국이나 중국에서는 현재 Hadoop을 대체할만한 기술로 급부상하고 있으며, 국내에도 최신 기술에 발 빠른 사람들은 이미 사용하고 있거나, 관심을 갖고 있습니다. 성능이 좋고 사용하기 쉬울 뿐 아니라, 범용으로 사용할 수 있는 프레임웍이기에 앞으로 더 여러 분야에서 많이 사용하게 될 것입니다. 아직 Spark를 접해보지 못하신 분들은 한번 시간을 내어 살펴보시길 추천합니다.기존의 데이터 분석 시스템 아키텍처기존의 데이터 분석 시스템 아키텍처기존의 시스템은 비용을 줄이기 위해 머신들을 사무실 구석에 놓고 직접 관리했으며, AWS S3 Tokyo Region에 있는 로그를 다운받아 따로 저장한 뒤, MapReduce로 계산을 하고 dashboard를 위한 사이트를 따로 제작하여 운영하고 있었습니다.이러한 시스템은 빅데이터 분석을 할 수 있다는 것 외에는 불편한 점이 많았습니다. 자주 고장 나는 하드웨어를 수리하느라 바빴고, 충분히 많은 머신을 확보할 여유가 없었기 때문에 분석 시간도 아주 오래 걸렸습니다. 그리고 분석부터 시각화까지 과정이 복잡하였기 때문에 간단한 것이라도 구현하려면 시간과 노력이 많이 들었습니다.Spark과 Zeppelin을 만나다이때 저희의 관심을 끈 것이 바로 Apache Spark입니다. MapReduce에 비해 성능과 인터페이스가 월등히 좋은 데다가 0.x 버전과는 달리 1.0 버전에서 많은 문제가 해결되면서 안정적으로 운영할 수 있어 비트윈 데이터 분석팀에서는 Spark 도입을 결정했습니다.Apache Zeppelin은 국내에서 주도하고 있는 오픈소스 프로젝트로써, Spark를 훨씬 더 편하고 강력하게 사용할 수 있게 해주는 도구입니다. 주요한 역할은 노트북 툴, 즉 shell에서 사용할 코드를 기록하고 재실행할 수 있도록 관리해주는 역할과 코드나 쿼리의 실행 결과를 차트나 표 등으로 시각화해서 보여주는 역할입니다. VCNC에서는 Zeppelin의 초기 버전부터 관심을 가지고 살펴보다가, Apache Spark를 엔진으로 사용하도록 바뀐 이후에 활용성이 대폭 좋아졌다고 판단하여 데이터 분석에 Zeppelin을 도입하여 사용하고 있고, 개발에도 참여하고 있습니다.또한, 위에서 언급한 하드웨어 관리에 드는 노력을 줄이기 위해서 전적으로 클라우드를 사용하기로 함에 따라서1 아래와 같은 새로운 구조를 가지게 되었습니다.새로운 데이터 분석 시스템 아키텍처새로운 데이터 분석 시스템 아키텍처새로운 데이터 분석 시스템은 아키텍처라고 하기에 다소 부끄러울 정도로 간단합니다. 애초에 전체 시스템 구성을 간단하게 만드는 것에 중점을 두었기 때문입니다. 대략적인 구성과 활용법은 아래와 같습니다.모든 서버는 AWS 클라우드를 이용수 대의 Zeppelin 서버, 수 대의 Spark 서버운영Spark 서버는 메모리가 중요하므로 EC2 R3 instance 사용로그는 별도로 저장하지 않고 서비스 서버에서 S3로 업로드하는 로그를 곧바로 가져와서 분석함중간 결과 저장도 별도의 데이터베이스를 두지 않고 S3에 파일로 저장Zeppelin의 scheduler 기능을 이용하여 daily batch 작업 수행별도의 dashboard용 Zeppelin을 통해 중간 결과를 시각화하며 팀에 결과 공유이렇게 간단한 구조이긴 하지만 Apache Spark와 Apache Zeppelin을 활용한 이 시스템의 능력은 기존 시스템보다 더 강력하고, 더 다양한 일을 더 빠르게 해낼 수 있습니다.기존현재일일 배치 분석코드 작성 및 관리가 어려움Zeppelin의 Schedule 기능을 통해 수행Interactive shell로 쉽게 데이터를 탐험오류가 생긴 경우에 shell을 통해 손쉽게 원인 발견 및 수정 가능Ad-hoc(즉석) 분석복잡하고 많은 코드를 짜야 함분석 작업에 수 일 소요Interactive shell 환경에서 즉시 분석 수행 가능Dashboard별도의 사이트를 제작하여 운영관리가 어렵고 오류 대응 힘듦Zeppelin report mode 사용해서 제작코드가 바로 시각화되므로 제작 및 관리 수월성능일일 배치 분석에 약 8시간 소요메모리를 활용하여 동일 작업에 약 1시간 소요이렇게 시스템을 재구성하는 작업이 간단치는 않았습니다. 이전 시스템을 계속 부분적으로 운영하면서 점진적으로 재구성 작업을 하였는데 대부분 시스템을 옮기는데 약 1개월 정도가 걸렸습니다. 그리고 기존 시스템을 완전히 대체하는 작업은 약 6개월 후에 종료되었는데, 이는 분석 성능이 크게 중요하지 않은 부분들에 대해서는 시간을 두고 여유 있게 작업했기 때문이었습니다.Spark와 Spark SQL을 활용하여 원하는 데이터를 즉석에서 뽑아내고 공유하는 예제Zeppelin을 활용하여 인기 스티커를 조회하는 dashboard 만드는 예제결론비트윈 데이터 분석팀은 수개월에 걸쳐 데이터 분석 시스템을 전부 재구성하였습니다. 중점을 둔 부분은빠르고 효율적이며 범용성이 있는 Apache Spark, Apache Zeppelin을 활용하는 것최대한 시스템을 간단하게 구성하여 관리 포인트를 줄이는 것두 가지였고, 그 결과는 매우 성공적이었습니다.우선 데이터 분석가 입장에서도 관리해야 할 포인트가 적어져 부담이 덜하고, 이에 따라 Ad-hoc분석을 수행할 수 있는 시간도 늘어나 여러 가지 데이터 분석 결과를 필요로 하는 다른 팀들의 만족도가 높아졌습니다. 새로운 기술을 사용해 본 경험을 글로 써서 공유하고, 오픈소스 커뮤니티에 기여할 수 있는 시간과 기회도 생겼기 때문에 개발자로서 보람을 느끼고 있습니다.물론 새롭게 구성한 시스템이 장점만 있는 것은 아닙니다. 새로운 기술들로 시스템을 구성하다 보니 세세한 기능들이 아쉬울 때도 있고, 안정성도 더 좋아져야 한다고 느낍니다. 대부분 오픈소스 프로젝트이므로, 이러한 부분은 적극적으로 기여하여 개선하여 나갈 계획입니다.비트윈 팀에서는 더 좋은 개발환경, 분석환경을 위해 노력하고 있으며 이는 더 좋은 서비스를 만들기 위한 중요한 기반이 된다고 생각합니다. 저희는 항상 좋은 개발자를 모시고 있다는 광고와 함께 글을 마칩니다.연관 자료: AWS 한국 유저 그룹 - Spark + S3 + R3 을 이용한 데이터 분석 시스템 만들기↩

기업문화 엿볼 때, 더팀스

로그인

/