스토리 홈

인터뷰

피드

뉴스

조회수 2255

리디북스 서버 스택 소개

2대의 서버로 시작한 리디북스는 각 기능의 요구사항에 최적인 솔루션들을 채용하고, 고가용성(High Availability)을 지향하면서 매우 복잡하고 다양한 구성으로 변모해왔습니다. 이 글에서는 리디북스가 어떤 스택에서 서비스를 제공하고 있는지 간략히 소개하려고 합니다. 각 스택의 선택 이유나 문제에 부딪히며 배운 노하우 등은 차차 포스팅하겠습니다.대략적인 구조리디북스 백엔드 구조도로드 밸런싱로드 밸런싱은 소프트웨어 로드 밸런서인 HAProxy를 이용하고 있습니다. HAProxy는 L4, L7 스위치의 기능 및 로드 밸런싱을 제공하고 구성 역시 매우 간편합니다. 리디북스는 고가용성을 위해 Active - StandBy 서버 한 쌍이 가상 IP를 공유하고, keepalived를 통해 서로의 상태를 확인하며 자동 failover 됩니다. 각 서버군이 사용하는 네트워크 트래픽에 따라 스위치와 연결되어 있는 네트워크의 속도가 다른데, 이를 효율적으로 사용하기 위해 HAProxy 서버 쌍을 2개 구성하여 DNS를 통해 HAProxy로 들어오는 트래픽도 분산하는 방식으로 네트워크 효율화를 이루었습니다.웹 서버Ubuntu 14.04 LTS 기반에 웹서버로는 Apache, Nginx를 사용하고 있습니다. 서점 용 웹 서버, 정적 파일 서버(CSS, JS 등), 통계용 서버, 책 파일에 DRM을 씌워 전송하는 다운로드 서버 등 여러 개의 웹 서버 그룹을 나누어 관리하는데, 각 서버가 하는 역할이나 테스트를 통해 확인한 병목 지점을 고려해 웹서버를 채택합니다.API 서버리디북스는 서점이나 앱에서 이용하는 수많은 API가 존재하는데 종류에 따라서는 초당 수만 개의 호출이 발생하는 경우도 있습니다. 이러한 트래픽을 감당하기 위해 비동기 처리가 필요한 경우 Node.js를 주로 이용하여 구현하고 있습니다. Node.js 프로세스는 PM2를 통해 클러스터 모드로 실행되어 요청을 처리합니다. 클러스터 모드는 프로세스에 대한 로드 밸런싱을 지원하며 프로세스를 순차적으로 재시작할 수 있어 무정지로 서비스를 재시작할 수 있습니다데이터베이스서비스 초기에 MySQL을 사용했고 현재는 MariaDB로 변경한 상태입니다. 한때 DB가 SPOF(Single Point Of Failure)였던 시기를 겪으면서 read/write의 분산을 위해 많은 노력을 들였습니다. 리디북스에서 실행하는 대부분의 데이터 연산은 읽기 동작이므로 애플리케이션 레벨에서 읽기/쓰기 접근을 구분하여 1차적으로 부하를 분산하고, HAProxy를 통해 여러 대의 slave로 분배해 2차적으로 부하를 분산합니다. 쓰기 동작이 빈번하거나 데이터 성격상 NoSQL이 필요한 경우 Couchbase와 Redis를 적극적으로 사용하고 있으며, MariaDB 상에서도 쓰기 동작의 분산 필요성이 대두됨에 따라 상반기에 샤딩을 준비하고 있습니다. 사용자 행동, 트랜잭션 로그 등 하루에도 방대한 양이 쏟아지는 데이터의 경우 Azure 내에 구성한 Hadoop 클러스터에 보관하며, Hive 저장소를 BI(Business Intelligence) 시스템 기반으로 활용하고 있습니다.파일 시스템리디북스에서 다루는 책 파일은 매우 방대하고 중요한 데이터입니다. 어떠한 일이 있어도 데이터 유실이 발생해서는 안되며, 일부 하드웨어 혹은 노드에 장애가 발생하더라도 서비스 장애 없이 파일을 서빙할 수 있어야 합니다. 저희는 GlusterFS로 6대의 노드를 클러스터를 구성하고 이를 파일 접근이 필요한 서버에서 NFS-like 형태로 마운트하여 사용하고 있습니다. 동일 데이터는 여러 노드(3 replica)에 분산 저장되며, 각 노드에도 RAID 구성을 하여 빠른 장애 대응 및 데이터 유실 방지에 노력하고 있습니다.검색리디북스의 책/저자 검색 등은 ElasticSearch를 통해 이루어집니다. 형태소 분석기는 오픈소스인 은전한닢에 따로 정의한 dictionary를 조합해 사용하고 있고, 2대의 노드로 클러스터가 구성되어 있습니다. 추가/변경되는 도서 정보는 증분 색인을 통해 실시간으로 검색 서버에 반영됩니다.작업큐이메일 발송, PUSH 발송 등의 작업들은 웹 애플리케이션이 직접 실행할 경우 페이지 응답속도를 떨어뜨리고, 진행상황 파악이나 실패 시 재시도하는 등의 실행 관리가 어렵습니다. 이런 문제를 해결하기 위해 Beanstalk라는 Work Queue에 작업을 일단 쌓아두고, 여러 대의 서버에서 실행되고 있는 컨슈머들이 작업을 가져와 순차적으로 진행하는 형태로 구성되어 있습니다.모니터링장애 발생 포인트와 시점을 예측할 수 없는 만큼 장애 발생의 빠른 인지를 위해 모니터링은 매우 중요합니다. 리디북스는 99.999%의 고가용성(High Availability)을 목표로, 버그와 장애 없는 안전한 운영을 위해 아래와 같이 다양한 오픈소스 및 유료 솔루션을 도입하여 활용하고 있습니다.30+ 이상의 서버 리소스를 모니터링하기 위한 Munin(On-Premise) 및 NewRelic(SaaS)서버에서 발생하는 각종 오류와 예외를 모니터링하기 위한 Sentry로그인, 결제 등 서점의 핵심적인 기능의 정상 여부를 모니터링하는 Pingdom각종 배치작업과 주기적으로 실행되는 스크립트를 모니터링하기 위한 PushMonNode.js 프로세스나 Redis 상태 모니터링을 위한 Keymetrics(SaaS)데이터의 무결성을 주기적으로 감지하는 각종 In-house 스크립트#리디북스 #서버 #서버개발 #스택 #백엔드 #node.js #개발자 #개발언어 #스킬스택 #소개
조회수 1528

코인원 마이페이지가 더욱 더 새로워졌습니다 :) - 유저플로우셀 팀터뷰

웹서비스에서 나만의 서비스 이용내역과 개인정보를 확인할 수 있는 공간을 ‘마이페이지'라고 하죠. 유저들은 마이페이지를 통해 나의 상태를 체크하며 해당 서비스에 좀 더 애착을 갖기도 합니다. 이번에 코인원 마이페이지도 더욱 더 새로워지면서 애정이 가득해졌다는 유저들의 제보가 속속 들어오고 있어요!오늘은 코인원 마이페이지를 새롭게 탄생시킨 유저플로우셀 예은님, 정유님, 현진님, 종헌님과 함께 마이페이지의 모든 것을 파헤쳐보겠습니다.코인원 유저플로우셀은 트레이딩 영역을 제외한 전반적인 서비스 영역을 담당하고 있습니다. 각 서비스에 대한 유저 경로 동선을 만들고 서비스를 제공하며, 누구나 거래를 하고 싶은 코인원을 만들고 있답니다. :-)Q. 안녕하세요, 유저플로우셀 여러분. 자기소개와 함께 현재 업무를 소개해주세요!예은 : 유저플로우셀에서 서비스 기획자로 일하고 있는 지예은입니다. 저는 코인원 유저들이 겪는 문제상황과 UX트렌드 분석을 통해 기존의 서비스를 개선하고 고도화하고 있어요.정유 : 프로덕트 디자이너로 일하고 있는 이정유입니다. 유저플로우셀은 유저와 거래소를 이어주는 모든 페이지를 담당하고 있어요. 저는 기획자들과 함께 유저의 니즈를 페이지에 UI(User Interface)적으로 어떻게 반영할지 고민하고, 이를 디자인 시스템에 녹여 시각적 일관성을 전달합니다.  현진 : 프론트엔드 개발자로 불철주야 개발 중인 박현진입니다. 프론트엔드는 한마디로 코인원 프로덕트에서 실제로 유저들에게 보여지는 웹화면이에요. 저는 유저들에게 보이는 영역을 책임지며 프로그래밍하고 있습니다.종헌 : 웹 API를 담당하고 있는 백엔드 개발자 김종헌입니다. 프론트엔드가 유저에게 보이는 영역을 담당한다면, 저는 보이지 않는 곳인 백엔드에서 입출금 서비스, 거래기록, 개인정보 등 코인원의 다양한 서비스와 유저를 연결하고 있어요.Q. 이번에 마이페이지 개선이 대대적으로 진행되었습니다. 어떤 계기와 방향성으로 개선하게 되었나요?예은 :  마이페이지 개선은 유저의 고충을 파악하기 위한 코인원 고객센터 인터뷰에서 시작되었습니다. 거래소 이용에 필요한 인증, 계정 보안에 대한 관리가 익숙하지 않은 유저들의 ‘페인 포인트(Pain Point)’를 발견했거든요. 서비스 기획자, 디자이너, 개발자가 함께 모여 UI나 정보로 사용자에게 도움을 주고 CS비용을 최소화 할 수 있는 방법을 고민하기 시작했습니다. 우선, ‘마이페이지'는 코인원 서비스를 이용하는 유저 개개인을 챙겨주는 공간이라고 생각합니다. 이번 개선 과정에서 가장 중점을 둔 부분도 ‘고객을 챙겨주는 마이페이지' 경험을 전달하는 것이었어요. 이렇게 설정된 방향성에 따라 유저들의 상태별로 필요한 상황을 안내하도록 구성했습니다. 한마디로 ‘유저 맞춤형 마이페이지’로 진화한겁니다!▲ 더욱 더 새로워진 코인원 마이페이지정유 : 이전의 마이페이지는 엉켜있는 플로우로 인해, 유저가 어떤 상태인지, 어떤 인증과정을 거치고 있는지 인지하기가 힘들었습니다. 목적을 달성하기 위해 마이페이지에 접속했지만 목적 달성을 끝마칠 수 없었죠. 먼저 흩어져 있는 기능, 정보, 구조들을 그룹핑하며 플로우를 개선하는 작업을 시작했어요. 아이데이션 과정을 거치면서 마이페이지를 ‘내 서랍, 내 방' 등 나만의 정체성을 확인할 수 있는 키워드로 정의했습니다. 그리고 키워드를 확장시켜 ‘나의 데이터'를 한 눈에 관리할 수 있는 대시보드 형태의 디자인을 지향하게 되었어요. 결과적으로 현재 마이페이지에는 나의활동, 개인정보관리, 인증단계 총 3 개의 탭으로 위계를 설정했습니다. :D▲ 코인원 거래소 인증단계가 훨씬 간편해졌습니다!Q. 기술적으로는 어떤 변화가 있었을까요?현진 : 마이페이지를 포함해서 코인원 웹 프로덕트에 기술부채(Technical Debt)가 조금씩 쌓여 있었어요. 이 부분을 덜어내기 위해 마이페이지를 개선하면서 ‘기획/디자인/개발’ 삼박자로 변화를 주는 리빌딩(Re-building)을 진행했습니다. 덕분에 기술적으로 관리 포인트가 많이 줄었어요. 이제는 웹 유지/보수가 좀 더 용이하게 되었답니다.종헌 : 그 동안 코인원 웹은 하나의 비대한 서비스로 운영되었습니다. 하나의 서비스가 덩치가 점점 커지다 보니 개발자가 서비스 로직을 온전히 이해하기 어려웠어요. 웹을 유지하고 보수하거나, 새로운 기능을 추가하는 것도 쉽지 않았습니다. 그래서 하나의 비대한 서비스를 여러 개의 작은 서비스로 나누는 작업인 리빌딩을 진행했어요. 여러 작은 서비스로 분리하고 책임 영역을 나누면서 서비스 로직에 대해 제대로 이해하고 체계적으로 코드를 작성할 수 있게 되었습니다.    Q. 마이페이지 개선 전과 후, 달라진 점을 말씀해주세요.예은 : 코인원 마이페이지는 이전보다 유저들에게 더욱 친근하게 다가가고 있습니다. 마이페이지의 콘텐츠가 유저의 상태에 맞춰 변화하며, 유저마다 다음 인증 과정이나 활동 내역을 다르게 안내합니다. 유저가 기능을 먼저 찾지 않아도, 마이페이지가 길을 찾아주는 가이드의 형태를 띄고 있어요.또한 인증단계 별로 수수료나 회원등급이 달라지는데, 유저들이 하나하나 가이드를 보며 찾아볼 수는 없다고 생각해요. 한눈에 자신의 상태를 파악할 수 있도록 UI를 활용하는 것이 중요한 부분이죠. 마이페이지의 개선된 UI로 유저가 코인원의 서비스 정책을 한층 더 깊게 이해하는데 도움이 되었으면 해요.정유 : 유저가 코인원 프로덕트와의 관계성을 인지할 수 있는 디테일들이 추가되었습니다. 가장 대표적인 예시로는 ‘코인원과 함께한 지 000일째 입니다.’라는 문구가 있겠네요. 코인원 유저들에게 ‘챙겨준다'라는 느낌을 전달하기 위해 정말 많은 회의와 아이데이션을 거쳤습니다. 그 과정 중 나왔던 아이디어인데 이번에 반영하게 되었어요. ‘제품’보다는 ‘서비스'로서 느껴질 수 있도록, 대화하는 느낌을 잘 살려주는 포인트이기에 매우 뿌듯했죠.▲ 심...심쿵....!!!!!현진 : 개발자 입장에서 바라봤을 때, 페이지 애니메이션이 가장 좋았어요. 페이지 애니메이션은 웹페이지가 다른 웹페이지로 이동할 때 발생하는 애니메이션을 말합니다. ‘툭' 하고 넘어가는 것이 아니라 ‘sha~(?)’ 하게 넘어가는 느낌이라고 할까요. 페이지와 페이지 사이가 하나의 관계성을 가지고 넘어가게 됩니다. 유저들은 ‘암호화폐 거래소에서 마이페이지에 이런 디테일한 부분까지 신경쓰고 있구나’를 느낄 수 있을거에요. 또한 에러메시지, 경고메시지와 같은 피드백 인터랙션도 정교해졌어요. 사용자와 교감할 수 있는 쪽에 코인원만의 감성이 잘 버무려졌습니다.종헌 : 이전의 코인원 프로필 서비스는 사용빈도가 높지는 않았어요. 그라바타(Gravatar)라는 외부서비스를 사용했는데, 이것을 사용하지 않는 유저들에게 친숙하지 않았거든요. 이제는 코인원에서 프로필 이미지를 정해두고 원하는 이미지로 클릭해서 쉽게 변경할 수 있게 설정했어요. 참고로 프로필 이미지를 설정하는 것이 보안측면에서도 좋습니다. 예를 들어, 은행에서는 프로필 이미지를 설정하면 바로 내가 사용하는 계정인지 아닌지를 알 수 있어요. 코인원에서도 프로필 이미지를 설정하면 내가 가입한 계정인지 아닌지 식별이 가능합니다.▲ 프로필 사진 설정 기능도 많이 이용해주세요 :)Q. 마이페이지의 개선 작업 과정에서 많은 고민이 있으셨을 것 같아요. 가장 중점적으로 생각했던 부분이 있었나요?정유 : 가장 중점이 되었던 부분은 서비스를 이용하는 유저 개개인의 상태를 반영하는거였어요. 유저별로 동일한 정보가 아닌 맞춤형 정보를 제공하기 때문에 한 페이지 안에 들어가는 정보의 위계가 상태값에 따라 계속 변하는 것이 관건이었습니다. 예를 들어, 마이페이지에는 나의 정보를 업데이트하기 위한 많은 버튼들이 들어갑니다. 그럼 유저 케이스별로 중요한 정보를 바꿔보면서 어떤 버튼이 가장 위계가 높은지 고민하고 계산해요. 이러한 과정을 거치면서 유저의 상태값을 쉽게 알려주고 변경할 수 있는 디자인이 완성되었습니다. 예은 : 기존부터 유저 인터뷰를 진행하며 ①신규 유저 ②타사 이용 유저 ③거래소 이용에 문제를 겪고 있는 유저 ④코인원을 오래 이용해준 고마운 유저 케이스까지 다양한 상황에 놓여있는 유저들에게 만족스러운 UX 경험을 드리기 위해 고민해왔습니다. 특히 운영지원셀과 코인원 고객센터 CS로 인입되는 주요 인터뷰들을 중점적으로 수집하여 인증과정에 문제가 되는 것들을 모아서 개선회의를 해왔어요. 이외에 마케팅, 프로덕트쪽도 함께 서비스를 제공하는 공급자 입장에서의 니즈도 취합해 마이페이지에 반영할 수 있었습니다.▲ (절대 설정샷 아니에요) 훈훈하게 회의중인 유저플로우셀!Q. 혹시 개선된 마이페이지를 이용한 코인원 고객들의 후기도 있었나요?예은 : 개선된 마이페이지로 바뀐 지 얼마되지 않아, 유저의 피드백을 직접적으로 접하지는 못했어요. 대신 정량적인 부분에서 여러 수치들이 올라간 것을 확인할 수 있었습니다. 대략적으로 재방문자의 UV(Unique Visitor)수가 개선 전과 대비해서 약 70%정도 크게 증가했습니다. 이전에는 회원가입을 끝마치고 인증과정 중에 페이지를 이탈한 유저도 보였지만, 개선된 후에는 마이페이지 탭 이용빈도가 크게 올라갔습니다. 마이페이지가 좀 더 원활한 거래소 서비스 이용을 위한 가이드 역할을 해주길 기대하면서, 지속적으로 니즈를 관찰하고 개선해 나갈 예정입니다.Q. 마이페이지 이외에도 기억에 남는 유저플로우셀의 프로젝트가 있나요?예은 : 코인원의 수익현황을 한 눈에 볼 수 있는 자산탭이 기억에 남아요. 그 동안 코인원 유저들이 수익률을 확인할 수 있는 기능을 많이 요청했었는데, 팀원들이 함께 고민하여 새로 개편한 기능이라서 그 의미가 컸어요.정유 : 저는 실질적으로 프로젝트에 돌입하기 전에 진행했던 코인원 유저 인터뷰가 가장 기억에 남아요. 인터뷰 내용이 개선점으로 가득찰 줄 알았는데, 응원의 목소리를 전달해주셨거든요. 더 열심히 UI 디자인을 해야겠다는 의욕을 불타오르게 해주었어요!현진 : 코인원 웹프로덕트를 사용하시는 분들이 눈치 채셨는지 모르겠지만, 마이페이지 이전부터 진행해왔던 리빌딩 프로젝트(랜딩, 거래소, 프로차트, 코인원 톡 등)들이 기억에 남아요. 사실 마이페이지 이전 리빌딩 프로젝트들은 기술적으로만 접근하다보니 우여곡절이 많았어요. 그래도 마이페이지 리빌딩은 업무적으로도 많이 배우고, 기술 뿐만 아니라 전체적으로 변화한 것이 보여 저 또한 성장하는 시간이었습니다.종헌 : 이외에도 유저플로우셀은 UX개선을 여러 프로젝트와 함께 진행하고 있습니다. 정신없긴 하지만 개발요소도 새롭고 다이나믹한 것이 많아서 즐겁게 하고 있습니다!▲ 화기애애하게 UI 시안을 보고 있는(?) 유저플로우셀Q. 코인원에서 디자이너 그리고 개발자로 일하는 큰 장점은 무엇인가요?예은 : 코인원에선 셀마다 다른 직무의 인원들이 빠르게 소통하여 의사결정하는 목적조직 형태로 일합니다. 중간중간 기획리뷰, 디자인리뷰 과정을 거치면서 더 꼼꼼하게 일하고, 다른 직무에 계신 분들의 작업도 공유하고 있어요. 거래소에서 일어날 수 있는 다양한 문제 상황을 긴밀하게 대응하고 있죠.정유 : 현재 코인원은 ‘셀(Cell)’이라는 목적조직 형태입니다. PM, 개발자, 디자이너가 한 조직에 속하다보니 Output 나오는 속도가 매우 좋아졌습니다. 또한 여러 직군이 함께 팀웍을 맞추다보니 서비스를 다양한 각도에서 바라볼 수 있고, 이는 디자이너로서 서비스 이해도를 높이는데 굉장히 좋은 환경이라고 생각해요.  종헌 : 코인원은 개발자도 기획 단계부터 적극적으로 참여하여 프로젝트를 설계하고 있습니다. 이로 인해 개발을 하다 예기치 않은 변수가 생기는 일이 거의 발생하지 않아요. 또한 정기적으로 회고를 하며 프로세스의 문제점을 도출해내고, 개선을 위해 다양한 시도를 해볼 수 있다는 것도 장점입니다. 현진 : 현재 코인원 기술본부는 트렌디한 기술을 곳곳에 사용하고 있어요. 기술에 민감하게 반응하는 프론트엔드 개발자분이 코인원에 온다면 기술적으로 매우 만족하실거에요. Q. 앞으로 이루고 싶은 목표는 무엇인가요?예은 : 암호화폐 거래소는 UX를 기획하기에 매우 도전적인 분야입니다. 블록체인 기술이 곳곳에서 화제가 되고 있지만, 아직 업계의 워딩이나 사용에서의 유저 친화적 성숙도가 높지 않은것 같아요. 앞으로의 목표는 누구나 쉽게 거래할 수 있는 암호화폐 거래소를 만드는 것입니다. 점점 더 발전하는 코인원의 모습을 많이 기대해주세요!정유 : 코인원 UI에는 아직 블록체인 공급자적 시선이 많이 담겨있어요. 예를들어, 개발자가 아니면 이해하기 어려운 용어나 UI가 남아있는 부분이 있거든요. 이를 디자인적으로 해소하고 싶습니다. 유저가 갖고 있는 암호화폐 거래 장벽을 낮추고, 코인원의 가치가 잘 반영된 프로덕트를 만드는 것이 목표에요. 종헌 : 코인원에서는 트레이딩 이외에도 여러가지 서비스들을 유저에게 제공하는 다양한 시도를 하고 있어요. 저는 다양한 서비스들을 연결하면서 서비스의 안전장치를 견고하게 쌓아올리고 싶네요. 장애 발생에도 끄떡없는 안정적인 코인원을 유저에게 선보이고 싶습니다.현진 : 대한민국에서 적어도 사용성 1위 암호화폐 거래소를 만들거에요. 유저플로우셀에서 마이페이지 이후에도 많은 프로젝트를 준비하고 있거든요. 매주(?) UX가 점차적으로 개선되는 코인원 거래소의 모습을 확인할 수 있을 거에요. 마지막으로 꼭 하고싶은 말이 있는데, 코인원에 많은 개발자분들이 지원해주셨으면 좋겠어요. 아직 업계에 부정적인 인식이 강하지만, 블록체인이 발전하는 과정을 보며 점차 해소될거라고 믿어요. 기술적으로 발전할 가능성이 무궁무진한 곳이니 기술적인 욕심을 채우고 싶은 분들, 함께 성장하고 싶으신 분들 코인원으로 오세요!▲ 코인원 유저플로우셀 많이 기대해주세요!무엇보다도 긍정적인 에너지로 가득찼던 유저플로우셀의 인터뷰를 들어봤어요.코인원 마이페이지에 큰 변화를 가져온 활기찬 에너지, 다들 느끼셨나요?마이페이지 이후에도 다양한 프로젝트를 준비하고 있는 유저플로우셀. 곧 코인원 웹 거래소를 사용하면서 UX적으로 편리한 사용성을 경험할 수 있을겁니다.끝으로, 특별한 문화를 경험할 기회! 코인원 채용에 함께하는 것도 잊지 마세요 :-)
조회수 2750

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

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

Tips for building fast portrait segmentation network with TensorFlow Lite

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

타다 시스템 아키텍처 - VCNC Engineering Blog

2018년에는 VCNC에 큰 변화가 있었습니다. 오랫동안 비트윈 기반의 서비스들을 개발하고 운영했지만 2018년 10월에 기사 포함 렌터카 서비스를 포함한 종합 모빌리티 플랫폼인 타다를 기획하고 출시하였습니다. 변화가 많은 모빌리티 시장에서 신규 서비스를 성공적으로 출시하기 위해 많은 고민을 하였습니다. 이번 글에서는 타다의 시스템 구성과 이를 위해 사용한 여러 기술을 소개하면서, 타다 개발팀의 기술적 결정을 공유해보고자 합니다.타다에서 사용하는 기술들의 로고. 왼쪽부터 Kotlin, Spring Boot, Kubernetes, Terraform, gRPC, Redis.기존과 다른 선택비트윈의 경우 Netty를 이용해 인하우스 네트워크 라이브러리를 만들기도 하였고, 메인 데이터베이스로 NoSQL인 HBase를 사용하는 등 남들이 통상적으로 사용하지 않는 기술 스택을 선택한 경우가 많았습니다. 그 배경에는 나름대로 이유가 있었지만, 서비스 초기에는 안정성에 어려움을 겪기도 하였고 서버 배포 과정이 느리고 복잡하여 쉬운 길은 아니었습니다. 여러 문제를 해결하기 위해 Haeinsa 등 라이브러리와 소프트웨어를 직접 만들기도 하였습니다.타다는 이슈가 많은 모빌리티 시장을 타겟으로 하고 있기 때문에 Time to Market이 특히 중요했습니다. 개발하는 기간 동안 시장 상황에 따라 기능의 우선순위가 변하기도 하였습니다. 이에 따라 서비스를 빨리 출시하고 외부의 변화에 유연하게 대처할 수 있도록, 완성도 있게 만들어져 있는 프레임워크나 라이브러리를 선택하였고, AWS에서 이미 잘 관리되고 있는 서비스를 적극적으로 활용하였습니다.사용 중인 기술들Kotlin: Java는 불편한 점이 많지만, JVM에 대한 경험을 무시할 수는 없어 비교적 새로운 JVM 기반 언어인 Kotlin을 사용하기로 하였습니다. 다른 여러 JVM 기반의 대안 언어들이 있지만, Spring Boot에 쉽게 적용할 수 있고 커뮤니티에서 적극적으로 권장하고 있는 점 등 여러 이유로 Kotlin을 선택하게 되었습니다.Spring Boot: 널리 쓰는 웹 프레임워크이며 이미 지원하는 기능 또한 많기 때문에 보일러 플레이트 코드 작성을 줄이고 서비스 개발에 집중할 수 있습니다. SQS 메시지 처리, HTTP 요청 및 응답으로 Protocol Buffers 메시지 사용 등 프레임워크에서 제공하는 기능을 많이 활용하고 있습니다.Kubernetes: 컨테이너 오케스트레이션 플랫폼으로 배포 자동화와 스케일링 등 여러 가지 운영적인 편의성을 제공합니다. 처음에는 kops를 이용해 클러스터를 직접 띄웠지만, 지금은 EKS를 이용하고 있으며 직접 object를 만들기보다 helm을 이용하고 있습니다.gRPC: 실시간성이 중요한 차량 위치나 운행 상태 변화 등은 Streaming을 이용하여 전달하고 있습니다. 직접 개발할 수도 있었지만, 서비스 개발에 집중하고 앞으로의 관리 오버헤드를 줄이기 위해 gRPC를 이용하기로 하였습니다.Redis: 서버 간 메시징을 위해 Redis의 Pub/Sub 기능을 사용하고 있습니다. 메시지 브로커 기능을 제공하는 RabbitMQ, ActiveMQ, Kafka 등 여러 옵션이 있었지만, 개발을 시작하던 당시에는 Redis만이 ElastiCache를 이용하여 쉽게 띄우고 관리할 수 있어 Redis를 선택하게 되었습니다.Protocol Buffers: gRPC 뿐만 아니라 HTTP/2로 주고받는 메시지를 정의할 때도 이용하고 있습니다. 덕분에 따로 문서화 하지 않고 proto파일을 공유하여 더욱 명확하고 편리하게 API 명세를 공유할 수 있었습니다.Terraform: HCL을 이용해 인프라스트럭처 프로비저닝 및 관리를 편하게 해주는 도구입니다. AWS 서비스의 생성 및 관리를 콘솔에서 직접 하지 않고 Terraform을 이용하고 있습니다.사용 중인 AWS 서비스들AWS는 개발팀이 오랜 기간 사용하여 가장 익숙한 클라우드 플랫폼이기 때문에 큰 고민 없이 선택할 수 있었습니다.EKS: Kubernetes 클러스터의 마스터 노드들을 쉽게 띄우고 관리해주는 서비스입니다. 서울 리전에 EKS가 출시된 후에는 관리 오버헤드를 줄이기 위해 EKS로 옮겼습니다.ECR: 타다 서버를 배포할 때는 Docker Gradle Plugin을 통해 docker 이미지를 만들고 ECR에 푸시합니다. 그 후 helm 명령을 통해 Kubernetes에 배포합니다.SQS: 배차 요청을 처리하기 위해 SQS를 이용합니다. 배차 요청을 구현하는 방법에는 다양한 옵션이 있었지만 AWS 서비스를 최대한 활용하여 빠르게 개발할 수 있었습니다.RDS: 타다의 대부분 데이터는 Aurora에 저장하고 있습니다. RDS를 이용하면 DB의 배포와 관리가 쉬우며, Aurora는 MySQL과 호환될 뿐만 아니라 같은 비용이면 성능이 더 좋습니다.Kinesis: 실시간 차량 위치 정보 및 로그를 수집하기 위해 사용하고 있습니다. 다른 오픈소스 소프트웨어를 직접 이용하기보다는 AWS에서 제공하는 서비스를 최대한 이용하고 있습니다.Firehose: 비트윈에서는 KCL를 활용해 Acheron이라는 프로그램을 직접 만들어 로그들을 S3에 저장하였지만, 이제는 서울 리전에서 Firehose를 사용할 수 있으므로 큰 고민 없이 사용하기로 하였습니다.시스템 구성타다에서는 필요에 따라 서비스를 여러 종류로 분리하여 운영하고 있습니다. 일반적인 모바일 앱 API와 실시간 차량의 위치 정보를 바탕으로 사용자의 요청에 대해 적합한 차량을 배차하는 기능이 필요했습니다. 핵심적인 역할을 하는 일부 서비스와 시스템 구성에 대해 간단하게 소개합니다.라이더 앱: 아이폰은 Swift, 안드로이드는 Kotlin으로 작성하였으며 여러 오픈소스 라이브러리를 적극적으로 활용하였습니다. 서비스 특성상 RIBs라는 아키텍처를 사용하여 개발하였습니다.드라이버 앱: 아이폰과 안드로이드를 모두 지원하려면 기술적, UX적으로 고려해야 할 점들이 많고 불특정 다수의 유저를 대상으로 하는 앱도 아니었기 때문에 안드로이드 버전으로만 개발하게 되었습니다.서버: 모바일 앱의 요청을 대부분 처리하며 Spring Boot로 작성된 HTTP/2 API 서버입니다. Protocol Buffers로 정의된 메시지를 JSON 형태로 주고받습니다.gRPC 서버: 서버에서 발생하는 이벤트를 실시간으로 전달하기 위한 서버입니다. Redis Pub/Sub을 통해 받은 이벤트 메시지들을 클라이언트들에게 전달합니다.Dispatcher: 배차 요청을 처리하는 서버입니다. 주변 차들의 ETA 계산을 위해 외부 API를 이용하는데, Reactor를 이용해 비동기적, 동시적으로 요청하여 쓰레드 점유 없이 효율적으로 처리되도록 구현하였습니다.Tracker: 차량 위치 정보 수집 서버입니다. KCL를 이용해 위치 정보 레코드를 읽어 들여 TrackerDB에 기록합니다.Redis: 서비스 초기에는 차량의 최신 위치 등을 저장하기도 했지만, 지금은 주로 서버 간 메시징을 위해 Pub/Sub 기능을 이용하고 있습니다.DB: 운행 기록, 사용자 데이터 등 대부분 데이터를 기록합니다. 비트윈에서는 HBase를 이용했지만 타다의 경우 아직 절대적인 트래픽이 많지 않기 때문에 트랜잭션 등 다양한 편의 기능을 제공하는 RDB를 이용하고 있습니다.TrackerDB: 차량 운행 정보 및 차량의 최신 위치 등을 저장합니다. Aurora를 이용하며 대부분의 요청이 차량 위치 정보 업데이트이므로 안정성을 위해 별도의 인스턴스를 띄워 사용하고 있습니다.Kinesis Log Stream: 타다의 여러 서비스에서 로깅을 위해 이용합니다. Firehose를 통해 S3에 기록됩니다.Kinesis Tracker Stream: 드라이버의 실시간 위치 정보는 Kinesis를 통해 Tracker로 전달됩니다.서비스 플로우차량 위치 업데이트차량 위치 업데이트는 요금 계산, 차량 위치 제공 등 서비스에서 가장 많이 일어나는 요청입니다. 드라이버 앱에서 안드로이드 Foreground 서비스를 이용해 GPS 정보를 수집하고 일정 주기마다 서버로 현재 위치를 전송합니다. 이렇게 전송받은 GPS 위치 정보는 데이터 크기를 최소화하기 위해 Protocol Buffers로 직렬화되어 Kinesis 레코드로 만들어지게 됩니다. Tracker에서는 전달된 Kinesis 레코드를 읽어 간단한 처리를 한 후에 TrackerDB에 삽입합니다.서비스 초기에는 차량의 마지막 위치에 대한 정보만 Redis에 적었습니다. 그러나 차량의 이동 경로를 효율적으로 조회해야 할 일이 생겼는데, 당시 차량 이동 경로는 로그로만 저장되고 있었습니다. S3 Select나 Athena를 이용해 조회하는 방안도 고려했지만, 일단은 Aurora에 저장하기로 하였습니다. 당분간은 Aurora로도 충분했고 RDB를 쓰는 것이 가장 쉽고 편한 방법이었기 때문입니다.차량 배차차량 배차는 서비스의 가장 기본적인 기능으로 배차 요청에 가장 적절한 주변 차량을 할당하는 플로우입니다. 라이더 앱에서 유저가 배차를 요청하면 서버가 배차 요청 정보를 DB에 기록하고 배차 요청 메시지를 SQS 대기열에 집어넣습니다. Dispatcher가 배차를 처리하는 로직을 수행하여 차량이 매칭되면 드라이버 앱으로 이벤트가 전달됩니다.드라이버가 배차를 수락하면 서버로 수락 요청이 전송되고 서버에서는 DB의 배차 요청 상태를 수락 상태로 변경합니다. 배차 요청이 수락되었다는 이벤트는 결과적으로 gRPC 서버를 통해 해당 이벤트를 구독하고 있던 유저에게 전달됩니다.Dispatcher에서 배차를 처리하는 로직은 여러 옵션이 있었지만 가장 간단하고 효율적으로 개발하기 위해 SQS의 기능을 최대한 활용하였습니다. Dispatcher 수를 늘리는 것만으로도 처리량 확장이 가능하며 Dispatcher가 갑자기 종료되어도 한 대라도 살아있다면 결국에는 잘 처리가 됩니다. Dispatcher가 배차 요청을 받으면 다음과 같은 로직을 수행합니다. 종료 조건을 만족하지 않았다면 일정 시간 후 동일한 로직을 다시 반복합니다.배차가 가능한 상태라면 배차 로직을 수행합니다. 이동 경로와 교통정보를 고려하여 적합한 주변 차량을 찾습니다.만약 적합한 차량이 있다면 배차 요청을 해당 드라이버에게 할당되었다는 정보를 DB에 적고 배차 할당 이벤트를 전파합니다. 드라이버의 수락을 기다리기 위해 일정 시간 후 로직을 재시도합니다.만약 적합한 차량이 없다면 일정 시간 후에 로직을 재시도합니다.배차 요청이 드라이버의 수락을 기다려야 하거나 타임아웃이 남아있는 상태라면 적절한 시간 후 재시도합니다.배차 요청이 수락되어 완료된 상태거나 취소되었거나 타임아웃이 지난 상태라면 SQS에서 메시지를 삭제합니다.못다 한 이야기타다를 런칭하는 날, 기사 간담회에서 쏘카의 VCNC 인수 이후 짧은 기간 동안 타다를 만들 수 있었을 리 없으니, 실제 개발 기간은 어느 정도냐는 질문이 있었습니다. 짧은 기간 내 서비스를 성공적으로 런칭할 수 있었던 것은 상황에 맞는 올바른 기술적 선택들뿐만 아니라 훌륭한 팀원들이 있었기에 가능했던 일이었습니다. 타다는 개선해야 할 부분도 많고 앞으로 새로운 기술적 도전들이 많이 있을 것입니다.네 그렇습니다. 결론은 기술적 난제들을 고민하면서 좋은 팀과 서비스를 함께 만들고 키워나갈 좋은 분들을 기다리고 있다는 것입니다.
조회수 1527

8퍼센트 '프로덕트' 팀 인터뷰

안녕하세요, 8퍼센트입니다.8퍼센트는 다양한 매체와 콘텐츠로 이야기를 전하고 있습니다. 이번엔 인터뷰를 통해 8퍼센트를 이루고 있는 각 팀들의 이야기를 들어보며 고객에게 더 가까이 다가가고자 합니다. 그 첫 번째 주인공은 서비스 개발을 담당하는 ‘프로덕트’ 팀입니다.Q. 안녕하세요, 인터뷰를 위해 프로덕트 팀 허재영 님과 이호성 CTO님이 자리해주셨는데요. 먼저, 8퍼센트의 제품을 만드는 프로덕트팀은 구체적으로 어떤 일을 하시나요?A. 프로덕트 팀은 8퍼센트의 서비스를 만드는 팀입니다. 고객들이 8퍼센트를 이용하는 데 있어서 불편함이 없도록 서비스를 개선하고 유지하는 역할을 하고 있습니다. 예를 들어 기존의 인터넷 기반 금융 서비스는 공인인증서, Active X를 보면 알 수 있듯이 사용자의 편리함에 초점을 두고 있지 않습니다. 저희의 역할은 사용자들에게 더 편리하고 효율적인 금융 생활을 할 수 있도록 새로운 금융서비스를 시도하는 것입니다.Q. 새로운 금융시스템을 구축하고 운영해나가는 과정이 쉽지 않을 것 같은데, 그것에 대해 어려움과 극복해낸 경험이 궁금합니다.A. 8퍼센트는 이미 만들어진 솔루션을 사용하거나 외주를 통해 시스템을 구축하는 기존의 회사들과 다르게 직접 바닥부터 금융 시스템을 쌓고 있습니다. 금융시스템은 정확함과 안정성을 빼놓을 수 없는데 사용자가 많아지고 시스템이 거대해질수록 생각지 못한 부분에서 오류가 생기게 됩니다. 이러한 오류는 회사의 손실을 발생시키고, 고객들의 신뢰를 잃게 합니다. 위와 같은 일이 발생하지 않게 시스템을 정밀하게 설계하는 것은 기본이고 추가로 발생하는 문제들에 대해 올바르게 대처하는 것이 금융 시스템을 구축하고 운영하는 과정입니다. 물론 힘든 과정이지만 단계별로 시스템을 구축하는 것이 앞으로 남들과 다른 서비스를 제공할 수 있는 기반이 된다고 생각합니다.서비스 초기, 지급 프로세스에 문제가 있었던 적이 있었는데, 이를 인지하고 그에 대한 대응을 진행했습니다. 또한, 대응에 그치지 않고 테스트와 수정한 부분에 대해 제대로 동작하고 있는지 알아보는 QA(Quality Assurance) 프로세스를 갖추는 계기가 되었습니다. Q. 그렇게 만들어지는 8퍼센트 서비스만의 차별화된 점은 무엇인가요?A. 첫 번째는 '자동 투자'입니다. 자동 투자를 선택하게 되면 예치금과 상환된 투자금이 지속적으로 재투자됩니다. 따라서, 고객이 직접 신경 쓰지 않아도 자산을 쉽게 불릴 수 있습니다. 두 번째는 '스페셜딜'입니다. 일부 스페셜딜은 기업이 제공하는 서비스를 투자자가 직접 체험해볼 수 있으며, 이를 통해 고객은 투자 이외의 부수적인 혜택을 누릴 수 있습니다. 소개해드릴 마지막 차별점은 다양한 업체와의 제휴입니다. 8퍼센트는 현재, 기존 금융권에 있는 많은 금융 회사들과 제휴를 맺고 있습니다. 기존 금융 회사가 오랜 시간 쌓아놓은 시스템과 노하우, 그리고 8퍼센트의 서비스를 합쳐 좋은 서비스를 제공하는 것은 저희 서비스의 강점이 된다고 생각합니다. 또한, 기존 금융권뿐 아니라 토스와 같은 스타트업과의 제휴 역시, 토스 플랫폼에서 간편하게 8퍼센트 서비스를 이용할 수 있다는 점에서 이런 다양한 제휴는 저희만의 차별점이 된다고 생각합니다.Q. 8퍼센트 서비스에는 정말 다양한 장점이 있는 것 같습니다. 이렇게 좋은 서비스를 구상할 때 중요하게 생각하는 기준이 무엇인가요?A. 프로덕트 팀에서는 제품을 구상하는 데 있어서 ‘고객들에게 전달될 수 있는 가치’, ‘안정성과 정확성’, ‘사용성’ 이렇게 세 가지 기준을 중요하게 생각하고 있습니다. 금융 서비스는 대부분 돈으로 환산되는 가치를 추구합니다. 프로덕트 팀이 추구하는 금전적인 가치 역시 투자를 했을 때 돈을 벌고, 대출을 통해 돈을 절약하는 것입니다. 이러한 금전적 가치는 개인과 개인들이 서로 연결되어 발생한다는 점에서 사회적 가치에 기여한다 생각합니다. 또한, 핀테크 서비스들이 나오기 이전에 투자와 대출은 상당히 무겁고 다가가기 힘든 면이 있었습니다. 그래서 프로덕트 팀에서는 투자와 대출로 이뤄진 8퍼센트 서비스를 이용자가 손쉽게 쓸 수 있게 만드는 ‘사용성’이 제품을 구상하는 데 있어서 중요한 기준이 됩니다. 예를 들어, 토스 플랫폼을 통해서 저희 투자 서비스를 이용할 수 있게 하는 것도 ‘사용성’을 높이는 것의 일환입니다.Q. 얘기를 들어보니 명확한 기준을 통해 좋은 서비스가 나오는 것 같습니다. 8퍼센트 투자나 대출 서비스를 직접 이용하시나요? A. 모두가 소액부터 거액까지 다양하게 투자 서비스를 직접 체험하고 있습니다. 이는 고객의 입장에서 생각해볼 기회가 되어 일하는데 좋은 자극이 됩니다. ‘개밥 먹기’라고 개 사료를 만드는 회사에서 실제로 먹어보면서 제품이 어떤지 테스트하는 것에서 유래한 말이 있습니다. 8퍼센트 역시 이런 '개밥 먹기' 테스트를 꾸준히 하고 있습니다. 상품 2.0이 처음 출시되었을 때, 직접 소액 대출을 받아 안내, 혹은 연체 문자가 잘 오는지 등 대출 프로세스를 경험하기 위해서 대출 서비스를 이용했습니다. 물론 회사 내부 관계자에게 대출하기 위해 투자자를 모은다는 것은 윤리적인 문제가 있기 때문에 딜을 내부로만 열어서 회사 분들이 투자한 것만으로 모집했습니다. 신용등급은 당연히 떨어지지 않았고 상환하며 아직 잘 쓰고 있습니다.Q. 현재 P2P 금융 법제화에 대한 논의가 활발히 진행되고 있는데, 과거 새로운 규제가 생겼을 때 대처한 경험이 궁금합니다.A. 지금까지 가장 큰 변화는 작년 5월 가이드라인이 시행되면서 일어났습니다. 그전까지 투자자들로부터 모집한 자금은 회사의 소속으로 되어있었는데, 회사가 부도가 나게 되면 그 돈이 압류되어 투자자들의 돈을 못 빼는 현상이 발생할 수 있었습니다. 가이드라인에서 제시한 부분 중 가장 큰 것이 바로 이에 대한 것입니다. 투자자의 돈을 제삼자가 보관하게 해라 즉, 금융기관이 그 돈을 보관하게 하라는 것인데 이를 위해 농협과 함께 설계부터 시작해 지금의 시스템을 만들었습니다. 농협 측에 자금을 보관하고 저희가 시스템상으로 자금의 흐름을 요청하는 식으로 자금이 직접 저희를 통하지 않고 P2P 거래가 이루어지게 되었습니다.Q. 프로덕트 팀의 대처 능력이라면 법제화 같은 변화에서도 흔들림 없는 서비스를 제공할 수 있을 것 같습니다. 마지막으로 프로덕트팀의 목표는 무엇인가요?A. 프로덕트 팀에서는 항상 ‘우리가 바라는 프로덕트가 무엇일까?’ 고민합니다. 개발자로서 가장 안타까운 것은 열 명의 팀원들이 서로 힘내서 만들어내는 서비스가 사라지는 것입니다. 더 나아가, 사라지지 않는다는 것은 사회적인 가치를 인정받는다는 것입니다. 물론 돈을 만들어낸다는 얘기이기도 하지만 우리가 열심히 만든 자식과도 같은 서비스의 사회적인 가치를 인정받고 지속가능하게 하는 것이 최종적인 목표입니다. 특히 이번 18년도 1분기에 8퍼센트의 단기적 성장과 함께 미래 계획이 구체화 되고 그에 대한 긍정적인 확신이 생겨 큰 동기부여가 되었습니다. 8퍼센트 고객들도 더 편리하고 효율적인 서비스를 만들어갈 저희와 끝까지 동행해주셨으면 좋겠습니다.인터뷰는 8퍼센트의 모든 팀을 소개할 때까지 계속되니 많이 기대해주세요:)> 8퍼센트 서비스 보러 가기 #8퍼센트 #에잇퍼센트 #프로덕트팀 #프로덕트 #인터뷰 #팀원소개 #팀소개
조회수 867

[Tech Blog] How we pipe data

버즈빌에서는 미국과 일본을 비롯한 전 세계 30개국에서 1,700만 이상의 유저의 행동에 대한 데이터를 수집하고 있습니다. 이 데이터에는 유저들이 잠금화면에서 어떤 Action을 수행하는지부터 잠금화면에 어떤 광고가 노출되고 유저들이 어떤 광고를 클릭 하는 지 등의 정보들이 포함되는데요. 이러한 데이터는 여러 종류의 다른 소스로부터 오고 각기 다른 종류의 DB (MySQL, DynamoDB, Redis, S3 등등) 에 저장됩니다. 하지만 데이터를 분석하고 활용하기 위해서는 이렇게 흩어져서 저장된 데이터들을 한 곳으로 모으는게 필수적입니다. 그래서 저희 팀에서는 이렇게 다양한 소스로 부터 발생해서 다양한 DB에 저장된 데이터를 어떤 과정을 통해 한 곳으로 모을 것인가에 대해서 고민하게 되었습니다. 그리고 고민 끝에 각각의 DB에 저장된 데이터를 하나의 큰 데이터 스토리지에 모을 수 있는 ‘데이터 파이프라인’을 구축하는 계획을 세우게 되었습니다. 하지만 다양한 소스로부터 수집된 수많은 데이터들을 잘 유지해가며 하나의 큰 DB에 모을 수 있는 데이터 파이프라인을 구축하는 것이 쉽지 않았는데요. 이 포스팅을 통해서 버즈빌에서는 어떻게 각각의 데이터들을 수집하고 저장하는지 또 이런 데이터들을 통합하기 위한 파이프라인을 어떻게 구축했는지 공유하고자 합니다. 본격적인 이야기에 앞서 현재 버즈빌에서 모든 데이터가 모이는 데이터 스토리지로 사용 중인 RedShift에 대해 이야기하고 싶습니다. 개인적으로는 정말 쓰면 쓸수록 감탄이 나오는 데이터 스토리지라고 생각합니다. Redshift는 AWS에서 관리하는 SQL기반의 열기반 스토리지(SQL based columnar data warehouse)이며 복잡하고 대규모의 데이터 분석에 적합합니다. 고객들로부터 생성된 수많은 종류의 데이터를 기반으로 다양한 인사이트를 얻고자 하는 많은 기업들(Yelp, Coursera, Pinterest 등)이 사용하고 있는 솔루션 이기도 합니다. 버즈빌에서는 여러가지 특징을 고려하여 Redshift를 도입하게 되었는데요. 그 이유는 아래와 같습니다.  Performance Performance Performance.     Column 기반 스토리지 -> 필요한 Column에만 접근한다.   Join이나 aggregation이 많은 복잡한 쿼리도 쉽게 계산할 수 있다.   분산 저장 방식 (Distributed Storage)   Date Ingestion이 빠르다. (Ingest first, index and clean later)     Horizontal Scalability   sharding이나 clustering에 추가적인 complexity가 필요하지 않다. 데이터가 원래 노드에 저장되기 때문에 horizontal scaling을 위해서는 그냥 추가적인 노드만 붙이면 된다. 다른 AWS서비스들과 쉽게 연동이 가능하다. (장점 이자 단점)    하지만 몇 개의 아쉬운 점들도 있습니다. :  다른 RDBMS와 달리 Mutilple indice를 지원하지 않는다.  1 Distribution Key and 1 Sort Key   MySQL이나 다른 RDBMS처럼 uniqueness나 foreign key constraint를 걸 수 없다.     모은 데이터를 어떤 방식으로 Redshift로 옮겨야 할까요? 버즈빌이 구축한 데이터 파이프 라인은 크게 3갈래의 메인 루트가 있습니다.   1) Athena Preprocessing Batch job을 통해서 (잠금화면 활동, 광고 할당) Why? 전처리 작업(Preprocessing)이 필요한 가장 큰 이유는 들어오는 데이터의 어마어마한 크기 때문입니다. 또 어떤 데이터들은 너무 raw하기 때문에 애널리스트나 데이터 사이언티스트가 분석에 활용할 수 있는 형태로 바꾸기위해 전처리가 필요하기도 합니다. 버즈빌에서는 이런 데이터들을 처리하기 위해서 AWS Athena를 사용하고 있습니다. Athena는 과금 방식이 Athena 쿼리로 읽은 데이터의 사이즈를 기반으로 하기 때문에 다른 EMR이나 MapReduce solution들을 사용했을때보다 상대적으로 적은 비용으로 활용할 수 있다는 장점이 있습니다. How?  먼저 S3로 데이터를 보냅니다. 그 후, Athena를 활용하여 데이터를 가공/처리합니다. 가공된 데이터를 읽어서 Redshift로 보냅니다. (COPY command 활용)  Pros?  서버를 따로 가질 필요가 없습니다. (EMR 클러스터나 서버를 관리할 필요가 없음) 경제적입니다. (S3에서 1TB를 읽을때마다 $5 정도의 비용)  Cons?  사용량이 몰리는 시간대 (12:00 AM UTC)에는 일부 쿼리가 실패할 수 있습니다. -> 중요하고 필수적인 데이터는 Athena가 아닌 다른 방법을 통해 처리하는것이 적합합니다. PRESTO DB의 기능을 (아직은) 온전히 활용할 수 없습니다.     2) Firehose를 통해서 (Impression, Clicks, Device, Events) Why? Kinesis Firehose는 Redshift, Elasticsearch, S3와 같은 최종 목적지까지 다양한 데이터들을 안정적으로 옮길 수 있는 파이프라인을 제공할 뿐 아니라 Fluentd와 매끄럽게 잘 연동된다는 점에서 굉장히 뛰어난 서비스 입니다. Fluentd는 서버로부터 firehose까지 데이터가 안정적이고 꾸준하게 전달 될 수 있도록 도와줍니다.  따라서 firehose와 fluentd의 연동을 통해서 따로 두개의 파이프라인 ( SERVER -> S3, S3 -> Redshift) 을 관리할 필요 없이 데이터 소스부터 최종 저장소까지 이어지는 하나의 파이프 라인만 관리할 수 있게 됩니다. How?  (https://docs.aws.amazon.com/firehose/latest/dev/what-is-this-service.html)  적절한 data format과 원하는 ingestion period를 설정하여 Firehose delivery stream을 만듭니다.   conf["user_activity"] = { "DataTableName": "user_activity", "DataTableColumns": "user_id, app_id, activity_type, timestamp", "CopyOptions": "FORMAT AS JSON "s3://buzzvil-firehose/sample/user_activity/jsonpaths/user_activity_log-0001.jsonpaths" gzip TIMEFORMAT AS "YYYY-MM-DDTHH:MI:SS" ACCEPTINVCHARS TRUNCATECOLUMNS COMPUPDATE OFF STATUPDATE OFF", "jsonpaths_file": "buzzvil-firehose/sample/user_activity/jsonpaths/user_activity_log-0001.jsonpaths", } configuration = { "RoleARN": "arn:aws:iam::xxxxxxxxxxxx:role/firehose_delivery_role", "ClusterJDBCURL": "jdbc:redshift://buzzvil.xxxxxxxxx.us-west-2.redshift.amazonaws.com:5439/sample_db", "CopyCommand": { "DataTableName": sample_table, "DataTableColumns": conf[type]["DataTableColumns"], "CopyOptions": conf[type]["CopyOptions"], }, "Username": db_user, "Password": db_password, "S3Configuration": { "RoleARN": "arn:aws:iam::xxxxxxxxxxxx:role/firehose_delivery_role", "BucketARN": "arn:aws:s3:::firehose_bucket", "Prefix": "buzzvil/user_activity/", "BufferingHints": { "SizeInMBs": 64, "IntervalInSeconds": 60 }, "CompressionFormat": "GZIP", "EncryptionConfiguration": { "NoEncryptionConfig": "NoEncryption", } } }  2. Fluentd docker containers을 각각의 서버에서 세팅하고 실행합니다.  @type tail path /var/log/containers/buzzad/impression.json pos_file /var/log/containers/td-agent/impression-json.pos format none tag firehose.impression @type kinesis_firehose region us-west-2 delivery_stream_name "prod-buzzad-impression-stream" flush_interval 1s data_key message  3. Firehose에서 데이터를 잘 모아서 Redshift 문제없이 보내고 있는지 모니터링 합니다.  Pros?  빠르고 안정적인 데이터 전송이 가능합니다. 모니터링이 편합니다.  Cons?  Schema가 자동으로 바뀌지 않습니다.( Redshift의 Schema를 수동으로 일일히 변경해주어야 합니다.)     3) MySQL Asynchronous Loads를 통해 (Ads, Contents, Ad Provider, Ad Publishers) Why? 여러대의 RDS MySQL DB로부터오는 데이터간의 sync를 맞춰가며 Redshift로 데이터를 복제하기 위해서는 3가지의 테크닉을 활용해야만 합니다. (이 방법은 소개하고 있는 세 메인 루트 중에서 가장 매력도가 떨어지는 방법입니다..) How?  FULL_COPY  MySQL 테이블 전체를 복사해서 SQL insert를 통해서 Redshift에 복사합니다.     INCREMENTAL_COPY  이전에 복사한 가장 마지막 Primary key부터 시작해서 새로생긴 row들을 읽어서 Redshift로 복사합니다.     UPDATE_LATEST_COPY  이전에 복사한 가장 마지막 타임스탬프부터 시작해서 새로 생성되거나 업데이트된 row들을 Redshift로 복사합니다.(중복된 값은 삭제).    Pros?  데이터의 특징에 맞게 잘 조정된 방법입니다. binary log를 통한 Replication보다 훨씬 다루기 쉽습니다.  Cons?  MySQL을 잘 조정하기 위해 여러대의 서버나 lambda를 다루어야만 합니다. -> Redshift sync task를 위해서 안정적인 schema altering을 할 수 있을 만큼 Redshift의 ORM이 발전된 상황은 아닙니다..    어떤 데이터를 다루는지에 따라서 위에서 소개한 3가지 방법 중 어떤 방법을 활용해야할지가 달라진다고 할 수 있습니다. 예를 들어 Transactianl log 같은 데이터들의 경우에는 firehose를 통해 전달하는 방법이나 먼저 aggregate하는 과정을 거친 후에 Redshift에 저장하는 식으로 처리를 해야 합니다. 그리고 MySQL에 저장된 fact table같은 데이터들은 CDC (change data capture) sync method를 통해서 Redshift에 데이터를 전달하고 동기화를 하는 과정이 필요합니다. 버즈빌에서는 위에서 소개해드린 3가지 방법을 적절히 조합해가면서 BD 매니저나 애널리스트들이 서비스간 플랫폼간의 데이터분석을 쉽게 할 수 있는 데이터 환경을 구축하기 위해서 노력하고 있습니다.
조회수 1076

오토 레이아웃(Auto Layout), 넌 누구냐!

OverviewiOS 프로그래밍을 하면서 많이 접했던 단어 중 하나는 오토 레이아웃(Auto Layout) 입니다. 스토리보드에서 화면을 만들 때 오토 레이아웃을 이용해서 뷰와 컨트롤의 크기와 위치를 지정합니다. 이미 잘 사용하고 있지만 문득 정확하게 오토 레이아웃은 무엇인지 궁금해져 이번 기회에 써 보기로 했습니다. 오토 레이아웃(Auto Layout)은?오토 레이아웃(Auto Layout)은 제약 조건(Constraints)을 이용해서 뷰의 위치를 지정하는 것입니다. 다시 말하면, 두 뷰 사이의 관계를 제약 조건이라는 것을 이용해서 뷰의 크기와 위치를 지정하는 것입니다. 너와 나의 연결 고리!오토 레이아웃은 여러 해상도를 지원하려고 이 세상에 나왔습니다. 아이폰의 크기가 다양해지면서 해상도도 달라졌는데, 다른 크기에서도 같은 화면을 똑같이 보여주기 위해 오토 레이아웃을 사용합니다. 세로 보기 화면뿐만 아니라 가로 보기 화면까지도 지원합니다. 아이폰SE 혹은 아이폰8 Plus에서도 같은 비율의 화면을 볼 수 있도록 오토 레이아웃을 사용하는 것입니다. 만약 오토 레이아웃을 사용하지 않는다면, 아이폰 기종마다 스토리보드를 만들어야 하죠. 이렇게 되면 스토리보드 파일이 많아집니다. 앱을 실행할 때 아이폰 기종을 확인하고, 그에 맞는 스토리보드를 찾아 화면을 보여주는 번거로움도 생깁니다. 위의 이미지를 보면 아이폰SE와 아이폰8, 아이폰8 Plus 브랜디 앱 화면. 기종이 달라도 보여지는 화면이 똑같다는 것을 볼 수 있습니다. 오토 레이아웃을 이용해서 하나의 스토리보드에서 모두 대응할 수 있는 것이죠.Frame Layout vs Auto Layout전통적으로 앱은 유저 인터페이스를 각 뷰의 프레임(frame)을 프로그래밍 방식으로 계산해 배치합니다. 유저 인터페이스를 배치하려면 뷰 계층의 모든 뷰에 대한 크기와 위치를 계산해야 합니다. 그리고 변경이 발생하면 영향을 받는 모든 뷰에 대해 프레임을 다시 계산합니다.Frame Layout뷰의 프레임을 프로그래밍 방식으로 정의하면 유연해집니다. 어떤 변화가 생겨도 대응할 수 있기 때문입니다. 그러나 모든 변경 사항을 직접 관리해야 하기 때문에 많은 노력이 필요합니다. 설계부터 시작하여 디버그 및 유지 관리까지 많은 것을 관리해야 합니다. 가장 효과적인 방법이지만 난이도도 많이 어려워집니다.이와 달리 오토 레이아웃은 일련의 제약 조건을 사용하여 유저 인터페이스를 정의합니다. 제약 조건은 앞서 말한 것 처럼, 일반적으로 두 뷰 간의 관계를 나타냅니다. 그런 다음 오토 레이아웃은 이러한 제약 조건을 기반으로 각 뷰의 크기와 위치를 계산합니다.Auto Layout화면에 배치하는 모습이 같기 때문에 프레임 방식을 사용해도 되고, 오토 레이아웃을 사용해도 됩니다. 둘 다 스위프트와 오브젝티브 C를 지원하기도 합니다. 각각 장단점이 있지만 가장 많이 사용하는 방법이 오토 레이아웃입니다. 빠르게 적용할 수 있고 많은 시간을 줄일 수 있기 때문입니다.스토리보드에서의 오토 레이아웃iOS 앱 개발은 스토리보드를 이용해서 화면을 만듭니다. 그래서 스토리보드가 익숙한 개발자들이 많은데, 사실은 뷰를 배치하면서 썼던 툴이 오토 레이아웃과 관련된 것이었습니다.스토리보드 오른쪽 하단에 있는 메뉴핀(Pin) 메뉴는 버튼 또는 레이블과 같은 UI 요소에 새로운 제약 조건들을 추가할 수 있습니다. 시계 방향으로 Top, Trailing, Bottom, Leading 제약 조건의 값을 입력할 수 있고, 화살표를 누르면 어떤 뷰와 관계를 가질 것인지 선택할 수 있습니다. 두 뷰와 핀 메뉴를 선택하면 같은 너비와 높이를 설정할 수 있습니다.Pin 메뉴정렬(Align) 메뉴는 다른 뷰와의 가로, 세로 정렬과 같은 정렬 제약 조건들을 추가할 수 있습니다. 정렬하고 싶은 두 뷰를 선택하여 수직 정렬, 수평 정렬을 추가할 수 있습니다.Align 메뉴맨 오른쪽 메뉴인 오토 레이아웃 이슈 툴은 오토 레이아웃 관련된 이슈들을 해결하는 옵션들을 제공합니다. 오토 레이아웃을 현재 설정된 상태로 재설정하는 옵션들입니다. 상단은 선택된 뷰와 관련된 것이고, 하단은 모든 뷰와 관련된 것입니다.Resolve Auto Layout IssuesAlign 옆에 있는 Stack 메뉴는 복잡한 제약 조건 없이 오토 레이아웃의 기능을 쉽게 뷰를 배치할 수 있도록 스택에 쌓아서 묶어주는 스택뷰를 생성합니다. 하나의 묶음으로 만들 뷰들을 선택하여 Stack 메뉴를 선택하면 스택처럼 그룹으로 됩니다. 여기서 뷰 사이의 공간과 정렬들을 설정할 수 있습니다.Stack View로 만든 간단한 뷰, 오른쪽 메뉴에 정렬과 뷰 사이의 공간을 선택할 수 있는 곳이 있습니다.스토리보드에서 뷰를 배치하고 오토 레이아웃 메뉴들을 이용하면 아래 스크린샷과 같이 제약 조건들을 볼 수 있습니다. 어떤 값을 지정하는 것이 아닌 같다는 뜻의 “=“를 이용하여 제약 조건들을 표현합니다.스토리보드에서 많이 볼 수 있는 제약 조건들(Constraints)프로그램 상의 제약 조건들스토리보드에서만 제약 조건들을 설정할 수 있는 건 아닙니다. 프로그램 상에서도 제약 조건들을 설정할 수 있습니다. 스토리보드에서 뷰를 배치한 다음, 제약 조건들을 소스 파일과 연결해서 값을 지정할 수 있습니다. 주로 어떤 변화가 일어나면 제약 조건들을 다시 설정할 때, 프로그램 상에서 값을 다시 설정합니다. 예를 들어, 데이터가 있을 땐 해당 뷰를 보여줍니다. 만약 데이터가 없으면 그 뷰가 사라지면서 그 뷰와 관련되어 있는 다른 뷰의 제약 조건들을 다시 설정하여 화면에 재배치하는 것입니다.func hideTag(_ hide: Bool) {         if hide {             self.labelTag1.isHidden = true             self.labelTag2.isHidden = true             self.constLabelTag1Top.constant = 0.0             self.constLabelTag1Height.constant = 0.0         } else {             self.labelTag1.isHidden = false             self.labelTag2.isHidden = false             self.constLabelTag1Top.constant = 15.0             self.constLabelTag1Trailing.constant = 5.0             self.constLabelTag1Height.constant = 20.0         }     } 위 소스에서 hide 값에 따라 레이블의 숨김을 설정하고 레이블의 제약 조건의 값을 재설정하는 메소드가 있습니다. 데이터가 있으면 숨김을 해제하고 제약 조건들의 값을 설정하지만, 데이터가 없으면 레이블을 숨기고 제약 조건들의 값을 0으로 설정합니다.스토리보드에서 연결한 제약 조건들을 가지고 설정할 수 있는데, 프로그램 상에서 직접 제약 조건들을 생성하여 사용할 수 있습니다. 아래의 예시는 뷰의 높이를 60으로 설정하는 코드입니다.NSLayoutConstraint(item: self.testView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 60) Conclusion애플에서는 개발자가 다양한 해상도에 대응할 수 있게 오토 레이아웃이라는 시스템을 개발했습니다. 스토리보드에서 쉽게 화면에 뷰를 배치할 수 있고, 별다른 기능을 추가하지 않아도 다양한 아이폰 크기에 맞춰서 대응해줍니다. 오토 레이아웃을 이용하여 멋지게 모든 아이폰과 아이패드에 대응하는 앱을 개발해보세요! 곧 산호세로 떠나 설레는 마음으로 글을 마치겠습니다. 감사합니다. :)글김주희 사원 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발팀 #개발자 #개발환경 #업무환경 #인사이트 #경험공유
조회수 1459

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

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

평균 응답시간의 의미

어플리케이션 성능 분야에서 평균 응답 시간은 어플리케이션 서버가 사용자에게 요청 결과를 반환하는 데 걸리는 시간을 말합니다. 어플리케이션 서버의 응답시간은 일반적으로 밀리세컨드에 가깝지만 부하량에 따라 많은 시간이 걸리기도 합니다. 고객이 기다리는 시간 3초인터넷 초창기인 1999년 전자 상거래 사이트의 최적로드 시간은 8초 였습니다. 2006년도에 들어서는 4초까지 줄어들었습니다. 그리고 지금은 3초를 고객을 떠나게 만드는 시간으로 이야기 합니다. 구글 이 운영하는 더블클릭(https://www.doubleclickbygoogle.com/articles/mobile-speed-matters/)은 모바일 페이지가 로드되는데 3초가 지나면 사용자의 절반 이상이 서비스를 포기한다고 조사결과를 발표했습니다. 3초라는 시간 속에는 웹페이지의 렌더링 시간과 네트웍이 사용하는 시간등이 포함되어 있기 때문에 웹 어플리케이션이 소모해야 하는 시간은 실제로 밀리세컨드에 가깝습니다. 하지만 실제 서비스의 장애가 발생하면서 웹 어플리케이션의 평균 응답시간은 점점 길어지게 됩니다. 성능분석에서 평균 응답시간부하가 늘어나면서 임계치가 넘어가면 초당 처리량은 더이상 증가하지 않게 됩니다. 논리적으로 생각 해보면 초당 처리량이 더이상 증가하지 않은 상태에서 사용자만 늘어나면 TPS와 인지시간이 상수처럼 동작하므로 응답시간이 사용자에 비례하여 늘어나게 됩니다. [응답시간(Respons Time) = [동시사용자수 / 초당 요청수(TPS)] - 인지시간(Think Time)하지만 일반적인 상황에서 응답시간은 밀리세컨드 단위의 값이데 비해 인지시간은 3초에서 10초 이상의 값을 가지고 됩니다. 그럼 이번에는 성능을 분석하는 스토리를 만들어 보겠습니다. 우리가 영어 문장을 한글로 번역하는 웹 서비스를 만든다고 해 보겠습니다. 우리는 동시 사용자 100명을 예상하고 서비스를 만들고 있습니다. 여기서 서비스 특성상 사용자가 한번 번역을 요청하고 다음번 요청을 보내는데 평균 30초의 시간이 걸립니다. 마지막으로 최대 응답시간은 0.5초를 넘지 않도록 설계하려고 합니다. 이런 경우 우리가 목표로 하는 초당 요청수는 서비스를 동시에 사용하는 사람들의 요청을 시간으로 나누므로 계산식은 동시사용자수(100명)/(응답시간(0.5초) + 인지시간(30초)) 이고 결과값은 약 3.27이 됩니다.     초당 요청수(TPS) = 동시사용자수 / [응답시간(Respons Time) + 인지시간(Think Time)]이렇게 성능을 계산하는 과정에서 서비스의 처리시간 즉 응답시간은 인지시간에 비해 매우 적기 때문에 인지시간이 커지면 커질수록 TPS에 관여하는 비율이 0에 수렴하게 됩니다. 결론적으로 성능을 설계하는 시점에서 응답시간은 별로 중요한 이슈가 아니게 됩니다. 대신 인지시간이 중요해 집니다.인지시간(Think Time)이란?웹 서비스를 사용하는 사용자는 자신의 요청을 확인하는 시간이 필요합니다. 이렇게 이전 요청과 다음 요청 사이의 시간을 인지 시간이라고 합니다. 인지 시간은 사용자나 서비스 유형에 따라 다릅니다. 예를 들어 시스템 간 상호 작용은 사람이 관여하는 웹 서비스 상호작용에 비해 매우 낮은 인지 시간을 포함합니다. 또는 블로그 서비스에 비해 사전검색 서비스의 인지시간은 매우 짧을 것입니다. 서비스의 도메인을 분석하여 인지 시간을 결정하는 것은 매우 중요합니다. 인지시간을 사용하여 분당 완료해야 하는 요청 수는 물론 시스템에서 지원할 수 있는 동시 사용자 수를 계산할 수 있습니다. 튜닝 지표로서의 평균 응답시간현실에서 웹 서비스의 응답시간은 수식과 다르게 나타나게 됩니다. 그래서 많은 성능 분석 도구가 평균 응답시간을 보여주고 있습니다. 실제 성능 분석 도구들이 알려 주는 평균 응답시간은 수집 주기 동안에 수집된 트랜잭션의 응답 시간을 합산하여 평균한 값입니다.와탭의 서비스는 5초 간격으로 트랜잭션의 평균 응답시간을 계산합니다. 응답시간이 성능 지표보다 튜닝지표로서의 의미를 가집니다. 예를 들어 사용자가 적은 밤 시간에 배치잡과 같은 일부 응답시간이 길어짐으로써 사용자가 많은 낮보다 평균 응답시간이 더 길수도 있습니다. 하지만 실제 성능을 올리기 지표로써 응답시간은 매우 직접적입니다. TPS와 상관없이 평균 응답시간이 길어지는 요소가 있다면 주변 요소와 함께 평균 응답시간을 살펴봐야 합니다. #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 943

[인공지능 in IT] '머신 비전', 내 눈에 걸리기만 해봐

50~60년대 국내 상황은 말로 표현하기 힘들다. 당시 강대국들은 전쟁 직후 한국이 다시 정상적으로 복귀하는 것은 불가능하다고 여길 정도였으니, 여러 모로 살아남기 힘든 환경이었던 것만은 분명하다. 하지만, 뭐든지 열심히 노력하는 특유의 국민성을 바탕으로 한걸음씩 내딛기 시작했고, 1988년 서울 올림픽까지 개최할 정도로 경제 성장을 이뤘다. 당시 필자가 태어난 것은 아니었지만, 여러 자료나 부모님 세대의 말씀을 조합하면, 이 같은 성장의 중심에는 제조업의 부흥이 있었기 때문이다.제조업은 국가 실물 경제의 근간이라고 할 정도로 중요한 역할을 담당한다. 단단한 제조업 생태계가 창출해 내는 부가가치를 바탕으로 서비스업이 발전한다면, 산업의 경쟁력을 잃지 않으면서 지속적인 성장을 이뤄낼 수 있는데 큰 보탬이 된다. 최근에는 인공지능과 같은 고도의 기술이 널리 퍼져 제조업의 중요성을 더욱 부각하고 있다. 전통적인 기계 산업 기술은 과학기술을 지탱하는 뿌리의 역할을 하고, 인공지능이나 데이터의 확장 등 탄탄한 제조업 중심의 주력 산업과 융합해 폭발적으로 성과를 낼 수 있다. 결국, 아무리 새로운 기술이 등장한다 해도, 제조업과는 떼려야 뗄 수 없는 관계인 셈이다.인공지능은 제조업에서 매우 유용하게 쓰이고 있다. 그 중에서 공장 자동화에 큰 역할을 하고 있는 '머신 비전(Machine Vision)'에 대해서 이야기를 해보자. 머신 비전은 사물인식, 얼굴인식, 이미지 캡션, 문자 인식 등 여러 형태로 적용되며, 최근 들어 딥 러닝을 통해 더욱 강력해지고 있다. 특히, 비전을 활용해 불량품을 검출하는 'Defect Detection'은 제조업에서 큰 역할을 할 수 있다. 대다수의 공장에서 제품 생산 마지막 공정은 '품질보증(Quality Assurance, QA)'이다. QA를 통해서 생산한 제품 혹은 부품에 문제가 없는지 확인한 후, 구매자에게 좋은 품질의 제품만을 제공해야 하기 때문에 매우 중요하다.실제로 대량생산라인을 보유하고 있는 제조업 기반 기업은 QA에 막대한 비용을 소모하고 있다. 때문에 유심히 확인하지 않거나, 몇몇 샘플들만 체크하고, 심지어 QA를 생략하는 경우도 있다. 결국 피해는 고스란히 최종 구매자에게 이어진다. 예를 들어, 새로 장만한 스마트폰이나 자동차 부품에 흠집이 있는 경우, 최종 구매자가 겪어야 할 불편함은 작지 않다. 또한, 고객 충성도 하락까지 이어질 수 있어 기업은 사전에 방지해야 한다.불량품 검출이 이루어지는 프로세스를 간단하게 알아보자. 스켈터랩스의 정수익 책임 PM의 도움을 받아 이미지로 구성했다.< 불량품 검출 프로세스, 출처: 스켈터랩스 >먼저 부품 생산 과정 중 불량을 탐지하기 위해서는 광학 기기를 사용해 사진을 찍어야 한다. 그리고 촬영된 사진을 이용해 머신 비전으로 탐지하는 것이다. 하지만, 머신 비전이 적용되었다고 해서 바로 족집게처럼 불량품을 검출해낼 수 있는 것도 아니다. 이미 많은 이들이 알고 있지만, 딥 러닝은 수많은 데이터셋을 바탕으로 선행한 학습 전제가 필요하다. 결함으로 판명된 부품들에 대한 데이터를 수집하고, 학습해 '이 부품은 이런 형태의 손상이 있으니 불량이다'라고 판단하는 방식이다. 인식하고, 학습하고, 검출하는 단계를 계속해서 반복하며 기계가 점점 '똑똑해진다'라고 할 수 있다.이어서 스켈터랩스의 사례를 참고해보자. 내부에서 개발하고 있는 불량품 검출 서비스는 크게 세가지 부분으로 구성된다. 파란색 네모 안에 있는 이름은 가제다.< 스켈터랩스의 머신 비전 불량품 검출 서비스 >하나씩 살펴보면, 'Dulok'은 실제로 현장에서 촬영되는 이미지를 모니터링하거나, 이를 클라우드에 업로드하는 '모니터링 모듈'이며, 'Ewok'은 웹상으로 부품 정보에 대해 'curation', 'labeling', 추론 결과를 확인할 수 있도록 하는 '애플리케이션'이다. 마지막으로 'Gorax'는 '학습을 통해 부품의 결함을 검출하는 모델'이다. 이 부분은 실제 서비스에서 단순히 딥 러닝을 통한 추론 외에도 다른 피쳐들이 제공되어야 한다.기존에는 사람이 이미지 상에서 결함에 대한 정의를 하나하나 내리고, 결함의 특징을 수동으로 설정해야 했다. 때문에 반도체나 LCD처럼 표면 형태가 정형화되어 있는 분야에서만 머신 비전 기술을 활용할 수 있었다. 반대로 섬유나 천연가죽 등 표면 형태가 비정형화된 분야에서는 결함 특징 값을 수동으로 설정하기 어려워 육안검사에 의존해야만 했다.그러나 점차 '머신 비전' 기술이 발전하면서 적용되는 영역은 계속 늘어나고 있다. 이는 품질을 높이는 결과로 이어져, 결과적으로는 최종 소비자들이 혜택을 받는다. 이처럼 인공지능 기술은 향후 지속적으로 발전을 거듭해 제조업의 일자리를 뺏는 것이 아닌, 함께 공생하는 생태계를 구축하는데 도움될 것이라 생각한다.이호진, 스켈터랩스 마케팅 매니저조원규 전 구글코리아 R&D총괄 사장을 주축으로 구글, 삼성, 카이스트 AI 랩 출신들로 구성된 인공지능 기술 기업 스켈터랩스에서 마케팅을 담당하고 있다#스켈터랩스 #기업문화 #인사이트 #경험공유 #조직문화 #인공지능기업 #기술기업
조회수 2947

iOS 아키텍처 패턴(MVC, MVVM, VIPER)

Overview“글 한 번 써보실래요?” 입사하고 일주일이 지나 기술 블로그에 글을 써 보라는 제안을 받았습니다. 여러 고민 끝에, 아이폰 앱(이하 ‘iOS’) 주니어 개발자로서 프로젝트 경험과, 공부한 내용을 바탕으로 글을 쓰기로 했습니다. 적절한 짤이라고 생각하는 중iOS 개발자 사전 준비iOS 개발자의 길에 들어섰다면 이미 앱 개발과 개발 언어에 대해서는 알고 있을 겁니다. 개발 프로그램 Xcode와 프로그램을 지원하는 macOS 환경, 개발 언어 Swift 또는 Objective-C, iOS 앱 프로그래밍 등 iOS 앱을 개발하기 위해 필요한 내용까지도요. 우선 ‘iOS 주니어 개발자라면 꼭 알고 있어야 할 것’들을 아래 목록과 같이 정리했습니다. 글을 읽기 전, 목록 중에서 공부가 더 필요한 것이 있다면 꼭! 검색해보세요. Xcode, macOSApple Developer ProgramSwift or Objective-CCocoa TouchUIKitAuto Layout…iOS Architecture Patterns(아키텍처 패턴)“Viper 패턴 들어보셨어요?” Viper는 단순히 ‘독사’를 의미하는 줄 알았는데, MVC 패턴와 같이 디자인 패턴의 한 종류라는 건 입사하고 나서 알게 됐습니다. MVC와 SingleTon(싱글톤) 패턴은 익숙했지만 Viper 패턴은 생소했습니다. Viper 패턴을 3일 안에 분석하겠다는 저의 부끄러운 과거를 반성합니다... ㅜㅜ검색해보니 다양한 디자인 패턴을 찾을 수 있었습니다. iOS 개발자는 앱 프로젝트를 시작하기 전 또는 이미 진행되고 있는 프로젝트에서 개발을 시작한다면 우선 어떤 패턴으로 설계되어 있는지 파악해야 합니다. 그러므로 오늘은 iOS 개발에 주로 사용되는 패턴인 MVC, MVVM, VIPER를 간단하게 살펴보겠습니다.MVCMVC 패턴Model(모델), View(뷰), Controller(컨트롤러). Model에서는 애플리케이션에서 사용할 데이터들을 관리하고, View는 유저 인터페이스를 표현 및 관리합니다. Controller는 View와 Model의 다리 역할을 해 View의 입력을 Model이 반영하고, Model의 변화를 View에 갱신하는 역할을 합니다. 하지만, 애플의 MVC 패턴은 기존 MVC 패턴과 다릅니다. View와 Controller가 강하게 연결되어 있어 View Controller가 거의 모든 일을 합니다.1) 애플 MVC 패턴MVVMMVVM 패턴Model(모델), View(뷰), ViewModel(뷰모델). Controller를 빼고 ViewModel을 추가한 패턴입니다. 여기서 View Controller가 View가 되고, ViewModel이 중간 역할을 합니다. View와 ViewModel 사이에 Binding(바인딩-연결고리)가 있습니다. ViewModel은 Model에 변화를 주고, ViewModel을 업데이트하는데 이 바인딩으로 인해 View도 업데이트됩니다. ViewModel은 View에 대해 아무것도 모르기 때문에 테스트가 쉽고 바인딩으로 인해 코드 양이 많이 줄어듭니다.import Foundation // ViewModel var gameScore: Int? var gameScoreLabel: UILabel func updateGameScoreLabel() {   var text = ""   if let gameScore = gameScore, gameScore == 100 {       text = "Excellent!!"   } else if let gameScore = gameScore, gameScore >= 90 && gameScore < 100>       text = "Great Job!"   } else if let gameScore = gameScore, gameScore < 90>       text = "Not Bad~"   }   gameScoreLabel.text = text } // View Controller gameScoreLabel.text = viewModel.updateGameScoreLabel간단한 예를 들면, 게임 점수에 따라서 textView에 보여줄 내용을 담당하는 함수 등, View에서 변화가 일어나는 함수들이 View Controller에 정의되어 사용하는 경우가 많을 겁니다. 이런 함수들이 점점 많아지면 View Controller가 Massive, 많은 코드를 담게 됩니다. 그래서 이런 함수들을 ViewModel에 옮기고, 값들을 미리 세팅한 다음에 view controller에서 viewModel을 선언하고 viewModel의 함수를 불러오는 식으로 사용하면 됩니다. 매우 간단한 예제이기 때문에 대략 viewModel과 view controller에서 어떻게 사용하는지만 보시면 될 것 같습니다. 이 패턴은 주로 Reactive programming(ReactiveCocoa, RxSwift 등)을 할 때 많이 사용하는 패턴이어서 다음에 설명하겠습니다.VIPERVIPER 패턴View(뷰), Interactor(인터렉터), Presenter(프리젠터), Entities(엔티티), Router(라우터). MV(X) 패턴과 다른 패턴으로 MVC 패턴을 대체하기 위해 만들어진 패턴입니다. 먼저 Entity는 그저 모델 객체입니다. 단순하게 어떤 모델의 속성들만 있는, Dumb Model이라고 부를 수 있습니다. 이 모델 객체를 조작하는 것이 바로 Interactor입니다. 어떤 행동(behavior or use case)에 따라서 모델 객체를 조작하는 로직이 담겨 있습니다. 작업이 완료되어도 View에 아무런 영향 없이 오로지 데이터 작업만 합니다.Presenter는 데이터를 Interactor에서 가져오고, 언제 View에 보여줄지 결정합니다. View에 보여주기 전 내용을 준비하는 로직을 담당한다고 생각하면 됩니다. View는 Presenter에서 어떻게 보여줘야 할지 요청대로 디스플레이하고, 사용자의 입력을 받으면 다시 Presenter로 넘깁니다. Presenter는 View/ViewController, Interactor, Router와 상호작용합니다. Interactor로부터 조작된 데이터를 가져오고, 디스플레이하기 위해 데이터들을 준비한 다음 View/ViewController에 보냅니다.Router 또는 Wireframe은 화면 전환(navigation information)을 담당합니다. Presenter가 “언제” 화면을 전환해야 하는지 안다면, Router는 화면 전환을 “어떻게” 하는지 알고 있습니다. Router는 화면 전환 애니메이션을 구현하고, View Controller를 생성하여 Presenter와 연결합니다.항목내용ViewPresenter의 요청대로 디스플레이하고, 사용자 입력을 Presenter로 보내는 작업을 합니다.InteractorUse case에 따라서 Entity 모델 객체를 조작하는 로직을 담고 있습니다.PresenterInteractor로부터 데이터를 가져오고, View로 보내기 위해 데이터를 준비하여 “언제” View에 보여줄지를 결정합니다.Entity모델 객체. Dumb Model.Router(Wireframe)화면 전환(navigation information)을 담당하며, Presenter가 “언제” 화면 전환해야하는지를 안다면, Wireframe은 화면 전환을 “어떻게” 하는지를 알고 있습니다.하...지금까지 설명한 내용들은 막상 프로젝트 만들어 소스를 작성하려고 하면 막막해집니다. 역할이 잘 분할되어 있기에 앱의 기능을 하나 정하여 interactor, entity, presenter, view, router 만들고, 또 앱의 기능에 따라서 다시 interactor, entity,…. 고민을 많이 해야 해서 다시 MVC 패턴으로 돌아가고 싶은 마음이 생깁니다.크게 보면 Add Module와 List Module, 그리고 공통적인 모델(데이터)을 잘 분리한 앱 구조Conclusion도대체 우리는 왜 다양한 앱 디자인 패턴을 알아야 할까요? 그 이유는 바로 앱의 특성에 따라 적합한 설계를 가지고 작업해야 하기 때문입니다. 간단한 앱 프로젝트는 쉽게 개발하고 적용할 수 있는 MVC 패턴이 더 적합합니다. 반대로 MVVM 패턴이나 VIPER 패턴을 적용하면 점점 커지는 앱 프로젝트에 잘 대응할 수 있습니다. 또는 어떤 디자인 패턴이 적용된 앱 프로젝트에 참여하면, 그 디자인 패턴에 대해 알아야 앱 구조를 이해하고 기능을 추가하거나 수정할 수 있고, 작업하는 시간을 줄일 수 있을 겁니다.가장 좋은 패턴은 사람마다 차이가 있습니다. 패턴마다 장단점도 있습니다. 다만 어떤 패턴이든지 간에 구조화되고 정리된 코드는 쉽고, 직관적입니다. 이 글 하나만으로 앱 패턴을 완벽하게 마스터할 수는 없어도 패턴의 종류와 특징을 알게 되었다면 본전입니다. 다음 편도 기대해주세요! :-) 도움말 1) View Controller에서는 Controller가 View의 life cycle(라이프 사이클)에 관여하기 때문에 View와 Controller를 분리하기 어렵습니다. 개발자들 사이에서는 Massive View Controllers라고도 불립니다. 앱을 테스트할 때, Model은 따로 분리되어 테스트를 할 수 있어도 View와 Controller는 강하게 연결되어 있기 때문에 각각 테스트하기 어렵습니다. 참고문헌 iOS Architecture Patterns: Demystifying MVC, MVP, MVVM and VIPER글김주희 사원 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유 #iOS

기업문화 엿볼 때, 더팀스

로그인

/