스토리 홈

인터뷰

피드

뉴스

조회수 981

BarkeMon Go Design

요즘 전 세계에 Pokemon Go 광풍이 불고있다. 페북 타임라인의 90%이상이 포켓몬고 관련 소식으로 도배가 될 정도다. 우리나라에는 지도관련 이슈로 서비스되는 지역이 거의 없지만, 속초지역에 가능하다는 소식에 속초행 버스표가 매진될 정도다.사실 필자가 서비스 중인 바크 앱도 1/위치기반 앱, 2/모든 유저가 8개의 개 캐릭터가 되는 앱, 3/익명의 사람들과 즐기는 앱이라는 3가지 이유로 포켓몬고와 어느정도 유사한 점이 있다... 라고 말하면 욕먹을 수 있으니 그냥 유사한 가치를 전달하기를 희망한다... 로 하겠다.이에, Pokemon Go를 패러디해서 BarkeMon Go를 디자인 해 보았다. 다음과 같은 기본적인 앱 피처가 존재하는 AR 위치기반 어플이다. (어디까지나 패러디임을 다시한번 밝힌다)1. 메인 화면에서 바크를 짖다 보면 근처에 있는 바크 유저가 디텍트 된다.2. 바크 유저가 사정거리 안으로 접근하면 AR모드로 전환된다.3. AR모드에서 아직 저 유저가 누군지는 모르나 그 유저의 캐릭터가 보인다.4. 캐릭터에 바크볼을 던져서 그 유저를 캐치할 수 있다.5. 캐치된 유저는 에너지가 다 달게되고 캐치한 유저는 에너지팩, 레벨업 등 다양한 혜택을 얻는다.근처에 있는 바크 유저 디텍트내가 만일 강남역에 있다면, 바크 앱을 키고 다음 동영상과 같이 바크를 겁나게 짖어본다. 현재 반경 1마일 이내에 있는 유저들이 보이고, 서로 짖을 수 있다.<iframe width="940.000000" height="529.000000" src="//play-tv.kakao.com/embed/player/cliplink/v2cacshmNlMnmw4JJsxqM3s@my?service=daum_brunch§ion=article&showcover=1&showinfo=0&extensions=0&rel=0" frameborder="0" allowfullscreen="">근처에 있는 바크 유저에게 마구 짖어댄다.사정거리 내 접근 시 AR모드 전환내가 일부러 근처에 있는 유저를 디텍트하면서 접근할 수 있다. 물론 상세한 지도는 보이지 않는다. 상세한 지도가 보이게 되면 개인정보보호 이슈도 있고, 유저 발견이 너무 쉬워지니까. 서로 반경 50m 이내로 근접하게 되면 AR모드를 킬 수 있다. 아래와 같이 AR모드를 키고 유저를 찾다 보면 그 유저가 있는 방향에서 그 유저의 캐릭터가 보인다. 포켓몬고에서 하는 것 처럼 저 유저를 향해 바크볼을 던진다. 서로 먼저 바크볼을 던져서 캐치하는 유저가 이기는거고, 바크볼에 갇힌 유저는 바크에너지가 전량 소모된다. 이긴 유저는 바크 에너지팩, 레벨업 등 다양한 혜택을 얻는다. 50m 이내에 유저가 접근하면 AR모드로 전환할 수 있다.신개념 소셜 AR게임 BarkeMon Go!!포켓몬고는 트레이너가 근처에 있는 포켓몬을 잡는 게임이라면 바크몬고는 실제 바크 유저를 잡는 게임이다. 아래와 같이 다양한 장소에서 걸어다니는 실제 바크 유저를 잡는 재미가 있는 앱이다. 출시 예정일은... 미.. 미정이다. 글쓴이는 스팀헌트 (Steemhunt) 라는 스팀 블록체인 기반 제품 큐레이션 플랫폼의 Co-founder 및 디자이너 입니다. 비즈니스를 전공하고 대기업에서 기획자로 일하다가 스타트업을 창업하고 본업을 디자이너로 전향하게 되는 과정에서 경험한 다양한 고군분투기를 연재하고 있습니다.현재 운영중인 스팀헌트 (Steemhunt)는 전 세계 2,500개가 넘는 블록체인 기반 앱들 중에서 Top 10에 들어갈 정도로 전 세계 150개국 이상의 많은 유저들을 보유한 글로벌 디앱 (DApp - Decentralised Application) 입니다 (출처 - https://www.stateofthedapps.com/rankings).스팀헌트 웹사이트 바로가기
조회수 914

나는 수줍은 사람입니다

이미지 _tvN '응답하라 1988'“이 이상혁이 진짜 우리 상혁이야?” 제 이모님께서 신문기사를 보시고 제 어머니께 전화하셔서 던진 한 마디입니다. 오랫동안 연락이 없었던 동창들도 제게 문자나 전화를 통해 같은 것을 묻습니다. 자신이 아는 상혁이가 이렇게 신문에 나오고 남들 앞에 나설 리가 없다며 말입니다.이미지_ 한국경제 인터뷰그렇습니다. 저는 참 수줍음이 많은 사람입니다. 처음 창업을 해서 지금에 오기까지, 10년이 넘는 세월 동안 했던 언론 매체와의 인터뷰는 정말이지 손에 꼽습니다. 참으로 부족한 제가 마치 무엇이라도 된 것 마냥 나서는 것이 싫어서이기도 하지만, 애당초 대중 앞에 서는 것이 어색하고, 또 조금은 두렵기도 합니다. 그런 제가 이렇게 블로그라는 매체를 통해 여러분께 인사를 드리기로 결심한 데는 적잖은 고민과 용기가 필요했습니다. 앞으로 저와 저희 경영진, 그리고 다른 필진들이 이 공간을 통해 여러분께 전해드릴 이야기들이, 정말 편견 없이 솔직하고 진정성 있게 전달될 수 있을지에 대한 걱정도 있었습니다. 그럼에도 불구하고 이렇게 소통을 위한 첫 걸음을 내딛기로 한 이유는, 대한민국 땅에서 새로운 방법으로 성장과 벤처 생태계 촉진을 위한 도전을 하고 있는 우리 창업가들의 이야기를 진솔하게 담아보고 싶었기 때문입니다.저희 옐로모바일에는 저와는 비교도 할 수 없을 정도로 멋진 창업가 분들이 많이 계십니다. 이 분들이 하루하루 보여주는 기업가 정신의 열매들은 옐로모바일의 모든 임직원들에게 정말 큰 귀감이 되고 있습니다. 이 작은 영웅들을 가까이서 모시면서 저희가 느껴왔던 창업과 기업가 정신이라는 것에 대해, 우리끼리만 누리기는 아까운 마음입니다. 이에 이를 여러분과 나누어 볼 수 있다면 정말 좋겠다는 단순한 의도에서 출발해 보고자 합니다. 옐로모바일이 바라보는 창업과 기업가 정신, 옐로모바일의 창업가들, 옐로인들의 이야기, 뿌듯했던 순간들, 옐로모바일의 사업이 궁금하다면, 옐로에게 주어진 질문들… 이와 같이 다양한 코너를 통해 다방면으로 저희의 이야기를 풀어 내고, 또 여러분과 소통하고자 합니다. 앞으로 이 곳에서 펼쳐질 옐로모바일, 그리고 대한민국 창업가들의 이야기에 많은 관심을 부탁드리며, 저는 곧 저희 사내기자 Y와의 인터뷰로 다시 찾아 뵙겠습니다.옐로모바일 대표이사이상혁 올림
조회수 1469

마케터를 위한 딥 링크 만들기

더 나은 사용자 경험 딥 링크(Deep Linking)는 사용자를 최종 목적지로 곧장 연결시켜 줍니다. 따라서 즉각적인(On-demand) 해결이 필요한 니즈를 가진 사용자일수록 딥 링크를 통해 복잡한 이동 과정을 생략할 수 있으며, 이는 더 나은 사용자 경험으로 이어집니다.광고에서 매력적인 상품을 발견한 사용자는 광고 클릭, 앱 설치, 앱 실행의 과정을 거치게 됩니다. 해당 상품에 대한 실제 정보는 앱 초기화면에서 검색을 거친 후에나 확인 가능합니다. 하지만 광고에 딥 링크가 적용되어 있으면 사용자는 앱 실행 후 곧바로 광고에서 봤던 상품으로 이동합니다. 따라서 딥 링크는 마케팅 메시지와 랜딩 페이지 사이의 일관성을 유지시키는 역할을 합니다.(딥 링크 적용 후, 마케팅 메시지와 랜딩 페이지의 일관성 유지는 쉬워지고 최종 페이지로의 이동단계는 축소됩니다. 이를 통해 사용자 경험과 전환성을 모두 높일 수 있습니다.)딥 링크 찾기이 딥 링크 활용이 쉽지 않은 이유는 딥 링크를 어디에서 찾아야 할 지 알 수 없기 때문입니다. 딥 링크는 웹사이트 URL처럼 쉽게 확인할 수 없으며, 앱을 만들면서 자동으로 생성되지 않습니다. 그렇다면 딥 링크는 어떻게 만드는 것일까요?딥 링크 생성 프로세스를 단순화하면 다음과 같이 표현할 수 있습니다.마케터가 딥 링크 요청 -> 개발자가 딥 링크 생성 -> 마케터가 딥 링크 적용마케터는 앱 빌드를 하지 않기 때문에 딥 링크를 직접 만들기 어렵습니다. 결국 개발자의 도움이 필요합니다. 따라서 이 부분에선 두 번째 단계인 딥 링크 생성을 마케터가 알기 쉽게 설명하려고 합니다. 생성 작업을 알게 되면 개발자와 원활하게 커뮤니케이션 할 수 있고, 이를 통해 작업의 효율성도 높아질 것이라 기대합니다.딥 링크 만들기마케터의 요청을 받은 개발자가 딥 링크를 생성하기 위해 어떤 작업을 하게 되는지 작업 순서대로 설명 하겠습니다. 이해를 돕기 위해 안드로이드 기준으로 말씀 드리겠습니다.(1) 안드로이드 매니페스트와 액티비티“이 화면으로 연결되는 딥 링크를 만들어 주세요”라고 요청이 왔다고 가정하겠습니다. 개발자는 그 화면에 해당하는 액티비티(Activity)를 안드로이드 매니페스트(Android Manifest) 파일에서 찾습니다.안드로이드 매니페스트 파일은 앱의 모든 구성요소가 설명되어 있는 명세서라고 이해하시면 좋습니다. 앱에 어떤 기능을 만들어 놓았더라도, 안드로이드 매니페스트에 그 기능이 기술되어 있지 않다면 그 기능은 존재하지 않는 것과 마찬가지입니다. 따라서 딥 링크로 연결할 화면 역시 안드로이드 매니페스트에 존재하고 있으며, 개발자는 이 액티비티에 딥 링크를 지정하기 위해 안드로이드 매니페스트를 수정하게 됩니다.(2) 인텐트 필터 추가앱 입장에서 생각해 보면 인텐트 필터(Intent-filter)를 쉽게 이해할 수 있습니다. 가만히 있던 앱은 갑작스럽게 딥 링크를 통해서 앱의 특정 액티비티를 열어달라는 호출을 받게 됩니다. 호출 받은 입장에서는 어떤 의도(Intent)로 호출을 했는지 요약된(filter) 설명을 들을 수 있으면 좋겠지요. 인텐트 필터는 위와 같이 액티비티를 호출하기 위한 목적과 방법을 앱에 알려주는 역할을 합니다.개발자는 안드로이드 매니페스트에서 딥 링크로 연결될 액티비티를 찾은 뒤, 특정 딥 링크에서 호출이 오면 이 액티비티를 열면 된다는 명령을 미리 기술하게 됩니다. 위의 설명이 실제로 어떻게 구현되는지 예제를 보면서 확인하겠습니다.<!-- 딥 링크로 호출할 액티비티 입니다 -->android:name="com.example.android.GizmosActivity"android:label="@string/title_gizmos" ><!-- 인텐트 필터가 추가되었습니다 --><!-- "https://www.example.com/gizmos”라는 URI를 허용합니다 -->android:host="www.example.com"android:pathPrefix="/gizmos" /><!-- note that the leading "/" is required for pathPrefix--><!-- "example://gizmos”라는 URI를 허용합니다 -->android:host="gizmos" />(Source: 구글 개발자 사이트)“com.example.android.GizmosActivity”라는 액티비티를 호출하기 위해 를 추가한 코드입니다.  항목은 화면 조회를,  항목은 브라우저에서 앱을 호출할 수 있도록 하는 역할을 하며, 딥 링크 구현을 위해서 기본적으로 포함되어야 하는 항목입니다.(3) 딥 링크 주소 생성 하위에  항목에서 딥 링크 주소를 ‘지정’할 수 있습니다. 없던 주소를 새로 만드는 것이기 때문에 주소에 어떤 문자열을 사용할지 지정해줘야 합니다. 일반적으로 개발팀 내부 규칙을 따르게 됩니다.딥 링크 주소는 일반적으로 커스텀 URI, 커스텀 스킴 등으로 부릅니다. 예제에서 두번째  항복을 보면 scheme으로 example을, host로 gizmos를 지정하고 있습니다. 스킴과 호스트를 조합한 것이 딥 링크 주소로 사용됩니다. 예제의 경우엔 example://gizmos가 딥 링크 주소가 됩니다. 결과적으로 사용자가 example://gizmos URI가 적용된 광고를 클릭하면 “com.example.android.GizmosActivity” 화면이 호출되며 앱이 실행됩니다.보완책: DEFERRED DEEP LINK딥 링크는 앱의 특정 화면을 호출합니다. 하지만 앱이 없는 사용자가 광고를 클릭한다면 어떻게 될까요? 아무런 동작도 일어나지 않을 것이고 어렵게 확보한 클릭을 잃게 되는 결과로 이어질 것입니다. 이런 경우의 보완책으로 와이즈트래커는 지연된 딥 링크(Deferred Deep Link)를 채택하고 있습니다.위 그림에서 볼 수 있듯이, 앱을 설치한 사용자가 광고를 클릭하면 특정 화면이 호출된 상태로 앱이 실행됩니다. 앱을 설치하지 않은 사용자가 광고를 클릭하면 우선 앱 마켓으로 이동하게 되며, 사용자가 앱을 설치한 후 최초 실행시 딥 링크가 호출하려던 화면이 나타나게 됩니다.만약 지연된 딥 링크가 없다면 사용자 경험을 설계하는데 상당히 많은 경우의 수가 생깁니다. 당연히 캠페인 기획, 실행, 운영 시에 고려할 사항도 크게 늘어나게 됩니다. 따라서 딥 링크를 사용하기로 결정 했다면 와이즈트래커와 같은 3rd Party를 통해 더욱 고도화된 기능의 장점을 충분히 활용하시면 좋겠습니다. * WISETRACKER는 모바일 광고 성과 측정부터 In-app 이용자/컨텐츠 분석, 푸시메시지 최적화까지 지원하는 모바일 통합 분석/타겟팅 솔루션입니다. 와이즈트래커 솔루션의 무료체험을 원하실 경우 여기를 클릭해주세요.* WISETRACKER가 제공하는 무료 데이터 분석 컨설팅를 원하신다면 여기를 클릭해주세요.#와이즈트래커 #마케터 #마케팅 #꿀팁 #인사이트 
조회수 1582

협업툴 플로우 리뉴얼 기능

협업툴 플로우 리뉴얼 기능협업툴 플로우 리뉴얼 PC버전이 우선 공개되었고 순차적으로 모바일 앱도 리뉴얼 버전이 오픈됩니다. 어떤 기능이 변경되었는지 정리하였습니다.플로우 리뉴얼 버전 바로 보기 https://youtu.be/K_k3a5ljUJM01. 눈이 편안해진 디자인To be프로젝트 박스의 색상 영역을 줄여, 전체적인 디자인 통일성을 강화, 전체적인 색상이 줄어들어 훨씬 깔끔해졌죠. 눈에 주는 피로감도 줄어들었습니다.02. 넓어진 업무 공간To be넓은 모니터에서도 사용하기 좋게, 좌우 데드스페이스를 없애 업무공간이 넓어진만큼 더 많은 업무 내용을 한눈에 볼 수 있습니다.03. 빨라진 속도AS IS구버전의 플로우는 프로젝트 접속 시 3초 이상 시간 소요되었습니다.TO BE프로젝트에서 프로젝트로 이동할 때, 알림 리스트를 클릭해서 이동할 때, 채팅을 열 때 등 모든 상황별 이동 속도가 완전히 빨라졌습니다.04. 이동 동선 최소화이동동선을 최소화해서, 이제 프로젝트를 들락날락할 필요가 없어졌습니다!뿐만 아니라, 뒤로가기와 새로고침 기능도 추가되어 원래 보고 있던 게시물로 돌아가기 위해 헤매지 않아도 됩니다.알림을 팝업 화면으로 확인 가능알림을 눌러도 내가 보고 있던 페이지를 벗어나지 않고, 팝업 화면에서 알림 내용을 확인할 수 있습니다. 보고 있던 화면에서 알림으로 뜬 게시물을 바로 확인하고, 닫을 수 있습니다.좌측 메뉴바에 ‘최근 업데이트’ 탭 추가최근 업데이트된 프로젝트는 좌측 메뉴바에서 바로 볼 수 있어 최근 프로젝트를 확인하기 위해서 메인페이지로 이동할 필요가 없어졌습니다.모아보기에서 ‘업무 추가’를 한번에!업무 리스트를 모아보다가 추가하고 싶은 관련 업무가 떠오를 때! 프로젝트로 이동해서 새 게시물을 등록할 필요가 없습니다. 업무 모아보기 페이지에서 ‘+업무 추가’ 기능이 생겨, 바로 업무 추가를 할 수 있고 프로젝트 캘린더에서는 일정을 바로 추가할 수 있습니다05. 쉬워진 검색 기능검색 카테고리를 선택할 수 있어, 더 빠르게 검색하고 쉽게 찾을 수 있습니다!검색 필터를 설정하기 귀찮아도 검색 결과가 알아서 카테고리별로 분류되기 때문에 더 쉽게 찾을 수 있게 되었습니다.리뉴얼 활용법 Tip1. 달라진 버튼명 확인좌측 메뉴바의 버튼명 일부가 변경되었습니다![달라진 명칭]전체 일정 ➡ 캘린더전체 파일 ➡ 파일함담아둔 글 ➡ 북마크나를 지정 ➡ 나를 언급보관함 ➡ 프로젝트 폴더리뉴얼 활용법 Tip2 달라진 위치 확인‘관리자 설정’, ‘직원 초대’ 위치 변경우측 상단에 있던 ‘직원 초대’와 ‘관리자 설정’은 좌측 메뉴바 하단으로 위치가 달라졌습니다. (관리자 설정은 어드민으로 버튼명이 변경되었습니다.)프로젝트 내 ‘파일함’, ‘업무’, ‘일정’, ‘할일’ 위치 변경우측 메뉴 탭에 있던 항목들의 위치가 달라졌습니다! (‘일정’은 ‘캘린더’로 버튼명이 변경되었습니다.)+할일은 게시물 유형별 필터 기능이 새롭게 추가되어 메뉴탭에서는 삭제되었습니다!LIVE로 진행되는 온라인 시연회에서 플로우 컨설턴트와 실시간으로 질문을 주고 받으며 달라진 플로우를 확인할 수 있습니다.온라인 시연회 신청링크: https://forms.gle/JD97p3jeBtRnQd3eA협업툴 플로우 바로가기
조회수 1744

[Tech Blog] PhantomJS를 Headless Chrome(Puppeteer)로 전환하며

버즈빌에서는 모바일 잠금화면에 내보내기 위한 광고 및 컨텐츠 이미지를 생성하기 위한 PhantomJS 렌더링 서버를 다수 운영하고 있습니다. 일반적으로 PhantomJS는 웹페이지 캡쳐에 많이 쓰이지만, 기본적으로 headless하게 웹페이지를 렌더링하고 캡쳐할 수 있다는 특성 때문에 동적인 이미지 생성에도 많이 활용됩니다. 버즈빌의 렌더링 서버는 200개 이상의 컨텐츠 프로바이더로부터 실시간으로 잠금화면 컨텐츠 이미지를 생성하고 있어 분당 수백 건의 이미지를 안정적으로 생성하는 것이 가능해야 합니다.  렌더링 서버의 스케일링 이슈를 해결하기 위해 버즈빌에서는 여러 대의 렌더링 서버를 둬서 횡적으로 확장을 함과 동시에, 개별 서버 내에서도 리소스 사용률을 높이기 위해 Ghost Town이라는 라이브러리를 작성해 PhantomJS 프로세스 풀을 구성하여 사용하고 있었습니다(Scaling PhantomJS With Ghost Town ) 한편, 시간이 지나면서 잠금화면에서 렌더링하는 이미지 템플릿의 종류가 다양해지고, emoji 및 여러 특수문자를 표현하기 위해 렌더링 서버에 여러 폰트(대표적으로 Noto Sans CJK)를 설치해야 하는 요구사항이 추가됐는데, PhantomJS에서 폰트 렌더링이 일관적이지 않은 문제가 발생했습니다. 동일한 템플릿이지만 폰트가 비일관적으로 렌더링되고 있는 모습 이 문제의 정확한 원인은 결국 찾지 못했지만 PhantomJS의 이슈였거나 시스템 상에 폰트가 시간이 지나면서 추가 설치됨에 따라 font cache가 서버마다 일관되지 않은 상태가 되었기 때문인 것으로 짐작하고 있습니다. 다른 워크로드와 마찬가지로 렌더링 서버도 최초에는 packer를 이용해 일관되게 이미지를 빌드하고 업데이트하려고 했지만, 자주 기능이 추가되거나 배포되는 서비스가 아니기에 서버를 오래 띄워놓고 수동으로 유지보수를 한 케이스들이 누적되어 더 이상 packer를 이용해 시스템이나 폰트를 최신 상태로 유지하는 것이 어려운 상태였습니다. 모든 눈꽃송이가 자세히 보면 조금씩 다르게 생겼다는 것에서 비롯된 snowflake, 즉 배포된 서버들이 시간이 지남에 따라 조금씩 다른 상태가 된 것입니다. 평소에는 문제가 없어 보이지만, 추가적인 확장성이 필요해 scale out을 하거나 새로운 템플릿을 개발해 배포를 하면 문제가 발생하는 상황이었습니다. 사실 더 큰 문제는 PhantomJS 프로젝트가 더 이상 관리되지 않는다는 점이었습니다. 2017년 Google Chrome 59버전부터 Headless Chrome이 내장되기 시작하였고, 곧바로 Node API인 puppeteer가 릴리즈 되어, 현시점에서 가장 많이 쓰이는 렌더링 엔진을 손쉽게 headless로 사용할 수 있는 환경이 되었습니다. 때문에 PhantomJS 관리자가 사실상의 중단을 선언하였고, 2018년에는 최초 개발자에 의해 프로젝트가 아카이브 되었습니다. 프로젝트가 업데이트되지 않는 것은 템플릿에 최신 CSS 스펙을 사용하지 못한다는 것을 의미하고, 버그 수정도 되지 않기에 어플리케이션의 유지보수가 굉장히 어려워짐을 의미합니다. 현재까지의 문제점을 정리하면 아래와 같습니다.  자주 배포되지 않는 서비스 특성으로 인한 서버들이 snowflake화 되는 현상(특히 폰트) PhantomJS의 개발 중단으로 인해 버그 픽스 및 최신 CSS 속성 사용이 어렵게 되고, 향후 유지보수나 새로운 템플릿 개발이 어려워짐  해결방안은 명확했습니다. 첫번째 문제를 해결하기 위해서는 어플리케이션과 폰트가 설치된 시스템을 통째로 컨테이너로 만들고, CI/CD 파이프라인을 통해 지속적으로 빌드하여 snowflake화 되지 않도록 하면 됩니다. 사실 최초에 packer를 이용해 AMI 이미지를 생성하도록 구성이 되어있었기에, 매 배포마다 AMI를 새로 생성하고 지속적으로 렌더링 서버를 배포하는 환경이기만 했으면 snowflake를 방지할 수 있었을 것입니다. 하지만 자주 기능이 추가되거나 배포되는 서비스가 아닌데다, AMI를 빌드하는 과정이 CI/CD에 통합돼 있지 않고 어플리케이션만 지속적으로 배포하는 환경이었기에 편의상 서버를 종료하지 않고 장기간 관리를 해 오게 되었고, packer로 새로운 AMI 이미지를 빌드하는 것이 어려워 졌습니다. 때문에 AMI 빌드를 통한 배포 대신, 이미 운영 중인 kubernetes 클러스터에 도커 컨테이너를 빌드해 immutable한 형상으로 배포하기로 결정하였습니다. 두번째 문제의 간단한 해결책은 PhantomJS를 puppeteer로 변경하는 것입니다. 이 부분은 생각보다 간단했습니다. 의도했는지는 알 수 없으나 puppeteer의 api는 PhantomJS와 꽤나 비슷합니다. drop-in replacement까진 아니지만, PhantomJS api 호출하는 부분만 살짝 바꿔주는 정도로 교체가 가능하였습니다. 물론 교체만 하였다고 해서 기존에 개발된 템플릿이 의도된 대로 출력되는 것을 보장하지는 않기에, 렌더링 서버가 렌더링하는 수많은 템플릿들을 PhantomJS와 puppeteer로 각각 출력하여 일일히 비교하는 작업이 필요했습니다. 어떤 템플릿이 어떤 인자를 필요로하며 의도된 출력 결과가 무엇인지에 대한 정의가 남아있지 않았기에 템플릿마다 샘플 케이스들을 생성하는 작업이 필요했습니다. 아직까지는 수동으로 결과를 비교해야하는 문제점이 있지만 적어도 직접 확인할 수 있는 것은 큰 도움이 되었습니다. 향후에는 자동화된 테스트 케이스를 구성하여 기능 개발이 좀 더 용이하도록 보완할 계획입니다. 결과는 만족스러웠습니다. 많은 경우 기존과 출력 결과가 달랐지만, 최신의 크롬 웹킷이 사용되면서 오히려 템플릿을 개발할 때 의도했던대로 CSS를 더 정확하게 렌더링하게 된 것이었습니다.  FROM node:10-slim RUN apt-get update && \ apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \ ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget unzip && \ wget https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64.deb && \ dpkg -i dumb-init_*.deb && rm -f dumb-init_*.deb && \ apt-get clean && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* RUN yarn global add [email protected] && yarn cache clean ENV NODE_PATH="/usr/local/share/.config/yarn/global/node_modules:${NODE_PATH}" RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser # Set language to UTF8 ENV LANG="C.UTF-8" RUN wget -P ~/fonttmp \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSans-unhinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKkr-hinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKtc-hinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKsc-hinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoColorEmoji-unhinted.zip \ && cd ~/fonttmp \ && unzip -o '*.zip' \ && mv *.*tf /usr/share/fonts \ && cd ~/ \ && rm -rf ~/fonttmp WORKDIR /app # Add user so we don't need --no-sandbox. RUN mkdir /screenshots && \ mkdir -p /home/pptruser/Downloads && \ mkdir -p /app/node_modules && \ chown -R pptruser:pptruser /home/pptruser && \ chown -R pptruser:pptruser /usr/local/share/.config/yarn/global/node_modules && \ chown -R pptruser:pptruser /screenshots && \ chown -R pptruser:pptruser /usr/share/fonts && \ chown -R pptruser:pptruser /app # Run everything after as non-privileged user. USER pptruser RUN fc-cache -f -v COPY --chown=pptruser:pptruser package*.json /app/ RUN npm install && \ npm cache clean --force COPY --chown=pptruser:pptruser . /app/ ENTRYPOINT ["dumb-init", "--"] CMD ["npm", "start"]  puppeteer를 사용하면서 약간의 권한 문제가 있어서 결과적으로 위와 같은 Dockerfile을 작성하게 되었는데, puppeteer 도커 이미지 작성에 관한 최신 정보는 여기서 확인할 수 있습니다. 컨테이너 오케스트레이션(K8s)을 사용하면 process 기반의 스케일링은 컨테이너를 여러대 띄워 로드밸런싱을 손쉽게 할 수 있지만, 개별 컨테이너의 throughput을 향상시키기 위해 기존에 Ghost town을 작성해 PhantomJS 프로세스 풀을 만든 것처럼 크롬 프로세스 풀을 구성하기로 하였습니다. 프로세스 풀 구성에는 generic-pool 라이브러리를 사용하였으며 아래처럼 구성하였습니다.  const puppeteer = require("puppeteer"); const genericPool = require("generic-pool"); const puppeteerArgs = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]; const createPuppeteerPool = ({ max = 5, min = 2, maxUses = 50, initialUseCountRand = 5, testOnBorrow = true, validator = () => Promise.resolve(true), idleTimeoutMillis = 30000, ...otherConfig } = {}) => { const factory = { create: async () => { const browser = await puppeteer.launch({ headless: true, args: puppeteerArgs }); browser.useCount = parseInt(Math.random() * initialUseCountRand); return browser; }, destroy: (browser) => { browser.close(); }, validate: (browser) => { return validator(browser) .then(valid => Promise.resolve(valid && (maxUses <= 0 || browser.useCount < maxUses xss=removed xss=removed xss=removed> genericAcquire().then(browser => { browser.useCount += 1; return browser; }); pool.use = (fn) => { let resource; return pool.acquire() .then(r => { resource = r; return resource; }) .then(fn) .then((result) => { pool.release(resource); return result; }, (err) => { pool.release(resource); throw err; }); }; return pool; }; module.exports = createPuppeteerPool;  Caveats PhantomJS에서 puppeteer로 전환함에 있어서 몇가지 주의해야 할 점이 있었는데요. 첫째는 기존에 사용하던 템플릿의 html에 이미지 소스를 file:// url 프로토콜을 이용해 로드하는 경우가 있었는데, PhantomJS에서는 정상적으로 로드가 되지만 Headless Chrome에서는 보안 정책으로 인해 로컬 파일을 로드할 수 없었습니다(관련 이슈). 때문에 로컬 이미지가 필요한 템플릿은 Express 서버에서 static file serving을 하도록 하고 http:// 프로토콜로 변경하였습니다. 다음으로 발생한 문제는 PhantomJS을 이용한 기존 구현에서는 jade template을 compile한 후 page 객체의 setContent 메소드를 이용해 html을 로드하였는데, puppeteer에서는 page#setContent API 호출 시 외부 이미지가 로드될 때까지 기다리지 않는다는 점입니다. puppeteer 에 올라온 관련 이슈에서는 `=setContent`= 대신 아래와 같이 html content를 data URI로 표현하고 page#goto의 인자로 넘기면서 waitUntil 옵션을 주는 방식을 해결방법으로 권하고 있습니다.  await page.goto(`data:text/html,${html}`, { waitUntil: 'networkidle0' });  이 때 주의해야 할 점은 waitUntil의 옵션으로 networkidle0이나 networkidle2 등을 사용하면 외부 이미지가 충분히 로드될 때 까지 기다리는 것은 맞지만, 500ms 이내에 추가적인 네트워크 커넥션이 발생하지 않을 때까지 기다리는 옵션이기 때문에 외부 이미지가 로드되더라도 추가적으로 500ms를 기다리게 됩니다. 때문에 SPA 웹페이지를 캡쳐하는 경우가 아니라 정적인 html을 로드하는 경우라면 `load` 이벤트로 지정하면 됩니다. 이외에도 향후에 프로젝트의 유지관리나 운영 중인 서비스의 모니터링을 위해 Metrics API 엔드포인트를 만들어 prometheus에서 메트릭을 수집할 수 있도록 하고 grafana 대시보드를 구성하였습니다. 이 대시보드는 어떤 템플릿이 실제로 사용되고 있는지, 템플릿 렌더링에 시간이 얼마나 소요되는지 등을 모니터링할 수 있도록 구성하여 사용되지 않고 있는 템플릿을 판단하거나 서비스 지표를 모니터링 하는 데 이용하고 있습니다. grafana와 prometheus를 이용해 구현한 렌더링 서버 모니터링 대시보드. 마치며 최근에 들어서는 PhantomJS를 사용하던 많은 곳에서 puppeteer로의 전환을 해오고 있어 본 포스팅에서 다루고 있는 내용이 크게 새로운 내용은 아닐 수 있습니다. 하지만 버즈빌에서는 렌더링 서버가 과거에 이미 PhantomJS를 사용하는 것을 전제로 상당한 최적화가 진행되어 왔고, 꽤나 높은 동시 처리량이 요구되는 상황에서 puppeteer로 교체를 해버리기에는 여러 불확실한 요소들이 존재하는 상황이었습니다. 버즈빌의 핵심 비즈니스 중 하나인 잠금화면에 사용되는 이미지를 렌더링하는 서비스가 레거시(개발이 중단된 PhantomJS)에 의존하는 코드베이스 때문에 변경이 어려워지는 것은 향후 꽤나 큰 기술부채로 작용할 것이라 판단하였습니다. 이번 마이그레이션을 진행하면서는 이 부분을 염두에 두고 컨테이너를 사용해 CI/CD 파이프라인을 구축해 지속적으로 컨테이너 기반의 이미지를 생성하도록 변경하였고, 그 결과는 꽤나 만족스러웠습니다. 마이그레이션 이후 그간 밀려 있던 신규 템플릿 개발이나 신규 컨텐츠 프로바이더를 추가하는 과정이 수월해졌기 때문입니다. 빠르게 변화하는 비즈니스 요구사항에 대응하다보면 기술부채는 필연적으로 쌓일 수밖에 없습니다. 개발자에게는 당연히 눈에 보이는 모든 기술부채들을 청산하고 싶은 욕구가 있지만 늘 빚 갚는데 시간을 쓰고 있을 수만은 없는 노릇입니다. 리소스에는 한계가 있으니까요. 어떤 기술부채를 지금 당장 해결해야하는지 의사결정을 하는데 있어 고민이 된다면 일단 “측정”을 해보는 것을 권장합니다. 수치화된 지표가 있다면 당장 의사결정권자나 팀을 설득하는 데 사용할 수도 있지만, 서비스의 핵심 지표들을 하나 둘씩 모니터링 해나가다 보면 서비스에 대한 가시성이 높아지고 미래에 정말로 병목이 되는 지점을 찾아내기 쉬워질 것입니다. 참고 자료  https://docs.browserless.io/blog/2018/06/04/puppeteer-best-practices.html https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md Icons made by Freepik from Flaticon is licensed by Creative Commons BY 3.0    *버즈빌에서 개발자를 채용 중입니다. (전문연구요원 포함)작가소개 Liam Hwang, Software Engineer 버즈빌에서 DevOps를 담당하고 있습니다. Cloud Native 인프라를 구현하기 위해 여러 노력을 기울이고 있으며 새로운 기술들을 공부하는 것을 좋아합니다.
조회수 2084

Circle CI에서 rbenv를 이용해서 Ruby 2.2와 CocoaPods 0.39 버전 사용하기

최근 Circle CI에서 Ruby 버전을 2.3으로, CocoaPods 버전을 1.0으로 업그레이드함에 따라 발생하는 빌드 문제를 rbenv를 이용해서 해결한 경험을 공유합니다. 최종적으로 완성된 Gemfile과 circle.yml 파일은 마지막 섹션에서 확인하실 수 있습니다.1. CocoaPods 1.0지난 2015년 12월에 CocoaPods 1.0.0 베타 버전이 처음 공개되었습니다. CocoaPods이 1.0 버전으로 업그레이드되면서 굉장히 많은 변화가 있었는데요. 가장 큰 변화는 DSL입니다. 추상 타겟Abstract Target과 타겟 상속Target Inheritance이 새롭게 소개되면서, 0.39 버전까지 자주 사용되던 link_with 및 :exclusive => true와 같은 구문이 제거되었습니다.이에 따라 기존에 사용하던 Podfile이 CocoaPods 1.0 버전과는 호환되지 않는 문제가 발생했습니다. 이를 해결하기 위한 가장 좋은 방법은 새로운 DSL을 사용하여 Podfile을 다시 작성하는 것이지만, 꽤 많은 서드파티 라이브러리를 사용하는 StyleShare의 경우 새로운 DSL을 적용하여 빌드하면 각종 문제로 인해 빌드가 정상적으로 이루어지지 않았습니다. 4년동안 유지되고 있는 프로젝트이다보니, 레거시 Objective-C 코드와 라이브러리, 그리고 새로운 Swift 코드와 라이브러리가 혼용되어 사용되는 것도 원인 중 하나일 것입니다.따라서 StyleShare에서는 CocoaPods 0.39 버전을 사용하기로 결정을 했습니다. 하지만 최근 Circle CI에서 CocoaPods 버전을 공식적으로 1.0 버전으로 업그레이드하면서 빌드가 깨지기 시작했습니다. Circle CI 환경에서 CocoaPods 0.39 버전을 사용하려면 어떻게 해야 할까요?▲ ㅠㅠ2. Bundler를 이용해서 Gem 관리하기Bundler는 Ruby로 작성된 라이브러리들의 버전을 관리해주는 강력한 도구입니다. CocoaPods에서 Podfile에 의존성을 기재하듯, Bundler에서는 Gemfile에 의존성을 기재합니다.source 'https://rubygems.org' gem 'cocoapods', '~> 0.39' $ gem install bundler 명령어를 사용하면 Gemfile에 기재된 의존성 라이브러리들을 설치해줍니다. 이렇게 설치된 CocoaPods을 사용할 때에는 $ pod COMMAND 대신 $ bundle exec pod COMMAND 명령어를 사용해야 합니다.$ gem install bundler $ bundle install --path vendor/bundle $ bundle exec pod --version 0.39.0 3. Ruby 2.3과 CocoaPods 0.39Bundler를 사용해서 CocoaPods 0.39 버전을 사용하기만 하면 모든 문제가 해결될 줄 알았습니다. 하지만 더 큰 삽질이 남아있었는데요. 바로 Ruby 2.3 버전이 CocoaPods 0.39 버전과 호환되지 않는 것이었습니다.$ bundle exec pod install Updating local specs repositories Analyzing dependencies 신나게 $ bundle exec pod install 명령어를 실행하니, 의존성을 분석하는 듯 싶다가 갑자기 에러를 주르륵 뱉습니다. 에러 로그의 #### Error 항목을 보면 에러 메시지가 나와있습니다.NoMethodError - undefined method `to_ary’ for #이 에러 메시지로 CocoaPods GitHub 저장소의 이슈를 검색해보면 꽤나 많은 이슈가 올라와 있습니다. 이 이슈들을 보면, 모두 Ruby 버전이 2.3이라는 공통점이 있습니다. Ruby 버전을 2.2로 내렸더니 문제가 해결됐다는 댓글들도 굉장히 많고요. Circle CI의 Ruby 버전을 2.2로 낮추면 문제가 해결될 것 같습니다.Circle CI 문서 내용에 따라 circle.yml에 Ruby 버전을 기재해봅시다.machine: ruby: version: 2.2.5 그러나 Circle CI의 OS X 컨테이너에서는 Ruby 버전 변경을 지원하지 않는다고 합니다.▲ ㅠㅠ (2)4. rbenv를 이용해서 Ruby 2.2 사용하기그러다가 알게된 것이 바로 rbenv입니다. rbenv를 사용하면 여러개의 Ruby 버전을 깔끔하게 관리할 수 있게 됩니다. rbenv는 Homebrew를 사용해서 쉽게 설치할 수 있습니다.$ brew install rbenv rbenv는 ~/.rbenv 디렉토리에 안에 여러 Ruby 버전을 설치하고 관리합니다. rbenv를 설치한 뒤 가장 먼저 할 일은 환경변수 $PATH를 설정해주는 것입니다. $PATH에는 $HOME/.rbenv/shims와 $HOME/.rbenv/bin 경로가 포함되어있어야 합니다.4.1 환경변수 설정하기Circle CI에서는 환경변수를 설정하는 편리한 인터페이스를 제공합니다. 하지만, Circle CI에서 실행되는 각 명령어는 별도의 쉘에서 실행됩니다. 그말인 즉슨, 각 명령어가 실행되기 직전에 새로운 쉘이 실행되고, $PATH 환경변수를 덮어쓰는 .bash_profile이 실행된 후 명령어가 실행된다는 뜻인데요. 이렇게 될 경우 $PATH 환경변수의 가장 우선순위는 항상 /usr/local/bin이 가지게 됩니다. 그리고 같은 이유로 $ export FOO=bar와 같은 명령어도 사용할 수 없게 됩니다.1고민을 하다가 생각해낸 방법은 바로 .bash_profile의 내용을 변경(!)하는 것입니다. 그렇게 되면 우리가 원하는 $PATH를 항상 우선순위로 둘 수 있게 됩니다. 아래와 같이 환경변수를 설정하는 명령어를 .bash_profile의 가장 아랫줄에 삽입하도록 설정했습니다.machine: pre: - echo "export PATH=\$HOME/.rbenv/shims:\$HOME/.rbenv/bin:\$PATH" >> .bash_profile - echo "export RBENV_SHELL=bash" >> .bash_profile 4.2 rbenv에 Ruby 2.2 설치하기그 다음으로 할 일은 원하는 Ruby 2.2 버전을 설치하는 것입니다. $ rbenv install -l을 사용해서 설치 가능한 모든 Ruby 버전을 조회할 수 있고, $ rbenv install 2.2.5 명령어를 사용해서 2.2.5 버전을 설치할 수 있습니다.$ rbenv install -l Available versions: 1.8.5-p113 1.8.5-p114 1.8.5-p115 1.8.5-p231 ... $ rbenv install 2.2.5 이렇게 설치된 버전은 두 가지 방법으로 사용될 수 있습니다. 한 가지 방법은 시스템 전체에서 사용하는 것이고, 다른 한 가지 방법은 프로젝트 단위로 사용하는 방법입니다. 시스템 전체에서 사용하려면 $ rbenv global 2.2.5 명령어를, 프로젝트 단위로 사용하려면 $ rbenv local 2.2.5명령어를 사용합니다.global 명령어를 사용해서 Ruby 버전을 선택하면 ~/.rbenv/version 파일에 선택된 버전이 기록됩니다.$ rbenv global 2.2.5 $ cat ~/.rbenv/version 2.2.5 local 명령어를 사용하면 현재 디렉토리의 .ruby-version 파일에 선택된 버전이 기록됩니다.$ rbenv local 2.2.5 $ cat .ruby-version 2.2.5 local 명령어로 선택된 Ruby 버전은 global 명령어로 선택된 Ruby 버전보다 우선순위가 높습니다. $ rbenv version 명령어를 사용하면 현재 선택된 버전을 확인할 수 있습니다.$ rbenv version 2.2.5 (set by /project/path/.ruby-version) Circle CI에서는 편의를 위해 global 명령어를 사용해서 Ruby 버전을 선택하도록 했습니다.dependencies: pre: - brew update - brew install rbenv - rbenv install 2.2.5 - rbenv global 2.2.5 4.3 Bundler 다시 설치하기rbenv를 사용해서 새로운 Ruby 버전을 설치했기 때문에, Circle CI 시스템에서 제공하는 Gem도 다시 설치해야 합니다. 우리는 Bundler로 Gem 의존성을 관리하기로 했으므로, Bundler만 재설치합니다.$ gem install bundler --no-ri --no-rdoc $ rbenv rehash $ gem install 명령어를 실행한 후에는 $ rbenv rehash 명령어를 실행해서 executable 경로들을 재설정해주어야 합니다.4.4 ~/.rbenv 경로 캐싱하기rbenv를 사용해서 Ruby를 설치하는 과정이 굉장히 오래 걸립니다. 이 경우, Circle CI에서 제공하는 캐싱 기능을 사용해서 이 과정을 한 번만 하고 건너뛸수 있게 됩니다.dependencies: cache_directories: - ~/.rbenv 위와 같이 circle.yml를 설정해주면 컨테이너 실행시 ~/.rbenv 디렉토리가 캐시로부터 설정됩니다. 캐싱된 디렉토리를 사용하는 경우 Ruby 버전이 미리 설치되어있기 때문에 $ rbenv install시에 --skip-existing 옵션을 추가해주어서 캐싱된 버전을 재설치하지 않도록 합니다.5. 마치며최종적으로 완성된 Gemfile과 circle.yml 파일은 다음과 같습니다.Gemfilesource 'https://rubygems.org' gem 'cocoapods', '~> 0.39' circle.ymlmachine: pre: - echo "export PATH=\$HOME/.rbenv/shims:\$HOME/.rbenv/bin:\$PATH" >> .bash_profile - echo "export RBENV_SHELL=bash" >> .bash_profile xcode: version: 7.3 dependencies: cache_directories: - ~/.rbenv pre: - brew update - brew install rbenv - rbenv install 2.2.5 --skip-existing - rbenv global 2.2.5 - gem install bundler --no-ri --no-rdoc - rbenv rehash - bundle install --path vendor/bundle override: - bundle exec pod --version - bundle exec pod install https://circleci.com/docs/environment-variables/#custom ↩#스타일쉐어 #개발 #개발자 #개발팀 #후기 #일지 #인사이트
조회수 633

올바른 크리에이티브 디렉터, 이시우

   올바른 크리에이티브 디렉터, 이시우 믹픽인사이드는 믹스앤픽스와 함께 성장하고 있는 구성원들의 이야기를 들어보는 시리즈입니다. 다양한 분야의 사람들이 모인 만큼 풍부한 이야기가 완성되었는데요. 각각의 개성들이 꿈을 향해 달려온 시간, 믹픽과 함께 나아갈 시간에 대해 나누며 우리가 하고 싶은 일로 꾸려나가는 회사를 그려봅니다.                Q1. 간단한 자기소개 부탁드립니다.  안녕하세요. 크리에이티브 디렉터이자 콘텐츠 기획자 이시우입니다.    Q2. 어떤 분야에서 크리에이티브 디렉터로 활동을 하신 건가요?  사람들의 감정을 일러스트로 표현하는 작업을 했어요. 열등감, 연인과의 관계, 친구 관계 등 다양한 주제를 작품에 담았죠.    Q3. SNS에 작품을 적극적으로 공유하시더라고요. 네. 제가 작업한 작품들을 페이스북 페이지와 인스타그램에 올리고 있어요. 중학교 때 기발한 인쇄광고를 보고 꿈을 가졌었는데 대학교 2학년 때 일러스트를 배우면서 본격적으로 시작하게 됐고요.    Q4. 작업물 중에서도 가장 애착이 가는 작품이 있을 것 같아요.  사람 혀에 목을 매달고 있는 작품이요. 그 당시 말 때문에 힘들어하는 친구들을 보며 그렸는데 말 한마디에 누군가 죽을 수 있다는 걸 표현하고 싶었어요. 당시에 제 페이스북 페이지에 올려서 좋아요 10만 개를 받았고, 그때를 계기로 본격적으로 작업을 하게 됐죠.    Q5. 사회적 이슈를 다루거나 공익성이 짙은 작업들이 눈에 띄더라고요.  네. 실제로 ‘공공옵티컬’이라는 예술단체에서 공공디자인 프로젝트를 진행하기도 했어요. 횡단보도 바닥에 ‘스마트폰 정지선’을 설치했고 사라져 가는 점자 블록을 되찾기 위한 옥외광고를 기획했죠.    Q6. 작가로서 기억에 남았던 일 한가지가 있다면? ‘부산 여중생 폭행사건’에 대한 일러스트를 그렸었어요. 중학생이 저질렀다기엔 믿을 수 없을 만큼 폭행의 정도가 컸지만, 피의자들에겐 다음엔 그러지 말라며 사건이 종결됐죠. 사람을 죽도록 때려 놓고 ‘참 잘했어요.’ 도장 찍어주듯이. 이 작품을 본 실제 피해자 친구가 청원 용도로 써도 되냐며 연락이 왔고 제 그림과 함께 청원이 올라갔었어요.    Q7. 광고를 기획하면서 계속해서 아이디어를 내는 일이 쉽진 않을 것 같아요. 개인 작업을 해오다 보니 틀에 얽매이지 않고 자유롭게 생각하는 편이에요. 그래서 광고를 기획할 때 상업적인 가이드 라인이 있더라도 그 경계를 넘나들어 튀는 아이디어들을 내고 있죠.    Q8. 앞으로의 계획을 말씀해주세요 제가 원하는 광고 회사에 들어왔으니 광고 기획에 좀 더 집중하고 싶어요. 특히 소셜미디어 콘텐츠를 사람들에게 효과적으로 전달하는 방법들을 연구하고 싶어요. 개인 작업도 시간이 되는대로 짬짬이 그려야죠.   
조회수 1412

단일 TABLE을 SELECT하자!

OverviewDB를 다뤄봤다면 SELECT문도 아실 겁니다. 가장 먼저 접하는 명령어 중에 하나이기도 하죠. 보통은 아래처럼 사용합니다. SELECT문SELECT     * FROM 테이블명  ; 명령을 주면 지정한 테이블에 저장된 모든 내용을 검색합니다. 이번 글에서는 테이블을 만들고 SELECT하는 과정을 다뤄보겠습니다. DB는 MySQL 5.6을 기준으로 하고, Tool은 MySQLWorkbench를 사용하겠습니다.Query, 너란 녀석테이블은 위와 같이 생성할 수 있습니다. 위의 내용은 MySQLWorkbench를 이용해 Model을 표시하면 아래와 같습니다. 구성원의 정보를 저장하도록 했고, 컬럼마다 의미를 갖게 됩니다. MBR_ID (구성원 아이디) : DB에서 구성원을 식별하는 아이디MBR_INDFY_NO (구성원 식별 번호) : 구성원을 실제 구별하는 번호로 과거에는 주민등록번호가 많이 사용되었고, 요즘은 e-mail 이 많이 사용됩니다.MBR_NM (구성원 명) : 구성원의 이름 테스트 데이터를 입력해 실행하면 어떤 결과가 나오는지 보겠습니다.가장 기본적인 SELECT문 실행계획을 보면 아래와 같이 나옵니다.실행 계획은 DB가 어떻게 Query를 수행할 건지 보여줍니다. Query가 복잡해지면 실행 계획을 보면서 Query가 올바르게 작성됐는지 확인하고 필요하다면 Query를 수정해야 합니다. DB를 시작할 때부터 실행 계획을 보는 습관을 기르는 게 중요한 이유입니다. 각 항목에 대한 설명id : SELECT 문에 있는 순차 식별자로 Query 를 구분하는 아이디select_type : SELECT의 유형SIMPLE : Subquery나 union 이 없는 단순한 SELECTtable : 참조되는 테이블의 명칭TB_MBR_BAS : 참조되는 테이블명type : 검색하는 방식ALL : TABLE의 모든 ROW를 스캔 위의 이미지는 임의로 만든 자료를 이용해 Query를 실행한 결과입니다. 실행 계획은 TABLE : TB_MBR_BAS 를 TYPE : ALL 전체 검색한다고 나옵니다. 실행한 내용도 같습니다. 여기서 MBR_NM 이 “나서영”인 자료를 검색해볼까요. WHERE 조건이 들어가자 실행 계획도 내용이 변경되었습니다. rows와 Extra에도 값이 있는데요. 두 항목을 잠시 짚고 넘어가겠습니다. rows : Query를 수행하기 위해 접근해야 하는 열의 수Extra : MySQL 이 Query 를 수행할때의 추가 정보Using where : Query 수행시 TABLE에서 값을 가져와 조건을 필터링 함 위의 결과처럼 전체를 검색해 필요한 자료만 추출하는 것을 FULL TABLE SCAN or FULL SCAN 이라고 합니다. 그러나 FULL SCAN은 성능이 좋지 않기 때문에 우선 꼭 필요한 Query인지 검토해야 합니다. 보통 MBR_NM에 INDEX를 추가해서 해결하는데요. INDEX를 추가해서 같은 Query를 수행하면 실행 계획은 어떻게 달라질까요. 분명 같은 Query였는데 INDEX에 따라 실행 계획이 변경된 걸 알 수 있습니다. INDEX를 추가해도 수행한 결과는 같지만 검색 속도에 많은 차이가 있습니다. 각 항목에 대한 설명type - ref : 인덱스로 자료를 검색하는 것으로 현재는 매칭(=) 자료 검색을 나타냄possible_keys : 현재 조건에 사용가능한 INDEX를 나타냄(인덱스가 N개일 수 있음) IX_MBR_BAS_02 : 현재 조건에 사용 가능한 INDEXkey : Query 수행시 사용될 INDEX (possible_keys 가 N 개일 경우 USE INDEX, FORCE INDEX, IGNORE INDEX 로 원하는 INDEX 로 바꾸어 수행할수 있음)key_len : 수행되는 INDEX 컬럼의 최대 BYTE 수를 나타냄152 : 수행되는 INDEX 컬럼의 BYTE 수가 152ref : INDEX 컬럼과 비교되는 상수 여부 or JOIN 시 선행 컬럼 constant : 상수 조건으로 INDEX 수행rows : 678 : 678 rows 접근하여 값을 찾음Extra : using index condition : INDEX 조건에 대하여 스토리지 엔진이 처리(MySQL의 구성에서 스토리지 엔진과 MySQL 엔진이 통신을 주고 받는데 스토리지 엔진에서 처리 하여 속도가 향상됨) ConclusionINDEX가 없으면 결과가 나오기까지 5초 정도 걸리지만, 반대로 INDEX가 있으면 1초 안에 결과가 나옵니다. 별거 아닌 것 같아 보이지만 실무에서는 엄청난 차이입니다. Query를 작성할 때 실행 계획을 확인하고 조금이라도 빨리 결과가 나올 수 있도록 하는 것이 중요하기 때문이죠. 다음 글에서는 단일 TABLE 을 SELECT하는 것을 주제로 이야기를 나눠보겠습니다. 무사히 SELECT하길 바라며.글한석종 부장 | R&D 데이터팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 776

미루는 것과 "하나 둘 셋, 시작!"의 간격

객관적인 입장에서 나 스스로에 대해 가장 마음에 안드는 구석을 꼽자면 "미루는 특성"이다.사업을 하지 않았으면 몰랐을 문제점이기에 "미루는 것"에 대해 내가 배운 것에 대한 단상을 전해본다.마감 기한 직전까지 미뤄버리기~!우리는 마감일이 있는 과제를 받아 마감일 전에 일을 끝내는 것에 익숙하다.알림장에 적혀 있는 일기쓰기부터 대학 과제가 그와 같았다.마감일이 있는 과제를 받으면 우리는 나름대로 계획을 세워본다.마감 기한이 다가옴에 따라 점차 일의 양을 늘려가 기한 안에 과제를 완성하는 계획을 생각할 것이다. 상상속 과제 처리 계획한편 토르같은(벼락치고 망치는) 나란 존재는 어김없이 일을 이따위로 처리하기 일쑤다.나레기의 과제 처리내 머릿속의 아몰랑 원숭이팀 어번의 '할일을 미루는 사람들의 심리'라는 TED강연은 이와 같은 미루는 특성에 대해 정말 재미있게 풀어낸다.팀 어번에 따르면 일반인의 뇌는 합리적 결정권자가 선택을 주도한다.나와 같은 미루기 대마왕의 뇌 속에는 합리적 결정권자 바로 옆에 아몰랑 원숭이가 존재한다.아몰랑 원숭이는 '그냥 좀만 더 자기', '일단 모르는 걸로 하기', '나와 내가 공모하여 30분만 더 쉬는 것으로 하기', '왠지 내일부터 하는게 더 좋겠다고 생각하기' 등의 결과물을 만들어내는 존재다.틈만나면 합리적 결정권자의 지휘대에 올라 미쳐 날뛴다고 팀 어번은 묘사한다.합리적 결정권자가 생산적인 일을 하기 위해 합리적인 계획을 짜면 아몰랑 원숭이가 난입해 '음...아니!'로 일축하고 그럼 어느새 미루기 대마왕은 페이스북을 놓지 못하고 침대를 떠나지 못하는 상태가 된다.근데 이 강연에서 정말 재밌었던 건 다음과 같은 전제였다.마감 기한이 다가오면 아몰랑 원숭이를 무찌르는 존재가 나타나는데, 일명 패닉 몬스터다.패닉 몬스터는 보통의 상황에서는 자고 있다가 큰 망신을 당하거나, 경력에 재앙이 닥치는 등의 무시 무시한 상황이 펼쳐질 위험이 있는 경우에 돌연 깨어난다. 패닉 몬스터는 아몰랑 원숭이를 잠시 쫒아내고, 이 때 다시 합리적 결정권자가 지휘를 탈환하여 정신없이 일을 처리한다.아무도 시키지 않은 일을 미루면 우울해진다한편, 삶에서는 마감 기한이 없는 정말 중요한 일 -친구 관계를 관리하고, 새로운 것을 배우고, 운동을 시작하거나 하루에 한 번 가족들의 안부를 묻는 것과 같은- 들도 있다.문제는 이 상황이다.이 때는 패닉 몬스터가 깨어나지 않는다.우리는 무기한으로 어떠한 목표를 향해 움직이는 것을 유보할 수 있다.그런 상황에서 우리는 무기력함, 자기 혐오감, 자존감 및 자기 신뢰도 하락을 경험한다.기한이 없는 일에 대해해서는 패닉 몬스터가 나타나 아몰랑 원숭이를 처리해 주지도 않으며 나와의 약속에 대한 반복적 파기는 시간이 흐를 수록 움직일 힘을 뺏어간다. 스타트업을 한다는 건 아무도 시키지 않은 일을 한다는 것이다.특히나 철저한 체계가 잡혀져 있지 않은 초반엔 더욱 그러하며 잠시 운이 좋아 순항을 하게 되는 경우에도 그러하다.난 지독한 프로미루머였다.공동창업자는 그 때의 나를 기억하리라. 아몰랑 원숭이 그 자체가 나였다.하지만 나는 날 뜯어 고치는 걸 어떻게 하면 되는 지 알고 있었고, 무엇보다 지금 뜯으면 고쳐진다는 걸 알고 있었다.프로 미루머에서 때때로 카르페디에머가 된 나의 비법 몇가지를 공개하며 방향 없이 날아가는 내 단상을 마무리 하고자 한다.나름의 극복법1. 미루는 상황 자체를 상상하지 않는다. 일단 안락한 상황을 상상해버리면 그 선택을 할 가능성이 너무 높아진다. 미루는게 쉽고 재밌으니까. 내일의 내가 해줄거니까. 상상하는 순간 이미 아몰랑 원숭이는 나라는 배의 금일 항해의 키를 쥐게된다. (ㅈ된단 이야기)2. 그냥 미루는 마음을 그냥 이루는 마음으로 바꾼다. 이 방법엔 엄청난 주문이 있다. "하나 둘 셋, 시작" 이 바로 그것이다. 예컨데 알람을 듣고도 침대에 누워 '으음~으으으~ 아...음...어...좀만더 ㄴr...레..ㄱl...는 더잘랭 아몰랑" 의 순간에 그냥 '하나 둘 셋, 시작!'을 외치며 일어나버리는 것이다. 또는 '아 도대체 이걸 언제하지...망했네...근데 왠지 잠시 넷플릭스좀 볼까?'하는 순간에 그냥 일단 노트북을 켜고 워드파일을 켜버리는 것이다.엄청난 주문이다. 일단 뱉으면 머쓱해서라도 움직이게 된다.3. 이뤄본 경험을 미뤄본 경험보다 더 누적하여 '결국 하게되는 나'로 만든다. 누적 데이터 상 미뤄본 경험이 더 많다면 첫 째, 미뤄도 존심이 상하지 않으며 둘째, 스스로를 '결국 미룰것 같은 나'로 인지하게 된다. 반면, 이뤄본 경험이 더 많다면 첫째, 미루는 게 존심 상하고 짜증나며 둘째 스스로를 '하게되는 나'라고 긍정하게 된다.이상!아직 멀었지만 꽤 좋아졌다. 더 완벽한 프로가 되겠다.
조회수 987

[퍼포몸쓰 일상] #0 어쩌다 슬라운드

다니던 회사를 그만두고 한 달 정도 방황했다. 친구들의 동업 제안, 머릿속을 맴도는 사업 아이디어, 이런저런 스카우트 제의. 무엇하나 쉬운 게 없다고 생각했다. 핑계처럼 로켓펀치를 켜고 뻑뻑한 눈알 위를 겉도는 채용공고를 훑었다.딱 하나, 홈에 덜컥 걸리는 느낌이 들었다. 저녁 8시 40분. 입사 지원하고 두 시간이 채 지나지 않아 전화가 걸려왔다. 당황과 반가움 중간 어딘가의 감정을 안고 통화했던 기억이 난다. 그들이 찾던 포지션이 신기할 정도로 나와 맞아떨어진다는 데서 오는 반가움. 굉장히 빠른 액션에서 오는 당황. 전화받고 이틀 후 오전 11시로 인터뷰 약속이 잡혔는데 재밌는 건 그 날이 일요일이었다는 거다. 많은 인터뷰를 봤지만(인터뷰어로서, 인터뷰이로서) 주말 오전 인터뷰는 처음이었다. 좋고 나쁨을 떠나서 나에겐 이례적인 일이라 가벼운 마음으로 갈지, 진중한 마음으로 갈지 갈팡질팡했다.일요일 오전 11시.매트리스 업계의 적폐를 바꾸고 싶다던 두 남자와 만나 가장 먼저 한 이야기는 폴리에스테르 빨대에 관해서였다. 거북이의 콧구멍에서 빨대를 뽑아내는 영상 속 거북이가 얼마나 쾌감에 젖은 표정을 지었는지가 우리의 첫 이야기 소재였다. 아무도 어색해하지 않고 첫 만남에서 그런 이야기부터 시작했다는 게 지금도 조금 어처구니가 없지만 우리는 꽤 진지하게 이야기를 이어나갔다.다른 건 모르겠고, 제품 하나는 잘 만들고 싶었다던 그들은 고맙게도 내가 개인적으로 끄적이던 콘텐츠들을 너무나 마음에 들어했다. 반대로 난 짧은 대화에서도 묻어 나오는 그들의 제품에 대한 자부심과 전문성, 열의가 좋았다. 난 내 길지 않은 커리어의 대부분인 4년 반 정도를 스타트업에서 보냈다. 그래서 초기 스타트업이 멤버의 유능함과는 별개로 얼마나 고단한 길을 걷는지 잘 알고 있다.'그동안 쉼 없이 고생했으니 이번엔 좀 편하게 일하자''일단 돈 많이 주는 곳으로 가자''이름이 알려진 곳으로 가자'이직 고민을 하면서 머릿속을 가득 메웠던 생각들은 결국 사람 앞에 스러졌다. 초기 스타트업에서 굴러다녔던 경험만큼, 능력 있고 좋은 사람들과 같이 일하는 즐거움을 알기에 두 명의 founder와 이야기하면서 다시 한번 가족을 생각하지 않는 이기적인 결정을 내렸다(부모님, 장모님, 마누라 죄송합니다).一切有爲法 如夢幻泡影 如露亦如電 應作如是觀스타트업을 하면서 가장 많이 떠올리는 문구다(참고로 교회 다닌다). 현실의 꿈이 비록 손에 잡히지 않더라도 꿈을 빚기 위해 그렇게 난 슬라운드에 콘텐츠 마케터로 합류했다.  

기업문화 엿볼 때, 더팀스

로그인

/