스토리 홈

인터뷰

피드

뉴스

조회수 1367

[인공지능 in IT] 서로 다른 우리, 대화할 수 있을까?

설연휴 동안 그간 못 봤던 밀린 TV 프로그램들을 맘껏 즐기며 여유로운 시간을 보냈다. 그 중에서도 여러 분야의 전문가를 초빙해 특강을 해주는 tvN의 '어쩌다 어른'을 보기 시작했다. 몇 년 전 언어인문학을 주제로 한 조승연 작가님편을 보니 새삼 현재 대한민국이 처한 현실을 피부로 느끼게 되더라.< tvN>강연에서 가장 심도있게 다룬 부분은 대한민국 영어교육의 현실이다. 초등학교부터 영어 수업을 듣고, 심지어 말도 제대로 떼기 전인 유아기부터 영어를 주입시키는 것이 어느새 자연스러운 일이 되어버렸다. 하지만, 10년, 20년 이상 영어 교육을 받았는데도 막상 영어로 문서 작업을 하거나, 외국인이 길을 물어보면 식은땀을 흘리는 이유는 무엇일까? 어째서 한국에서는 영어를 제대로 하려는 노력보다, 영어를 아는 노력을 하고 있는 것일까?재미있는 사실은 우리만 영어를 배우려고 애먹는 것이 아니다. 미국인이 한국어를 배울 때에도 비슷한 현상을 겪는다. 강연 중 'FSI(The Foreign Service Institute)'에서 미국인들이 다른 나라 언어를 얼마나 공부해야 소통할 수 있는지에 대한 연구 자료를 공개했다. 언어별 Level 1부터 Level 5까지 다섯 가지 난이도로 구분 되어있고, 이에 따른 총 필요시간으로 구성되어 있는 연구에서, 한국어는 일본어, 중국어와 함께 소통하기 까지 총 2,200시간을 공부해야 하는 Level 5군에 속해 있었다.즉, 전세계 7,000여 개가 넘는 언어 중 한국어는 영어와 문장구조가 완전히 다르기 때문에, 24시간 내내 공부해도 90일 넘게 공부해야 한다는 것. 이렇듯 모국어가 아닌 다른 언어를 배우기 위해서는 어마어마한 시간과 노력이 필요하다. 만약, 단순히 언어를 알기 위해 배우는 것보다, 소통하기 위해 배운다면 흔히들 말하는 'ROI(Return on Investment)'를 더 높일 수 있자 않을까.출처: 동아일보소통을 위한 언어 학습은 비단 사람에게만 해당되는 것이 아니다. 기계와 사람의 소통 역시 요즘과 같은 인공지능 시대에서는 빼놓을 수 없는 부분이다. 몇 년 전부터 업계에서는 '챗봇(Chatbot)' 열풍이 불고있다. 챗봇은 대화(Chat)와 로봇(Robot) 두 단어를 합친 신조어로서, 각종 앱이나 웹을 기반으로 문자를 통해 사용자의 의도를 파악해 대화할 수 있는 인공지능 기계다. 여기에는 '자연어 처리(Natural Language Processing, NLP)', '자연어 이해(Natural Language Understanding, NLU)', '머신러닝(Machine Learning)' 등 수많은 기술이 접목되어 발전 중이다. 현재 챗봇은 나날이 진화하며, 텍스트를 텍스트로만 처리하는 것을 넘어, '음성으로 변환(Text-To-Speech, TTS)'시키거나, '음성을 텍스트로 변환(Speech-To-Text)'시키는 등 다양성에 있어 점점 넓은 범위에 적용되고 있는 추세다.< 출처: Understanding Natural Language Understanding, Bill MacCartney >글로벌 챗봇 시장은 매년 큰 폭으로 성장하고 있는 추세이며, 여러 사업 분야에 걸쳐 사용되고 있다. 북미의 시장조사기관 'Credence Research' 조사에 따르면, 글로벌 챗봇 시장은 2015년부터 2023년까지 연평균 35% 성장할 것으로 예상된다. IT솔루션 기업 'MindBowser'가 조사한 결과, 95%의 기업이 챗봇 활용성에 대해 긍정적인 반응을 보였으며, 고객응대(93%)부터, 마케팅(61%), 상품 주문(47%), 소셜 미디어(32%) 등 사업 분야에서 활용되는 용도 역시 다양한 것으로 밝혀졌다.챗봇은 어떠한 프로세스를 통해 실제로 작동하는지 살펴보기 위해서 사내 엔지니어의 도움을 받았다. 스켈터랩스에서 대화형 인공지능 프로젝트팀에 있는 정태형 엔지니어가 메신저를 통한 간단한 시범 사례를 스크린샷으로 찍어 보여주었다.< 인공지능 메신저 사례, 출처: 스켈터랩스 >여행지를 자동으로 추천해주는 엡에 적용할 수 있는 챗봇과의 대화다. 사용자가 "여행을 가고 싶다"고 말하자 자동으로 '카이트봇'이 반응하고, 여행 기간과 테마를 물어본다. 여기서 사용자가 "여행 기간"을 말하자 챗봇은 자동으로 '3월'과 '7일'을 인식, 이전 질문에서 대답하지 않은 테마에 대해 질문한다. 이렇게 사용자와 챗봇 사이에서 대화를 자연스럽게 주고 받을 수 있는 것은 대화의 구성 요소 중 '의도(Intent)', '개체(Entity)', '맥락(Context)'이 중요한 역할을 한다. 이를 간단히 살펴보도록 하자.의도(Intent)는 사용자가 어떠한 의도로 대화를 하는지를 의미한다. 위 스크린샷의 경우, 여행을 가는 것'이 의도라 할 수 있다. 예를 들어, "여행 가고 싶어"가 아닌 "여행 가볼까?"로 입력하더라도 - 미리 여행을 가는 것에 대한 자연어 기반 패턴이 'Intent Classifier'에 입력되어 있는 상태라면 - 이를 '사용자가 여행을 가고 싶구나'라는 의도로 이해할 수 있는 것이다.개체(Entity)는 사용자의 의도 중에서 실체가 될 수 있는 변수를 말한다. 개체는 사용자가 입력한 문장에서 특정한 변수가 달라질 때 사용된다. 위 스크린샷의 경우, '3월 3일', '해변', '일주일' 등과 같이 주로 명사 형태로 구성된 문장에 들어가는 구성 요소를 말한다.문맥(Context)은 이전 대화를 자연스럽게 이어갈 수 있도록 처리할 수 있는 기능이다. 예를 들어, 사용자가 챗봇에게 "가수 빅뱅의 프로필을 검색해달라"고 요청했다. 그리고 빅뱅의 노래를 듣기 위해 "거짓말 틀어 줘"라고 명령하면, 기존에 빅뱅이라는 가수에 대해 대화하고 있던 문맥을 인식해 God의 거짓말이 아닌 빅뱅의 거짓말을 재생하는 것이다.이 외에도 챗봇에는 '말뭉치(Utterance)', '시나리오(Scenario)', '슬롯채우기(Slot Filling)' 등 다양한 구성요소를 통해 대화를 이어나갈 수 있다. 물론, 아직 100% 인간과 대화하는 기술까지 이르지는 못했다. 하지만, 우문현답하지 않고 사용자 의도를 정확하게 파악하는 수준에 이르러 생활에 편의성을 제공하고 있다.한국어의 경우 언어의 난이도 때문에 국내 기업은 물론 많은 글로벌 IT 기업도 아직 완벽한 수준에 도달하지 못했다. '잘 한다'라는 말만 하더라도 '훌륭하게 하다', '만족할 만하다', '자주 하다' 등의 긍정적인 표현이 있는가 하면, '잘 하는 짓이다' 등의 부정적인 표현인 경우도 흔하기 때문이다. 결국 챗봇도 기계이기 때문에, 여러가지 문장과 상황을 학습시켜 한국어 성능을 향상시켜야만 한다.다시 '어쩌다 어른'으로 돌아가보자. 강연을 마무리할 즈음 조승연 작가는 이렇게 말한다."영어도 결국 언어의 한 종류, 영어를 쓰는 사람들도 우리와 같은 사람, 우리처럼 희로애락을 느끼는 인간입니다. 기계와 얘기하기 위해 법칙에 맞춰 말해야 하는 것이 아니라 그 사람과 감정을 통하게 해주는 어떤 도구입니다."여전히 우리는 챗봇이라는 기계와 소통한다기 보다, 일방적으로 질문을 던지고, 챗봇은 미리 입력되어 있는 규칙 안에서만 답한다. 학습을 통해 수많은 데이터가 축적된다 하더라도, 아직까지 언어를 통해 전달되는 인간의 감정을 완벽히 이해하기에는 부족한 것이 사실이다. 과연 기계가 '법칙'에 맞춰서 말해야 하는 것 이상을 넘어서는 순간이 올까? 우리는 그 순간을 찾아 지금도 노력하고 있는지 모른다.이호진, 스켈터랩스 마케팅 매니저조원규 전 구글코리아 R&D총괄 사장을 주축으로 구글, 삼성, 카이스트 AI 랩 출신들로 구성된 인공지능 기술 기업 스켈터랩스에서 마케팅을 담당하고 있다#스켈터랩스 #기업문화 #인사이트 #경험공유 #조직문화 #인공지능기업 #기술기업
조회수 1725

핀다(Finda)의 '따끈따끈한' 신입개발자 남은우:

핀다(Finda) 개발자 남은우님의 스타트업 생생LIFE 입니다원문은 링크를 통해 확인하실 수 있습니다!안녕하세요! 금융상품 추천서비스 '핀다'에서 프론트 엔드 웹 개발자로 근무하고 있는 남은우라고 합니다~ ^^저는 입사한지 6개월차가 되는 따끈따끈한 신입 개발자입니다. 올해 처음 웹 개발을 배우기 시작해서 인턴으로 들어오기까지 많은 것을 경험했는데요~ 제 이야기를 통해서 스타트업에서 일하기를 희망하시는 분들에게 조금이나마 도움이 되었으면 좋겠습니다. :)<핀다 개발자 남은우, 출처 : 핀다>스타트업에 지원하게 된 이유대학교 4학년 마지막 학기, 저는 아직 졸업하고 싶지 않은 철 없던 마음에... 휴학 할 명분(?)을 만들기 위해서 여기 저기 대외 활동을 찾고 있었어요. 그러던 중 우연히 지원한 소프트웨어 개발자 양성 과정에 운 좋게도 덜컥!! 합격해 버렸습니다. 6개월간 진행된 팀 프로젝트를 위해 배운 웹 개발에 흥미가 생겨서 본격적으로 개발 공부를 시작했는데요. 시간이 지날수록 개발 능력은 조금씩 늘어갔지만, 불안감도 나날이 커져갔습니다. 그 이유는 바로 '실무 경험'이 없었기 때문이었죠.제가 배운 개발 능력을 발휘할 수 있는 곳을 찾던 중에 스타트업 인턴즈를 만나게 되었습니다. 스턴에서 진행한 4주간의 코칭은 사회 초년생인 저에게 어찌보면 '치트키' 같은 시간이었어요. 자신에게 맞는 스타트업을 찾기 위해 3가지 핵심가치를 설정하거나, 면접 필수 요소, 기업분석 방법까지!!! 코치님의 여러가지 조언과 꿀팁들 덕분에 저에게 꼭 맞는 회사를 선택할 수 있었던 것 같아요.스타트업에서의 경험입사 첫째 날, 인턴임에도 불구하고 서비스 개발에 바로 투입(?) 되었습니다. 처음 제가 맡은 업무는 코드 리팩토링이었는데요. 이미 작성되었던 코드를 새로운 아키텍쳐로 변경하면서 구조에 대한 이해도를 높일 수 있었어요. 이 경험을 바탕으로 이후에 새롭게 추가되는 카테고리 개발이나 다른 채널들의 신규 소개 페이지 등을 빠르게 만들 수 있게 되었습니다.가장 좋았던 것은 커뮤니케이션이었는데요. 기획, 디자인, 개발의 유기적인 소통이 중요했기 때문에 개발자임에도 기획 미팅에 들어가거나, 디자인에 대한 의견을 낼 때가 많았습니다!! 팀원들 또한 열린 마음으로 저의 의견을 적극적으로 받아들여 주셨기 때문에, 새로운 아이디어를 낼 때가 많았던 것 같아요. 그리고 개발뿐만 아니라 여러 경험을 통해 서비스가 완성되는 과정을 지켜보는 것 또한 큰 자산이라고 생각했어요.<핀다 개발자 남은우, 출처 : 핀다>스타트업에 입사를 희망하는 분들에게스타트업은 대부분 바로 업무에 투입가능한 사람을 원하는 경우가 많아요. 따라서 지원하기 위해 어느 정도 준비가 필요하겠죠? 입사 후에 모든 일을 척척 수행할 수 있는 사람이면 좋겠지만, 전문적이지는 않더라도 자신이 지원하게 된 회사가 어떤 서비스를 제공하는지 파악하거나, 해당 서비스를 사용해보는 것이 좋아요.요새 드라마나 영화에 종종 스타트업 이야기들이 많이 나오는 것 같아요. 하지만 매스컴에 비춰지는 것이 자유분방하고 즐거운 모습뿐인 것 같아 조금 아쉬운 마음이 들기도 합니다. 회사에 따라 다르겠지만, 스타트업 특성상 조금 더 빠르게 달려야 할 때가 많거든요. 대신 남들보다 조금 더 빠르게 성장할 수 있다는 것!!! 입사를 희망하시는 여러분도 자신과 맞는 회사를 찾고, 꼭 특급 성장의 기회를 잡으셨으면 좋겠습니다.#핀다 #입사후기 #팀원소개 #팀원인터뷰 #팀원자랑 #기업문화 #조직문화
조회수 1203

CodeStar + Lambda + SAM으로 테스트 환경 구축하기

들어가기 전: 실제로 프로젝트와 팀원들과의 작업 환경을 구축한 경험을 바탕으로 작성했습니다. 한마디로 실화. Overview소스를 수정할 때마다 지속적인 테스트를 하기 위해 AWS lambda 로컬 테스트 환경, SAM을 결합해서 환경을 구축했습니다. 이번 글에서는 팀원을 추가하고 CodeCommit을 리포지토리로 사용하는 것도 소개하겠습니다. 예상 구성도테스트 환경 구축, 도저언!1. 팀원 추가하기 IAM 서비스를 이용해서 프로젝트를 같이 사용할 유저를 추가합니다. IAM에 유저를 추가하면 AWS 콘솔을 같이 사용할 수 있습니다. 사용자 추가를 클릭해 유저를 추가합니다. 팀원마다 한 개의 계정을 추가해야 합니다. 사용자 세부 정보 설정 > 엑서스 유형에서 ‘프로그램 방식 엑서스’와 ‘AWS Managrment Console 엑서스’를 체크합니다. 여기에서는 개발2팀 팀원인 강원우 과장의 계정을 생성했습니다.1) 비번은 귀찮으니 미리 세팅해둡시다. 유저 계정은 그룹을 생성해서 관리하면 편합니다. 그룹을 사용하면 보다 편리하게 계정 권한을 제어할 수 있기 때문입니다. 이번 예제에서는 그룹 이름을 codeStarGroup으로 만들었습니다. AWSCodeStarFullAcess를 정책으로 설정하고 ‘그룹생성’을 클릭해 그룹을 추가합니다. 2) codeStarGroup에 체크한 후, ‘다음: 검토’를 클릭해 진행합니다.‘사용자 만들기’를 클릭해 생성을 마무리합니다.계정 추가를 완료했습니다.사용자 이름(위의 예시에서는 kanggw)을 클릭하고, 뒤이어 ‘보안자격 증명’ 탭을 클릭합니다.콘솔 로그인 링크를 공유합시다. 링크를 입력하고 들어가면 그룹 로그인이 활성화가 되어있다는 걸 볼 수 있습니다.2. CodeStar 설정하기 프로젝트 인원을 무사히 추가했습니다. 이제 프로젝트를 만들어 봅시다. CodeStar 프로젝트 세팅 방법은 R&D본부 윤석호 이사님이 쓴 ‘애플리케이션 개발부터 배포까지, AWS CodeStar’를 참고해주세요.새 프로젝트를 생성합니다.python AWS Lambda를 선택합니다.프로젝트 이름은 ‘admin-lambda-API’로 입력하겠습니다. 그 후에 ‘다음’을 클릭합니다.‘프로젝트 생성’을 클릭합니다.우리는 Git을 이용해 로컬에서 직접 관리할 것이므로 ‘명령행 도구’를 선택한 후, ‘건너뛰기’를 클릭합니다.3분 만에 프로젝트가 생성되었습니다. 참 쉽죠?3. 프로젝트에 팀원 추가하기프로젝트를 같이 하려면 팀원을 추가해야겠죠. 팀원 추가는 codeStar 대시보드 좌측의 ‘팀’ 탭을 클릭하면 됩니다.‘팀원 추가’ 클릭IAM에서 등록한 팀원의 정보를 불러옵니다. ‘추가’를 클릭해 팀원을 추가합니다. 여기에서 중요한 사실 하나! 프로젝트의 소유자로 지정해야 소스 접근 및 코드 변경이 가능합니다.4. 코드 체크 아웃앞서 설명한 것처럼 직접 Git으로 소스를 받아야 하기 때문에 codeCommit으로 이동합니다. codeStar 대시보드 왼쪽 ‘코드’ 탭을 클릭하면 코드 내역들을 확인할 수 있습니다.‘URL 복제 > HTTPS’를 클릭해 경로를 복사합니다. 소스를 클론하기 전에 계정에 깃허용을 먼저 해주세요. IAM 돌아와서는 계정 설정을 변경해야 합니다.사용자 > kangww > 보안 자격 증명 탭 클릭 > HTTPS Git 자격 증명 > 생성Git에서 사용할 ID와 비밀번호를 받았습니다. 해당 정보를 팀원에게 전달합니다. 이제 workspace로 이동해 체크아웃을 시작합니다.git clone [복사한 경로] [id 입력] [pw 입력] clone이 완료 되었습니다. 이제 기본 프로젝트가 들어있기 때문에 바로 실행할 수 있습니다. 미리 설치된 SAM으로 실행해보겠습니다.이제 해당 경로에 이동해 SAM을 돌려서 정상적으로 구동되는지 확인해봅시다. (SAM설치 방식은 부록에서 소개합니다.) sam local start-api -p 3333 성공적으로 SAM이 구동되었습니다. (짝짝) http://localhost:3333 으로 접근해 결과를 확인할 수 있습니다. 이제 로컬에서 작업을 진행하면서 바로 바로 확인이 가능해졌습니다. 만약 동료와 함께 개발한다면 아래처럼 구동해야 자신의 IP에 접근할 수 있습니다.sam local start-api -p 3333 -host [자신의아이피] 글을 마치며CodeStar의 관리와 배포 기능은 강력합니다. 많은 부분을 알아서 해주니 고마울 뿐입니다.3) 이제 Lambda의 local 테스트 환경인 SAM을 이용해서 배포 전 과정까지 간편하게 테스트를 해보세요. 배포의 복잡함을 codeStar에서 해결하고 테스트를 하거나 개발을 할 때는 SAM을 이용해 효율적으로 업무를 진행합시다.글 쓰면서 발견한 다섯 가지1) codeDeploy > executeChangeSet 에 구동될 때 cloundFormation 이 자동 세팅 됩니다. 엄청 편합니다. API 배포가 진행되면 lambda에서 바로 수정하는 게 편합니다.2) codeCommit은 https 보다 ssh방식을 권장하며, https방식으로 하다가 꼬이면 여기를 클릭해 해결하세요.3) codeStar는 다음과 같은 추가 구성을 자동 세팅합니다.codeStar 용 S3 버킷codePipeLine용 S3 버킷cloundFormation 세팅lambda 세팅4) IDE를 cloud9을 사용하면 EC2 및 EBS가 생성되니 주의하세요. 그리고 생각보다 느립니다.5) 로컬에서 Git push를 하면 약 5분 정도 뒤에 최종적으로 배포됩니다.부록1)SAM을 설치하기 전, 여기를 클릭해 docker를 미리 설치하세요.2)SAM 설치 안내는 여기를 클릭하세요. ( npm install -g aws-sam-local )참고1)강원우 과장은 귀여운 두 달팽이, 이토와 준지의 주인이기도 하다. 2)AWSCodeStarFullAcess는 codestar 접근에 대한 권한을 부여한다.3)자동 배포까지 2~5분 정도 걸리는 게 어렵게 느껴질 수 있다.글천보성 팀장 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 1046

만땅에서 스푼까지 함께 달려온 찰스를 소개합니다

스푼을 만드는 사람들 여덟 번째 이야기마이쿤의 초창기 멤버 중 한 명인 'Charles' 를 인터뷰해보았다.그래서, 영어 유치원은 보내셨나요?https://brunch.co.kr/@mirr5510/17내가(Sunny) 처음 마이쿤에 입사하게 된 계기는 바로 Neil(대표)의 브런치 글과 마이쿤 관련 인터넷 기사를 읽고 나서였다. 많은 글 둘 중에 가장 궁금하고 특이하다고 생각했던 글이 바로 '영어 유치원' 보내자 였다.영어 유치원 보내자? 무슨 말이지? 하고 클릭해서 읽어보았다. 스푼 라디오라는 서비스 전 '만땅'이라는 배터리 공유 서비스를 시작할 때 첫 팀 빌딩에 관한 이야기였다. 닐의 주변 지인, 학교 후배들에게 함께 서비스를 만들자고 제안했을 때 유부남 팀원들에게 이렇게 말씀하셨다고 한다."우리, 아이들 영어 유치원 보내자"즉, 그만큼 잘하자. 우리 같이해서 성공하자라는 의미로 이렇게 말씀을 하신 것 같다. 그래서 찰스를 인터뷰할 때 가장 먼저 물어본 질문이었다. 그래서 아이들 영어 유치원은 보내셨는지 말이다.찰스 특징: 모자 좋아함"하하하.. 이미 저희 아이들은 많이 커서 유치원은 벌써 졸업했어요. 이제 테드랑 빅터의 차례가 아닐까 싶네요"찰스가 가장 좋아하는 맥주 'Charles' 당신이 궁금합니다.Q. 본인을 한 마디로 표현한다면?'동네형 또는 오빠' 저는 어색한 걸 싫어하고, 친화력이 좋은 편이기도 하고요. 사람들과의 편한 관계를 좋아해요. 그래서 먼저 보통 말을 먼저 잘 거는 편이에요"Q. 찰스도 혹시 딸 바보세요?"네, 저는 딸 바보예요. 아빠들은 딸 바보가 된다는 건 사실인가 봐요. 딸은 일단 아들과는 정말 달라요. 되게 예쁘고요.. 되게 애교가 많고요..(이때 눈이 반짝반짝하셨습니다) 아들은 보통 엄마를 찾던데, 딸은 항상 아빠를 찾더라고요. 아! 그리고 자다가도 아빠 들어오는 소리 들리면 나와서 뽀뽀해주고 다시 자러 가요. 6살인데 아빠한테 잔소리도 하고요. 마지막으로 하나만 더 자랑해도 돼요? 오늘 5일 만에 딸 얼굴을 봤는데 (안 자고 있을 때) 아빠가 엄~청 보고 싶었다면서 일주일치 뽀뽀를 엄청 많이 해줬답니다.. 이래서 다들 딸 바보가 되나 봐요."Q. 밀가루를 정말 좋아하신다고 들었습니다 feat. 코젤 다크"제 생각에 저는 탄수화물 중독자인 것 같습니다. 탄수화물을 정말 좋아해요. 특히나 칼국수를 정말 좋아하는데요. 맑은 거 말고 찐한 국물의 칼국수 있잖아요. 그거 너무 맛있어요. 제가 추천하고 싶은 칼국수집은, 논현동 영동시장에 있는 이름이 기억이 안 나는데.. 거기 진짜 칼국수 진짜 맛있습니다."P.S: 테드가 옆에서 조용히 슬랙으로 보내주셨습니다. 바로 이 칼국수 집이라네요. '손국시' https://m.blog.naver.com/PostView.nhn?blogId=rldudal0070&logNo=220165610372&proxyReferer=https://www.google.com/당신의 회사생활이 궁금합니다Q. 마이쿤의 초장기 멤버가 되신 계기를 더 알고 싶어요"저는 마이쿤에 입사하게 된 계기가 제 인생에서 가장 큰 결정이었어요. 저는 이 전 회사에서 9년 4개월 정도 근무를 했었어요. 그러던 어느 날 Neil이 회사에 놀러 오라고 하더라고요? 그래서 놀러 갔더니 보니까 이미 Yong 도 함께 일하고 있었고, 갑자기 닐이 사업 기획서를 보여주는 거예요. 이런저런 이야기 함께 나누다가 함께 일을 하자고 제안을 하더라고요. 정말 고민 많았어요. 마침 그때 제가 이직을 생각할 때였거든요. 그렇게 고민하고 와이프와 함께 의논을 했는데 고맙게도 와이프가 저를 믿어주고 응원해주었어요. 그리고 이런 생각을 했어요. "어차피 이직할 거라면 한 번 밑바닥에서 도전해보자!" 그리고 제 손으로 서비스를 함께 만들 수 있다는 것이 가장 큰 메리트이었고요. 드디어 내가 원하는 일을 하게 되었구나 생각했었죠. 무엇보다 서비스가 잘되면 우리 아이들에게 더 나은 미래와 경험을 줄 수 있다고 믿었고요. 무엇보다 잘 되지 않아도 살아가면서 나에게 정말 좋은 경험으로 남아 미래에 발판이 될 것이라고 생각했어요. 그렇게 저는 마이쿤의 초창기 멤버가 되었어요. 무엇보다 서비스에 대한 아이디어와 매력도가 높다고 느꼈고, 닐이 "영어 유치원 보내자!"라는 말에 혹했죠"Q. 첫 서비스를 실패했을 때 떠나지 않고 남았던 이유는?"제가 처음에 입사를 하자마자 와이프가 둘째를 임신한 걸 알게 되었어요. 근데 정말 너무 바빠서 집에도 못 들어가고 일을 했었어요. 가장으로서 남편으로서도 잘하고 싶었고, 일도 잘하고 싶었는데 마음처럼 쉽지가 않더라고요. 경제적으로 힘든 부분도 있었지만, 저는 이대로 이 팀이 헤어지기엔 너무나도 아쉬워서 남는 선택을 했어요. 저희 정말 열심히 했거든요. 진짜 정말 열심히 했는데 이대로 서비스가 잘 되지 않았다는 게 아쉬웠고, 이 일로 이 팀이 해체되는 게 너무 싫었어요. 그때 우리는 모두 정말 열심히 했지만 잘하진 못했었어요. 어떻게 가야 하는지 방향도 몰랐어요. 그래서 더욱 아쉬웠죠. 팀이 해체된다 할지 언정 후회 없이 헤어지고 싶었어요. 근데 저뿐만 아니라, 모든 사람들이 동의를 했어요. 우리 이번엔 열심히 하지 말고 '잘' 하자라고. 그리고 저는 외벌이에 유부남이라 팀원들이 저를 많이 배려해줬었죠. 와이프에게 가장 고마운 점이 그때 와이프가 그랬어요. "떠날 때 떠나더라도 후회 없이 해"이 말이 정말 큰 힘이 됐던 것 같아요. 와이프에게 많이 미안하고 고맙습니다."Q. 6년 동안 함께 해올 수 있었던 원동력은?"저는 정말로 솔직하게 여태 마이쿤에서 다른 곳으로 이직을 해야겠다는 생각을 해본 적이 없어요. 첫 번째 서비스가 망하고도 자발적으로 남은 이유도 이 팀과 함께 후회 없이 가고 싶다는 마음 때문이었어요. 저는 아직도 제가 성장하고 많이 배우고 있고, 배워야 한다고 생각하거든요. 어떠한 사람들과 일하느냐가 정말 중요한 것 같아요. 무엇보다 함께 시작하여 함께 실패하고 또다시 함께 일어났다는 점과 성장했다는 점이 기쁘고 뿌듯하고 더 큰 책임감을 느끼게 해 주거든요. 하지만 언젠가 제가 회사에 도움이 되지 않는 날이 온다면 그때는 스스로 떠날 생각입니다 (웃음)"Q. 리더로서의 삶은 어떤가요?"팀에 동료가 많아지게 되고 각각 다른 성격의 동료들이 생겨났어요. 각자 다들 일을 열심하 하고 잘하지만 팀으로서 하나가 되어 한 마음으로 커 나가는 건 다른 문제라고 생각해요. 각자의 개성을 살릴 수 있는 방법이 있을까? 내가 어떻게 하면 좋은 리더가 되어 후배들을 이끌어 줄 수 있을까? 하고 고민도 많이 해보고, 함께 이야기도 해보기도 하고요. 진심을 담아서 늘 말을 해요. 제 진심이 닿아야 팀원들도 저를 더 잘 따라 줄 테니까요. 면담을 통해서 불편한 것들을 해소해주려고도 노력하고 무엇보다 저도 그 들에게 많이 배우고 있어요. 아무리 신입이라도 해도 제가 생각하지 못한 부분을 배울 수 있거든요. 각자 살아온 환경과 경험이 다르고 본인이 잘하는 것들은 다 제 각각 다르니까요. 그래서 많이 노력하고 배우려는 리더가 되려고 노력 중입니다."Q. 새로운 서비스가 성장하면서 변한 게 있다면?Sunny 曰: "지난 만 5년 동안 마이쿤의 실패 그리고 재 도전 및 성장 과정을 모두 봐오셨잖아요. 뭐가 가장 많이 달라졌을까요? 정말 많은 것들이 변했겠지만요."Charles 曰: "저는 일단 스푼이라는 서비스가 성장하면서 좋아진 점이 정말 많아진 것 같아요. 그중 가장 큰 건 회사에 점점 더 전문적이고 실력 있는 분들이 입사하셨다는 겁니다. 물론 분위기는 예전하고 같을 수 없겠지요. 그때와 지금의 인원 차가 크니까요.분위기나 문화를 그때가 똑같이 유지를 한다는 건 불가능하다고 생각해요. 서비스의 규모에 맞게 함께 성장해 나가야 하니까요. 다양한 사람과 다양한 시선으로 보고 느껴야 회사 서비스가 더 성장할 수 있거든요. 그리고 서비스가 성장해야 우리 모두에게 좋은 일인 것이고요. 새로 입사하신 분들은 굉장히 능력자가 많으세요. 그분들에게 많이 배우려고 하고 있고 있고요"찰스가 좋아하는 진라면 + 파송송 + 계란탁 당신의 사생활이 궁금합니다Q. 좋은 아빠란 어떤 아빠라고 생각하세요?"내가 생각하는 좋은 아빠란?처음에는 애들과 재미있게 잘 놀아주면 그것만으로도 좋은 아빠라고 생각을 했었어요. 알고 보니 그것만으로는 부족하더라고요. 잘 놀아주는 것은 기본이고, 아이의 시선에서 세상을 바라봐 주고, 아이와 눈높이를 맞춰 주는 자세를 가져야 하고 제일 중요한 것은 아이의 마음을 공감해줘야 한다고 생각합니다. 이렇게 해야 정말 좋은 아빠가 아닐까?라고 생각합니다. 잘 놀아주는 것은 누구보다 잘한다고 생각합니다만 아직까지도 아이의 마음을 공감해주는 부분에서는  많이 부족합니다. 저도 모르게 아이를 혼내고, 반성하고 이 패턴이 반복이에요. 한때는 뱃속에 있을 때는 건강하기만을 원했는데, 막상 세상에 나와보니 어쩔 수 없나 봅니다."Q. 자녀분들이 개발자를 꿈꾼다면 추천하시나요?“개발자는 굉장히 매력적인 직업입니다.누구나 개발자가 될 수 있고, 즐길 수 있는 지금의 문화라면 저는 아이들에게 개발자가 되는 것을 추천해주고 싶어요. 성취감이라는 걸 얻을 수 있는 직업이기에, 그런 걸 느껴보게 해주고 싶기도 하고요.나의 재능으로 나를 포함한 누군가의 삶이 달라질 수 있는 경험은 좋은 경험이라고 생각해요. 무엇보다 개발자는 스스로 만들고 싶어 하는 욕구가 있는데요. 내가 개발한 서비스나 상품을 누군가가 사용하고 좋은 피드백을 준다면 정말 보람찬 일이거든요. 물론 부정적인 피드백을 받으면 상실감을 느낄 수도 있지만, 그 과정에서 또 성장해나갈 수 있기 때문에!”Q. 나중에 개발해 보고 싶은 서비스가 있나요?"있어요. 라면 서비스요. 저는 라면을 정말 좋아하거든요, 라면 중에서도 진라면을 가장 좋아하는데 진라면에 파 넣고 마늘 넣고 콩나물 넣고! 끓여먹으면 전 일주일 내내 먹을 수 있습니다. 그래서 '우리 함께라면'이라는 라면 서비스를 해보고 싶어요. 라면에 특화된 서비스죠. 제가 그래서 예전에 회사에서 사람들 한 명 한 명한테 혹시 라면 일주일 내내 먹을 수 있냐고 물어보기도 했었어요."Q. 만약 개발을 하지 않았더라면 지금 뭐하시고 계셨을 거 같으세요?"글쎄요. 저는 원래 사실 개발자가 되려던 마음은 없었어요. 군대 제대하고 우연히 한 번 해볼까? 했는데 시작하게 되어 업이 되었네요. 저는 아마 개발자가 아니라면 지금 공무원? 하지 않았을까 싶어요. 원래 저의 옛날 성향은 뭔가 모나지 않고, 평범한 사람이었거든요. 이렇게 큰 도전을 하기 전까지는요"당신이 마이쿤에서 우리와 함께 일해야 하는 이유는요저희는 정말 많은 실패와 역경을 거쳐왔고, 쓰러질 때마다 '함께' 일어났습니다. 이제서야말로 정말 본격적으로 성장하기 위해 달려야 하는 시점이에요.  해야 할 것도 정말 많고요. 회사와 서비스가 성장하는 경험을 할 수 있다는 것은 어디서 돈 주고도 못할 경험이라고 생각합니다. 지금 이 기회에 저희와 같은 배를 타신다면, 개개인이 노력한 만큼 서비스 성장에 기여하실 수 있고 본인 스스로도 성장하는데 많은 도움이 될 것이라고 생각해요. 이미 성장한 곳에서 경험을 하는 것도 분명 가치 있는 일이지만, 나 스스로가 성장에 기여할 수 있는 회사의 구성원이 될 수 있는 건 흔한 일이 아니라고 생각합니다.서비스 플랫폼 팀원들이 Charles를 한마디로 표현한다면?Kyu 曰:  '동네형' - 사실은 동네 아저씨에 더 가깝지만 마치 동네 형인 듯 다가와주는 사람.Sam 曰:  '장군님' - 어디서 자꾸 전리품(티셔츠, 스티커 등 )을 가지고 오신다.P.S 저희 어머님께서 NewRelic 티셔츠 편하다고 너무 좋아하십니다.Mark 曰:  '언니' - 가끔 삐지시는 거 같지만 언제나 잘 챙겨준다.
조회수 4745

안드로이드 앱의 Persistent data를 제대로 암호화해 보자! (2/2)

들어가기1부에서는, KeyStore 를 사용해 Shared Preferences 를 암호화 하는 법에 대해 알아봤습니다. 그리고 이 글에서는 Room을 사용한 Database 를 암호화 하는 방법에 대해 설명합니다.2018년 현재, 안드로이드 자체에서 데이터베이스를 암호화하는 기능을 제공해 주진 않습니다. 따라서 오픈 소스 프로젝트인 SQLCipher, SafeRoom 의 사용법 위주로 설명할 예정입니다. 또한 KeyStore 에 대칭키를 생성하는 기능은 API Level 23 이후에서만 가능하며, SQLCipher 가 Android KeyStore 를 지원하지 않고 있습니다.이로 인해 1부에서 소개한 키 암호화 메커니즘으로 보호한 별도의 키를 디스크 어딘가에 저장해 두고, 필요할 때만 복호화 해서 쓴 다음 복호화된 내용을 지우는 방식으로 구현해야 합니다. 하지만 이런 방식으로 사용하는 키는 메모리에 순간적으로 남기 때문에 좋은 공격 표면(Attack surface) 이 됩니다. 그 이유도 함께 다뤄 보겠습니다.SqlCipher team 에서 하루라도 빨리 현재의 char[] 형식의 passphrase 를 입력받는 대신, JCA 를 사용해 암호화하는 데이터베이스를 구현하길 기대해 봅시다.SqlCipher1부에서 보여드렸다시피 internal storage 에 저장한 데이터는 결코 안전하지 않습니다. 파일 DB 인 Sqlite 데이터는 포맷을 모르면 어차피 볼 수 없을테니 조금 다르지 않을까요? 그렇지 않다는 것을 다음 예에서 보여드리겠습니다. 루팅한 디바이스에서 adb pull명령으로 sqlite3 데이터베이스를 추출 후 내용을 열어보면 다음과 같습니다.$ hexdump -vC secure_database.sqlite3 00000000  53 51 4c 69 74 65 20 66  6f 72 6d 61 74 20 33 00  |SQLite format 3.| 00000010  10 00 02 02 00 40 20 20  00 00 00 02 00 00 00 04  |.....@  ........| 00000020  00 00 00 00 00 00 00 00  00 00 00 04 00 00 00 04  |................| 00000030  00 00 00 00 00 00 00 04  00 00 00 01 00 00 00 00  |................| 00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................| 00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 02  |................| 00000060  00 2e 01 5a 0d 0f 95 00  02 0e a9 00 0e a9 0f c9  |...Z............| 00000070  0e 6f 0e 6f 00 00 00 00  00 00 00 00 00 00 00 00  |.o.o............| ... 00000d30  00 00 00 00 00 82 37 03  07 17 57 57 01 83 4d 74  |......7...WW..Mt| 00000d40  61 62 6c 65 73 71 6c 69  74 65 62 72 6f 77 73 65  |ablesqlitebrowse| 00000d50  72 5f 72 65 6e 61 6d 65  5f 63 6f 6c 75 6d 6e 5f  |r_rename_column_| 00000d60  6e 65 77 5f 74 61 62 6c  65 73 71 6c 69 74 65 62  |new_tablesqliteb| 00000d70  72 6f 77 73 65 72 5f 72  65 6e 61 6d 65 5f 63 6f  |rowser_rename_co| 00000d80  6c 75 6d 6e 5f 6e 65 77  5f 74 61 62 6c 65 05 43  |lumn_new_table.C| 00000d90  52 45 41 54 45 20 54 41  42 4c 45 20 60 73 71 6c  |REATE TABLE `sql| 00000da0  69 74 65 62 72 6f 77 73  65 72 5f 72 65 6e 61 6d  |itebrowser_renam| 00000db0  65 5f 63 6f 6c 75 6d 6e  5f 6e 65 77 5f 74 61 62  |e_column_new_tab| 00000dc0  6c 65 60 20 00 00 00 00  00 00 00 00 00 00 00 09  |le` ............| ... [리스트 1] Internal storage 에 저장된 SQLite3 database 를 dump 한 결과.역시 기대했던대로 데이터가 하나도 암호화되어 있지 않은 것을 확인할 수 있습니다. 그렇다면 가장 간단한 방법은 SQLiteDatabase클래스를 확장하는 일일 텐데요, 문제는 이 클래스가 final 로 상속 불가능하게 되어 있단 점입니다. 이 때문에 암호화된 SQLiteDatabase 구현체는 이 클래스 및 이 클래스에 강하게 결합되어 있는 SQLiteOpenHelper 를 온전히 쓸 수 없다는 문제가 있습니다. 즉, 바닥부터 새로 만들어야 하는 상황인데요, 다행히도 Zetetic 사에서 만든 SQLCipher for Android 는 이 문제를 모두 해결해 주는 고마운 오픈 소스 프로젝트입니다.SqlCipher 의 사용법은 기존의 SQLiteDatabase 에 의존하던 로직들의 import namespace 만 바꿔주면 되도록 구현되어 있어 마이그레이션 비용도 거의 들지 않습니다.// 안드로이드에서 제공해 주는 SQLiteDatabase 클래스명 import android.database.sqlite.SQLiteDatabase; // SqlCipher 에서 제공해 주는 SQLiteDatabase 클래스명 import net.sqlcipher.database.SQLiteDatabase; // 프로그램 시작시 native library 를 로드해줘야 한다. class MyApplication extends android.app.Application {    @Override public void onCreate() {        super.onCreate();        net.sqlcipher.database.SQLiteDatabase.loadLibs(this);    } } [리스트 2] android SQLiteDatabase 에서 SqlCipher SQLiteDatabase 로 마이그레이션 하기물론 두 클래스는 전혀 타입 호환되지 않지만, net.sqlcipher.database.SQLiteDatabase 의 모든 메소드 및 field의 signature 가 기본 android.database.sqlite.SQLiteDatabase 와 같기 때문에 이런 변경이 가능합니다. SqlCipher 개발팀의 수고에 박수를 보냅니다.RoomRoom 은 SQL 을 객체로 매핑해 주는 도구입니다. Room 을 이용해 데이터베이스를 열 때는 보통 아래와 같은 코드를 사용합니다.object Singletons {    val db: DataSource by lazy {        Room.databaseBuilder(appContext, DataSource::class.java, "secure_database")            .build()    } } abstract class DataSource: RoomDatabase() {    abstract fun userProfileDao(): UserProfileDao } // 클라이언트 코드에서 아래와 같이 호출 val userProfile: UserProfile = Singletons.db.userProfileDao().findUserByUid(userId) [리스트 3] Room database 의 정의 및 활용Sqlite 의 기본 동작은 파일 데이터베이스에 단순 Read 및 Write 만 합니다. 따라서 데이터베이스 접근시 암호화/복호화 동작을 하는 callback 을 주입해야 데이터베이스를 암호화 할 수 있습니다. 그리고 RoomDatabase.Builder 클래스는 데이터베이스를 열때 우리가 주입한 일을 할 수 있는 hook method(openHelperFactory) 를 제공해 주고 있습니다. 다음 코드를 살펴봅시다.class RoomDatabase.Builder {    class Builder {        /**        * Sets the database factory. If not set, it defaults to {@link FrameworkSQLiteOpenHelperFactory}.        */        @NonNull        public Builder openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory)    } } interface SupportSQLiteOpenHelper {    /**     * Create and/or open a database that will be used for reading and writing.     */    SupportSQLiteDatabase getWritableDatabase();    /**     * Create and/or open a database. This will be the same object returned by {@link #getWritableDatabase}.     */    SupportSQLiteDatabase getReadableDatabase();    /**     * Factory class to create instances of {@link SupportSQLiteOpenHelper} using {@link Configuration}.     */    interface Factory {        /**         * Creates an instance of {@link SupportSQLiteOpenHelper} using the given configuration.         */        SupportSQLiteOpenHelper create(Configuration configuration);    } } [리스트 4] Room builder 의 SupportSQLiteOpenHelper 주입 메소드 및 SupportSQLiteOpenHelper.Factory 인터페이스 정의설명을 최대한 간소하게 하기 위해 관심가질 필요 없는 코드 및 코멘트는 모두 제외했습니다. 아무튼 SupportSQLiteOpenHelper 구현체를 주입하면 뭔가 데이터베이스 작업 이전에 우리의 로직을 실행할 수 있을 것 같습니다.사실 이 인터페이스의 핵심은 바로 getWritableDatabase(), getReadableDatabase() 구현입니다. javadoc 에도 있지만 두 메소드로 반환하는 인스턴스는 같아야 하며 또한 암호화를 지원해야 한다는 것을 알 수 있습니다.결국 우리 목표는 Room 과 데이터베이스 암호화 로직을 연결해 주는 SupportSQLiteDatabase 구현체를 만드는 것임을 알 수 있습니다. 이 인터페이스는 규모가 제법 크기 때문에 이게 만만한 일이 아님을 직감하실 수 있을 겁니다.saferoom 도입으로 SupportSQLiteDatabase 인터페이스 구현체 사용하기앞서 살펴봤듯 SupportSQLiteDatabase 구현에는 상당한 노력이 필요하단 것을 알 수 있습니다. 그런데 고맙게도 saferoom 이라는 오픈 소스 프로젝트가 우리의 귀찮음을 잘 해결해 주고 있습니다. saferoom 의 SupportSQLiteOpenHelper 구현체를 간단히 살펴보면 아래와 같습니다./** * SupportSQLiteOpenHelper.Factory implementation, for use with Room  * and similar libraries, that supports SQLCipher for Android.  */ public class SafeHelperFactory implements SupportSQLiteOpenHelper.Factory {    private final char[] passphrase;    public SafeHelperFactory(final char[] passphrase) {        this.passphrase = passphrase;    }    @Override    public SupportSQLiteOpenHelper create(final SupportSQLiteOpenHelper.Configuration configuration) {        return(new com.commonsware.cwac.saferoom.Helper(configuration.context,            configuration.name, configuration.version, configuration.callback,            this.passphrase));    }    /**     * NOTE: this implementation zeros out the passphrase after opening the database     */    @Override    public SupportSQLiteDatabase getWritableDatabase() {        SupportSQLiteDatabase result = delegate.getWritableSupportDatabase(passphrase);        for (int i = 0; i < passphrase>            passphrase[i] = (char) 0;        }        return(result);    }    /**     * NOTE: this implementation delegates to getWritableDatabase(), to ensure that we only need the passphrase once     */    @Override    public SupportSQLiteDatabase getReadableDatabase() {        return getWritableDatabase();    } } /**  * SupportSQLiteOpenHelper implementation that works with SQLCipher for Android  */ class Helper implements SupportSQLiteOpenHelper {    final OpenHelper delegate;    Helper(Context context, String name, int version, SupportSQLiteOpenHelper.Callback callback, char[] passphrase) {        net.sqlcipher.database.SQLiteDatabase.loadLibs(context);        this.delegate = createDelegate(context, name, version, callback);        this.passphrase = passphrase;    }    abstract static class OpenHelper extends net.sqlcipher.database.SQLiteOpenHelper {        SupportSQLiteDatabase getWritableSupportDatabase(char[] passphrase) {            SQLiteDatabase db = super.getWritableDatabase(passphrase); return getWrappedDb(db);        }    } } [리스트 5] Saferoom 의 SupportSQLiteOpenHelper 구현체.소스 코드를 보면 SQLiteDatabase 의 원래 요구사항을 만족하지 못하는 구현 부분도 보입니다만, 그래도 이 정도면 수고를 꽤 크게 덜 수 있어 훌륭합니다.그리고 로직을 잘 보면 데이터베이스를 연 직후 암호로 넘겨준 char[] 배열을 초기화 하는 코드가 있다는 점입니다. 이것이 바로 이 문서의 서두에서 말했던 attack surface 를 최소화 하기 위한 구현입니다. 이 글의 주제에서 벗어난 내용이기에 여기서는 다루지 않습니다만, 궁금하신 분들은 부록 1: in-memory attack 맛보기에서 확인하실 수 있습니다.SqlCipher + SafeRoom + Room 구현 및 코드 설명이상으로 데이터베이스 암호화 전략에 대해 살펴봤습니다. 이 장에서는 실제로 연동하는 방법에 대해 다룹니다.불행히도 2018년 현재 SqlCipher 는 Android KeyStore 를 지원하지 않고 있습니다. 그리고 인스턴스 생성에 쓸 비밀번호로 CharArray 가 필요한데, 이 값은 한번 정해지면 불변해야 합니다. 여기 사용할 키를 KeyStore 에 저장하면 문제를 깔끔하게 해결할 수 있을 것 같습니다. 하지만 1부에서 살펴봤듯이 하드웨어로 구현된 Android KeyStore 밖으로는 키가 절대로 노출되지 않는다고 합니다. 이 문제를 어떻게 해결해야 할까요?먼저, SqlCipher 에 사용하기 위해 KeyStore 로 생성한 AES256 키의 내용을 한번 살펴봅시다.val secretKey = with(KeyGenerator.getInstance("AES", "AndroidKeyStore"), {    init(KeyGenParameterSpec.Builder(alias,             KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)        .setKeySize(256)        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)        .build())    generateKey() }) val keyInfo = with(KeyFactory.getInstance(privKey.getAlgorithm(), "AndroidKeyStore"), {    factory.getKeySpec(privKey, KeyInfo::class.java) }) println("Key algorithm : " + secretKey.algorithm) println("Key format : " + secretKey.format) println("Encoded key size: " + secretKey.encoded?.size) println("Hardware-backed : " + keyInfo.isInsideSecureHardware) // 실행 결과 Key algorithm : AES Key format : null Encoded key size: null Hardware-backed : true [리스트 6] AndroidKeyStore 에 저장한 Key 는 어플리케이션에서 직접 쓸 수 없다.저희가 보유중인 개발 시료 Nexus 5 에서 실행한 결과 위와 같이 나타났습니다. secretKey.encoded 의 값이 메모리에 있다면 이 값을 SqlCipher 생성자에 넘겨줄 수 있겠지만 값이 null 이네요. 보안 측면에서는 다행일 지 모르지만 우리 구현에서는 쓸 수 없으니 문제입니다. 그래서 별 수 없이 임의로 키를 만들고(AndroidAesHelper#generateRandomKey()), 1부에서 소개했던 AndroidRsaCipherHelper 를 이용해 암호화한 값을 Shared Preferences에 저장하는 식으로 구현해 봅시다.val settingsPrefs = appContext.getSharedPreferences("app_settings", Context.MODE_PRIVATE) val settings = SecureSharedPreferences.of(settingsPrefs) val dbPass = with(settings, {    /*     * String.toCharArray() 같은 함수를 쓰면 로직이 좀더 간단해지지만, JVM 에서의 String은     * Immutable 하기 때문에 GC 이전에는 지울 방법이 없으므로 attack surface 가 더 오랫동안     * 노출되는 부작용이 있다. 따라서 key의 plaintext 는 가급적 String 형태로 저장하면 안된다.     */    var savedDbPass = getString("DB_PASSPHRASE", "")    if (savedDbPass.isEmpty()) {        // KeyStore 에 저장해도 SqlCipher 가 써먹질 못하니 그냥 1회용 키 생성 용도로만 활용한다.        val secretKey = AndroidAesCipherHelper.generateRandomKey(256)        // String 생성자 사용: 이 문자열은 heap 에 저장된다.        savedDbPass = String(Base64.encode(secretKey, Base64.DEFAULT))        putString("DB_PASSPHRASE",  AndroidRsaCipherHelper.encrypt(savedDbPass))        // 메모리 내에 plaintext 형태로 존재하는 attack surface 를 소멸시켜 준다.        secretKey.fill(0, 0, secretKey.size - 1)    } else {        // decrypt 메소드 내부에서 String 생성자 사용하므로 base64 인코딩된 plaintext 키는 heap 에 저장된다.        savedDbPass = AndroidRsaCipherHelper.decrypt(savedDbPass)    }    val dbPassBytes = Base64.decode(savedDbPass, Base64.DEFAULT)    /*     * SqlCipher 내부에서는 이 char[] 배열이 UTF-8 인코딩이라고 가정하고 있다.     * 그리고 UTF-8 인코딩에서는 byte range 의 char 는 1 바이트니까,     * 아래 변환을 거치더라도 키 길이는 32 byte(256 bit)가 유지된다.     *     * UTF-8 인코딩에서는 32 글자 != 32 바이트가 아님에 항상 유의해야 한다!     */    CharArray(dbPassBytes.size, { i -> dbPassBytes[i].toChar() }) }) [리스트 7] 암호화한 SqlCipher 용 passphrase 를 사용하는 방법.위 코드를 사용해 char[] 타입의 값 dbPass 를 얻을 수 있습니다. 리스트 7을 이용해 얻은 dbPass를 아래 코드에 사용하면 SqlCipher - SafeRoom - Room 의 연동이 끝납니다.val dataSource = Room.databaseBuilder(_instance, DataSource::class.java, "secure_database") .openHelperFactory(SafeHelperFactory(dbPass))                .build() // 메모리 내에 plaintext 형태로 존재하는 attack surface 를 소멸시켜 준다. dbPass.fill('0', 0, dbPass.size - 1) [리스트 8] SqlCipher - SafeRoom - Room 연동하기위 코드에서 볼 수 있듯, 임의로 저장한 키를 Base64 인코딩으로 변환, 그리고 그것을 다시 CharArray 로 변환하는 과정에서 key 가 메모리에 존재해야 하는 순간이 있습니다. 이 구간을 바로 공격 표면(attack surface) 이라고 합니다.JVM 단에서 넘겨주는 Passphrase 를 SqlCipher 내부에서 native 로 어떻게 처리하고 있는지는 SqlCipher SQLiteDatabase 구현및 SqlCipher crypto 구현 에서 확인할 수 있습니다.결과 확인하기SafeHelperFactory 를 주입한 Room database 파일을 추출 후 hexdump 로 확인해 보겠습니다.hwan@ubuntu:~$ hexdump -vC secure_database.sqlite3 00000000  8c 0d 04 07 03 02 11 eb  a4 18 33 4f 93 e8 ed d2  |..........3O....| 00000010  e9 01 21 d7 49 df 25 9a  f4 1d c7 1e ff 2d b0 13  |..!.I.%......-..| 00000020  fc 17 9b 4b b2 1c a3 1d  7d 1d 69 76 b1 ea ec e8  |...K....}.iv....| 00000030  1f 50 e4 c4 6c 50 e6 82  58 27 b9 fe 85 21 27 99  |.P..lP..X'...!'.| 00000040  ec 54 53 ba 32 c6 59 09  b4 30 65 39 a0 75 3e c4  |.TS.2.Y..0e9.u>.| 00000050  b8 f7 ea 47 14 df c4 f0  7c be 9f 62 26 49 1c b2  |...G....|..b&I..| 00000060  0f 63 00 7a 09 7e 33 e0  43 2b eb ea 80 21 bb 5d  |.c.z.~3.C+...!.]| 00000070  5c 04 ff 57 a3 a3 7f c2  19 42 b9 67 6c e3 d5 c8  |\..W.....B.gl...| ... 00000d30  c1 f3 93 1f 4e 5b 6a 70  39 c2 e9 2c 3e 8f 7e ff  |....N[jp9..,>.~.| 00000d40  73 3a 9a 39 0d 8a 1a 3e  6b d4 5b de 1f 6d c4 b8  |s:.9...>k.[..m..| 00000d50  fb 62 3e 21 09 0a 31 20  37 5d 8d 0a 39 6d 35 31  |.b>!..1 7]..9m51| 00000d60  26 d6 b0 22 41 7e 6c 54  7d 77 22 ba 1b f3 cf 5a  |&.."A~lT}w"....Z| 00000d70  e5 47 97 76 f0 89 e5 98  b3 37 3c 8d 43 af 0e b9  |.G.v.....7<.C...| 00000d80  18 74 fd f5 2a 41 d8 b1  d9 70 32 0b 5c 93 4b 0d  |.t..*A...p2.\.K.| 00000d90  bc 60 4c 25 9a ec 53 23  90 60 b2 52 a8 a1 b1 87  |.`L%..S#.`.R....| 00000da0  f3 3e 03 3e ac 0a 75 a0  61 d8 bd 07 b8 5a 48 66  |.>.>..u.a....ZHf| 00000db0  57 85 13 ac 04 26 55 30  34 46 57 bf 8b 42 c6 2d  |W....&U04FW..B.-| 00000dc0  9e 82 a2 df 77 bb b3 2e  96 43 70 23 23 03 df 1d  |....w....Cp##...| ... [리스트 9] Internal storage 에 저장된 SQLite3 database 를 dump 한 결과. 리스트 1과 비교해 보자.이로서 오픈 소스의 힘을 빌려 우리 앱의 데이터베이스를 비교적 간편하게 암호화 할 수 있음을 알 수 있습니다.맺으며이로서 Persistent data 암호화에 대한 설명을 마칩니다. Android KeyStore 가 API Level 23 이상의 기기에서만 100% 동작한다는 점은 2018년 현재까지는 큰 단점입니다. 하지만 사소한 데이터라 하더라도 보안의 중요성은 날로 강조되고 있습니다. 따라서 빠르던 늦던 고객 데이터 암호화에 투자해야 할 순간이 다가온다는 점은 변하지 않습니다.언젠가는 적용해야 할 고객 데이터 보호의 순간에, 이 글이 여러분의 앱의 보안에 조금이나마 도움이 된다면 좋겠습니다.부록 1: in-memory attack 맛보기앞서 계속 반복해서 설명드렸던 메모리 내의 attack surface 를 찾아내는 방법을 간단히 설명해 보겠습니다. 잘 지키려면 잘 공격하는 법을 알아야 하므로 알아두면 좋지 않을까요? 그리고 일반적인 앱 개발과는 다소 동떨어진 이 장의 내용이 이해되지 않으신다면 한줄요약한 메모리 내부의 값도 때로는 안전하지 않을 수 있다 는 한마디만 기억해 두시면 됩니다. 모든 데모는 LG Nexus 5(Hammerhead), 시스템 버전 6.0.1(M) 에서 실행한 결과며 시스템마다 약간의 차이는 있을 수 있습니다.마켓에 출시한 앱들은 debuggable:false 가 설정된 상태이므로 힙 덤프를 바로 뜰 수는 없습니다. 그런데 어떻게 in-memory attack 이 가능할까요? 다음 리스트는 디버그 불가능한 앱의 힙 덤프를 시도할 때 보안 정책 위반 오류가 발생함을 보여줍니다.hwan@ubuntu:~$ adb shell ps | grep "com.securecompany.secureapp" USER PID PPID VSIZE RSS WCHAN PC NAME u0_a431   25755 208   1700384 100888 sys_epoll_ 00000000 S   com.securecompany.secureapp hwan@ubuntu:~$ adb shell am dumpheap 25755 "/data/local/tmp/com.securecompany.secureapp.heap" java.lang.SecurityException: Process not debuggable: ProcessRecord{b6f96fc 25755:com.securecompany.secureapp/u0_a431}     at android.os.Parcel.readException(Parcel.java:1620)     at android.os.Parcel.readException(Parcel.java:1573)     at android.app.ActivityManagerProxy.dumpHeap(ActivityManagerNative.java:4922)     at com.android.commands.am.Am.runDumpHeap(Am.java:1248)     at com.android.commands.am.Am.onRun(Am.java:377)     at com.android.internal.os.BaseCommand.run(BaseCommand.java:47)     at com.android.commands.am.Am.main(Am.java:100)     at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)     at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:251) [리스트 10] debuggable=false 설정된 앱의 힙 덤프 시도시 발생하는 예외(SecurityException)SuperUser 는 가능할까요? SuperUser 권한으로 앱을 강제로 디버그 가능한 상태로 시작해 보도록 하겠습니다.hwan@ubuntu:~$ adb shell 32|shell@hammerhead:/ $ su 1|root@hammerhead:/ \# am start -D -n "com.securecompany.secureapp/MainActivity" && exit Starting: Intent { cmp=com.securecompany.secureapp/MainActivity } hwan@ubuntu:~$ \# adb shell ps | grep "com.securecompany.secureapp" USER PID PPID VSIZE RSS WCHAN PC NAME u0_a431   27482 211   1700384 100888 sys_epoll_ 00000000 S   com.securecompany.secureapp hwan@ubuntu:~$ adb forward tcp:12345 jdwp:27482 hwan@ubuntu:~$ netstat -an | grep 12345                                                           tcp4       0      0  127.0.0.1.12345         *.*                    LISTEN     hwan@ubuntu:~$ jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=12345 java.net.SocketException: Connection reset     at java.net.SocketInputStream.read(SocketInputStream.java:210)     at java.net.SocketInputStream.read(SocketInputStream.java:141)     at com.sun.tools.jdi.SocketTransportService.handshake(SocketTransportService.java:130)     at com.sun.tools.jdi.SocketTransportService.attach(SocketTransportService.java:232)     at com.sun.tools.jdi.GenericAttachingConnector.attach(GenericAttachingConnector.java:116)     at com.sun.tools.jdi.SocketAttachingConnector.attach(SocketAttachingConnector.java:90)     at com.sun.tools.example.debug.tty.VMConnection.attachTarget(VMConnection.java:519)     at com.sun.tools.example.debug.tty.VMConnection.open(VMConnection.java:328)     at com.sun.tools.example.debug.tty.Env.init(Env.java:63)     at com.sun.tools.example.debug.tty.TTY.main(TTY.java:1082) Fatal error:  Unable to attach to target VM. [리스트 12] SuperUser 권한으로도도 Java 디버거를 붙일 수 없다.다행히도 debuggable=false 로 릴리즈한 앱은 자바 디버거(jdb)를 붙일 수 없으니 프로그램 실행을 매우 정밀하게 제어할 수는 없다는 것을 알 수 있습니다(debuggable=true 설정된 앱에 위 과정을 실행하면 어떤 일이 벌어지는지 직접 확인해 보세요!).하지만 안드로이드의 앱은 ‘linux process’ 에서 실행되므로 SuperUser 권한으로 process 메모리 전체 dump를 뜨는 것은 막을 수 없습니다. 정공법으로는 /proc/PID/maps 의 내용을 분석하면 됩니다만 제가 안드로이드를 깊게 알고 있는 것은 아니라, 어느 영역이 dalvik heap 인지를 알아낼 수 없었습니다. 이 때문에 프로세스 메모리를 통째로 떠서 내용을 헤집어보는 방식으로 공격해 보겠습니다. 여담입니다만, 데모를 위해 공격한 앱은 dumpsys 명령으로 확인해보니 약 6MiB 의 Java heap 을 쓰고 있는데요, 이 크기를 줄이면 줄일 수록 공격이 더욱 수월할 겁니다.아래 데모에서는 안드로이드 기기용(arm-linux-gnueabi)으로 컴파일한 gdb 를 미리 설치한 결과를 보여드리고 있습니다. 참고로 여기 보이는 [heap] 은 아쉽지만 native heap 이므로 우리 공격 목표는 아닙니다.1|root@hammerhead:/ \# cd /proc/27482 1|root@hammerhead:/proc/27482 \# cat maps 12c00000-12e07000 rw-p 00000000 00:04 8519       /dev/ashmem/dalvik-main space (deleted) ... b7712000-b771f000 rw-p 00000000 00:00 0 [heap] bee86000-beea7000 rw-p 00000000 00:00 0 [stack] ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors] 1|root@hammerhead:/proc/27482 \# ifconfig wlan0     Link encap:Ethernet          inet addr:192.168.12.117          inet6 addr: fe80::8e3a:e3ff:fe5f:64c9/64 1|root@hammerhead:/proc/27482 \# gdbserver –attach :12345 27482 Attached; pid = 27482 Listening on port 12345 [리스트 13] SuperUser 권한으로 gdbserver 실행.hwan@ubuntu:~$ adb forward tcp:23456 tcp:12345 hwan@ubuntu:~$ netstat -an | grep 23456 tcp4       0      0  127.0.0.1.23456         *.*                    LISTEN     [리스트 14] 로컬 포트 23456 으로 원격 포트 12345 를 연결하는 과정.이제 모든 준비가 끝났습니다. 개발 기기에서 gdb로 원격 프로세스에 접근한 뒤, 메모리를 덤프해 봅시다.hwan@ubuntu:~$ ./gdb (gdb) target remote 192.168.12.117:12345 Remote debugging using 192.168.12.117:12345 0xb6f92834 in ?? () (gdb) dump memory /tmp/com.securecompany.secureapp.heap 0x12c00000 0xb771f000 (gdb) [리스트 15] gdb 로 메모리를 덤프하는 과정.덤프한 힙 덤프 파일 속에 있을지도 모르는 문자열을 검색해 봅시다. 그 전에 잠시, 데이터베이스에 사용할 키를 어떻게 처리했었나 되새겨 볼까요? if (savedDbPass.isEmpty()) {        // ...        // String 생성자 사용: 이 문자열은 heap 에 저장된다.        savedDbPass = String(Base64.encode(secretKey, Base64.DEFAULT))    } else {        // decrypt 메소드 내부에서 String 생성자 사용하므로 base64 인코딩된 plaintext 키는 heap 에 저장된다.        savedDbPass = AndroidRsaCipherHelper.decrypt(savedDbPass)    } [리스트 16] Base64 인코딩을 처리하기 위한 임시 String 생성 과정.우리 로직은 256 비트의 키를 Base64 변환해서 디스크에 저장합니다. 그리고 256비트의 byte array 를 base64 변환한 결과는 (4 * (256 / 3)) / 8 = 42.66 바이트 -> 4의 배수여야 하므로 44바이트입니다. 약 1.34 바이트의 pad 를 맞추기 위해 문자열의 끝에 =가 최소 1글자 이상은 있을 겁니다. 한번 찾아봅시다.hwan@ubuntu:~$ strings /tmp/com.securecompany.secureapp.heap ... /masterkey ... user_0/.masterkey em_s 1337 ... [리스트 17] strings 명령을 사용한 힙 덤프 파일내의 문자열 검색의외로 = 나 == 로 끝나는 문자열이 발견되지 않습니다. 하지만 안심하기는 이릅니다. 이건 단순히 (공격자의 입장에서) 운이 나빠서 발견되지 않은 것일 뿐입니다. 우리가 원하는 어떤 ‘순간’ 에 힙 덤프 명령을 내리지 않았기 때문에 그렇습니다. 우리의 구현은 attack surface 를 매우 짧은 시간동안만 메모리에 노출하기 때문에 이 순간이 짧으면 짧을 수록, 디바이스의 성능이 좋으면 좋을 수록 순간을 잡아내기가 더욱 어려워집니다. 즉, 이 문서에서 보여드린 방식으로 CharArray 의 내용을 아주 짧은 시간 동안만 사용하고 지워버리면 내용을 탈취하기 굉장히 어렵습니다. 하지만 안심하기는 이릅니다. nano-time 단위로 앱을 실행할 수 있는 환경을 가진 국가급 공격자는 여전히 있기 때문입니다.그리고 이 방법은 루팅하지 않은 기기에서는 절대 재현이 불가능하므로 루팅되지 않은 환경일 경우에만 실행 가능하도록 한다던가 하는 방식까지 더한다면 공격자가 더욱 우리 앱을 뚫기 힘들 겁니다.여담입니다만 독자 여러분들 중 GameGuardian 처럼 다른 게임의 메모리값을 마구 바꾸는 앱이 어떻게 동작하나 궁금하신 분들도 있을 겁니다. 그런 류의 앱들도 바로, 이 장에서 설명했던 방식으로 동작합니다.장황했던 이 장의 내용을 한줄로 요약하면 Android KeyStore 로 보호하지 않은 키는 많은 수고를 들이면 뚫을 수 있다고 할 수 있습니다.부록 2: SQLite database 의 UPDATE / DELETE 구현 특성SQLite3 의 구현특성상, UPDATE / DELETE 시에 이전 레코드의 값이 남아있는 경우가 있습니다. 암호화 했으니 좀더 안전하다곤 하지만 찌거기 값을 굳이 남겨둬서 공격자에게 더 많은 힌트를 제공할 필요도 없습니다.이 문서는 암호화 구현에만 초점을 맞췄기 때문에 상세하게 다루진 않습니다만, LINE Tech blog에 소개된 True delete 는 이 문제를 해결하기 위한 방법을 제시하고 있으므로 그 문서도 한번 읽어보시길 권합니다.더 보기SQLCipherSafeRoomAndroid SQLite3 True delete - by LINE tech blogDifference between java.util.Random and java.security.SecureRandomAttack surface on security measuresAOSP: DebuggingRootbeer: Simple to use root checking Android library#하이퍼커넥트 #개발 #개발자 #안드로이드 #앱개발 #모바일 #PersistentData #인사이트 #개발후기
조회수 1203

깃발 올려, Git Effect!

안녕하세요, 개발 2팀에서 단아함을 맡고 있는 오연주입니다. 평소에 관심이 많았던 깃(Git)을 공부하면서 알게 된 내용들을 글로 쓰려고 합니다. ‘어떤 닝겐이 만들었나’ 궁금할 정도로 천재적인 깃은 도대체 누가 만든 것일까요? 바로 리누스 토발즈(Linus Torvalds)입니다. 이름에서부터 OS의 느낌이 가득합니다. 네, 맞습니다. 그는 리눅스(Linux)의 창시자이기도 합니다. 리누스는 말했죠. “My name is Linus, and I am your God.” 리누스 토발즈 (Linus Torvalds)그가 깃을 만들기 전에는 보통 중앙집중식 VCS(Version Control System)를 사용했었습니다. 예를 들면 다음과 같은 도구들로요. CVSSVN(Subversion)…반면에 깃은 분산 버전 관리 시스템(DVCS, Distributed Version Control System)입니다. 그렇다면 중앙집중식의 대표주자인 Subversion(VCS)에 비해 무엇이 더 좋을까요? 속도가 빠르다. snv log svn diff -rN svn commit 등 대부분의 명령어가 네트워크 연결이 되어야 실행 가능한 명령어입니다. 그러나 git push git clone 등 몇몇 명령어를 제외하고는 네트워크에 연결되어 있지 않아도 로컬에서 실행할 수 있습니다. 용량이 적다. Mozilla의 SVN Repository는 126GB인데 반해 Git Repository은 420MB입니다. 왜냐하면 해쉬, 스냅샷을 이용한 효율적인 파일 변화 관리가 가능하기 때문입니다. 브랜치를 만드는 작업이 수월하다. SVN은 diff를 전부 적용해서 파일을 생성한 뒤 네트워크에서 내려받는 반면, 깃은 스냅샷을 가리키는 링크(Commit Object)만 만들면 됩니다.어떠한 특징을 가지고 있길래 이런 차이점이 생기는 걸까요?분산 저장소로, 로컬에서도 중앙 저장소와 연결되지 않은 상태에서 지지고 볶기가 가능하다니! 여러 개의 다른 저장소를 생성할 수 있고 서로서로 연결되어 독립적으로 개발 프로젝트를 진행할 수 있고 유기적인 업데이트가 가능합니다. 델타 기법이 아닌 스냅샷 방식을 사용합니다. SVN의 경우 파일 변화를 diff로서 추적한 반면, Git은 각 시점의 파일 상태를 모두 스냅샷을 찍어 관리합니다.변화를 기억했던 기존 방식변화된 소스를 커밋할 때 스냅샷을 찍는 방식두 가지 특징을 살리려면 깃이 여타 다른 VCS와는 다른 방식으로 정보를 관리할 필요가 있습니다. 예를 들어 Revision number로 히스토리를 관리했던 Subversion으로 분산된 저장소의 히스토리를 관리하려고 하면 ‘시점 충돌’ 문제가 발생합니다.그..그려봤습니다..금융 프로젝트에 참여했을 때의 일입니다. VCS 중 H사 툴을 사용하였는데 한 소스의 버전을 받고 개발하는 과정에서 커밋의 횟수가 많아지니 중앙 저장소 입장에서는 ver 1 → ver 9로 갑자기 타임워프하는 일이 생겼습니다. 그래서 개발자 스스로 본인의 버전을 모두 삭제한 후 ver 9였던 파일을 수동으로 ver 2로 바꿔주는 것이 관례였습니다. 소스가 모두 날아가는 경우가 있어 소스 commit 과정이 공포스러웠죠. 깃은 해쉬(hash)를 이용한 정보 관리를 통해 이런 문제를 말끔하게 해결합니다.Git의 핵심, 정보 Hashing! git reset --hard 3269aecad9ffea81763a42b9fff34c76a0aa4cf0 브랜디 소스 코드를 pull 했는데 특정 시점으로 돌아가 할 일이 생겨 위의 명령어를 입력했던 적이 있습니다. 명령어로 깔끔하게 원하는 시점으로 되돌아올 수 있었죠. 뒤에 붙는 40자리의 기괴한 문자열은 바로 깃이 정보를 관리하는 데에 사용하는 해쉬값입니다. 해쉬값이 제일 많이 보이는 곳은 git log 가 아닐까 싶은데요. commit 옆에 나열된 일련번호같은 문자열이 궁금하진 않으셨나요?깃은 소스 코드를 포함해서 히스토리를 관리하는데 필요한 모든 정보를 이런 해쉬로 저장 및 관리합니다. 이 해쉬값은 40자리 16진수 숫자이며 SHA-1 알고리즘으로 생성됩니다. SHA-1 알고리즘은 보안 표준 해쉬 알고리즘 중 하나입니다. 충돌할 확률은 1 / 10^45로, 매우 매우 낮기 때문에 수많은 정보를 저장 및 관리하기에 안전하고 적합합니다. 4GHz CPU로 SHA-1 해쉬 중복값을 찾아내려면 4000년이 걸린다.앞서 SHA-1 해쉬값으로 모든 정보를 저장한다고 말씀드렸는데, 과연 어떤 정보를 어디에, 어떻게 저장하고 있는 것일까요? 각 해쉬 값은 깃이 내부적으로 저장하는 파일 이름이 되기도 하는데, 이 파일들은 .git/objects 경로에서 전부 찾아볼 수 있습니다. 해쉬값 40자리 중 앞 2자리를 디렉토리 이름으로 따고, 뒤 38자리를 파일 이름으로 지정합니다. 각 파일 안에는 서로 다른 정보가 담겨 있습니다. 해쉬값으로 표현되는 이 파일들은 정보의 종류에 따라 3가지 객체로 분류됩니다. Blob ObjectTree ObjectCommit Object폴더나 파일명이 어떤 오브젝트인지 힌트를 주지 않기 때문에 세 가지의 오브젝트 파일 내용의 캡처를 위해 복불복으로 열어봤는데요, 하나의 파일을 열 때마다 포춘쿠키를 까듯 심장이 쫄깃쫄깃했습니다. Blob Object란 실제 파일을 뜻하며, 실제 소스파일을 가지고 있는 실세 오브젝트같은 느낌입니다. Blob Object - 열어보면 내가 작성한 소스 코드가 들어있다.Tree Object 내부에는 프로젝트 구조의 각 디렉토리에 대한 정보가 담겨 있습니다. 하위에 어떤 폴더와 파일을 가지고 있는지 알려주고, 객체 해쉬 값을 저장하고 있습니다. 이 Tree Object의 제일 상위 객체는 root이며, 프로젝트의 최상위 폴더에 대한 정보를 담게 됩니다.앞서 깃은 각 시점별 스냅샷을 찍어 관리한다고 했습니다. 스냅샷을 찍는 행위는 새로운 Root Tree Object를 만들고, 각 시점에 가지고 있는 Tree Object와 Blob Object로 새로운 트리 구조를 만드는 과정입니다. Tree Object - 하위에 php라는 폴더와 README.md라는 파일이 들어있는 것을 볼 수 있다.Commit Object는 커밋 시점의 Repository Root Directory의 해쉬 값을 가지고 있는 녀석입니다. Parent는 내 커밋 전에 커밋이 누구인지를 뜻하는데요. 또한, 커밋할 때의 committer(user), commit message등의 정보도 가지고 있습니다.Commit Object - 해당 commit 시점의 root tree object와 이전 커밋, 작성자 등에 대한 정보를 담고 있다.세 종류의 객체는 깃이 분산된 Repository 간의 소스 히스토리를 쉽게 관리하도록 도와줍니다. 해쉬값으로 관리되기 때문에 특정 스냅샷에 이동하거나, 히스토리를 변경 또는 추가하는 데에 적은 리소스만 필요합니다. 또 분산된 저장소 사이에 상호 시간 순서에 대한 모호함도 해결할 수 있었습니다. 이 정도면 갓누스….깃을 공부하기 시작한 이유는 Git UI Tool을 쓰면서 습관적으로 commit, push 버튼을 눌렀기 때문입니다. 깃에 대한 이해도가 있는 상태에서 사용한다면 실수가 줄어들 거라 생각합니다. 다음 글은 Git branching Model을 다루겠습니다. ps. Git, 협업과 원활한 커뮤니케이션을 위해 알고 씁시다! 우리 함께 깃빨 받읍시다!! 참고 Scott Chacon and Ben Straub, ⌈Pro Git, 2nd Edition⌋, Apress(2014)Schneier on SecurityProbability of SHA1 collisions, stack overflowSVN 능력자를 위한 git 개념 가이드, Insub Lee, Slide Share글오연주 사원 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 2130

시간을 줄여주는 CodeStar 사용 팁

편집자 주: 함께 보면 좋아요!애플리케이션 개발부터 배포까지, AWS CodeStarOverview: 작성 환경AWS CodeStar를 사용하면 애플리케이션의 서버, 언어 , 형상관리, 배포, 빌드까지 한꺼번에 관리할 수 있습니다. AWS를 사용하는 개발자라면 꼭 필요한 도구이기도 합니다. 이번 글에서는 CodeStar를 초기 설정할 때의 도움이 될 내용들을 소개하겠습니다.-서비스: AWS CodeStar-템플릿: Python Webservice, AWS Lambda목차파라미터 바인딩람다 환경변수 설정람다 레이어 설정xray 모니터링 설정람다 함수명 설정Global 섹션로컬 개발환경에서의 SAM 실행CodeStar 프로젝트 생성 후CodeStar로 프로젝트를 생성하면 소스코드와 배포를 위한 Code 시리즈 리소스들이 함께 만들어집니다. CodeCommit, CodeBuild, CodePipeline 등이 있습니다. 우선 기본으로 구축된 파이프라인부터 살펴보겠습니다.CodeCommit 리포지토리의 마스터 브랜치 코드를 변경하면 CodeBuild와 CloudFormaton 서비스를 통해 빌드, 테스트, 배포를 진행할 수 있게 설정되어 있습니다. 생성된 리포지토리의 template.yml 파일을 이용하면 프로젝트 리소스도 관리할 수 있는데, 특히 template.yml을 통해 CloudFormation으로 관리하는 리소스까지도 관리가 가능합니다.기본으로 생성된 template.yml 파일을 자세히 살펴보겠습니다.AWSTemplateFormatVersion: 2010-09-09 Transform: - AWS::Serverless-2016-10-31 - AWS::CodeStar Parameters: ProjectId: Type: String Description: CodeStar projectId used to associate new resources to team members CodeDeployRole: Type: String Description: IAM role to allow AWS CodeDeploy to manage deployment of AWS Lambda functions Stage: Type: String Description: The name for a project pipeline stage, such as Staging or Prod, for which resources are provisioned and deployed. Default: '' Globals: Function: AutoPublishAlias: live DeploymentPreference: Enabled: true Type: Canary10Percent5Minutes Role: !Ref CodeDeployRole Resources: HelloWorld: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: python3.7 Role: Fn::GetAtt: - LambdaExecutionRole - Arn Events: GetEvent: Type: Api Properties: Path: / Method: get PostEvent: Type: Api Properties: Path: / Method: post LambdaExecutionRole: Description: Creating service role in IAM for AWS Lambda Type: AWS::IAM::Role Properties: RoleName: !Sub 'CodeStar-${ProjectId}-Execution${Stage}' AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: [lambda.amazonaws.com] Action: sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/CodeStar_${ProjectId}_PermissionsBoundary' 파라미터 바인딩Parameters 섹션에서는 ProjectId, CodeDeployRole, Stage 등 템플릿에서 사용할 파라미터를 지정할 수 있습니다. yml 파일 안에서는 ${ProjectId} 와 같이 사용할 수 있고, CodePipeline 환경에서 파라미터를 전달할 수 있습니다.CodePipeline → Deploy → GenerateChangeSet → Advanced → Parameter overrides람다 환경변수 설정람다 함수에서 사용할 환경변수를 설정할 수 있습니다. 아래와 같이 람다 환경변수 TZ(timezone)를 지정하면 실행 환경의 표준 시간대 설정이 가능합니다.Resources: HelloWorld: Type: AWS::Serverless::Function Properties: Environment: Variables: TZ: 'Asia/Seoul' 람다 레이어 설정람다 레이어를 적용하면 패키지 관리가 훨씬 편리해집니다. 함수의 패키지 크기가 3MB를 넘지 않으면 콘솔에서 코드를 직접 확인 및 수정할 수 있습니다. 람다 레이어는 zip 파일로 관리되고, /opt 폴더에 압축 해제되며 생성됩니다.람다는 250MB의 제한이 있습니다. 만약 레이어를 사용해 분리하더라도 람다함수패키지와 람다 레이어의 합으로 걸려있으므로 크기 제약에서 벗어날 수는 없습니다.Resources: HelloWorld: Type: AWS::Serverless::Function Properties: Layers: - arn:aws:lambda:{region}:{id}:layer:{layer-name}:{version} xray 모니터링 설정Tracing Property를 이용하면 람다 함수의 Enable active tracing 설정을 할 수 있습니다. CloudFormation 템플릿 메뉴얼엔 TracingConfig로 안내하고 있어도 빌드에 실패하여 확인해보니 SAM 템플릿의 AWS::Serverless::Function 의 스펙에선 Tracing으로 안내되고 있는 걸 볼 수 있었습니다.Resources: HelloWorld: Type: AWS::Serverless::Function Properties: Tracing: Active 람다 함수명 설정람다 함수는 기본적으로 아래와 같은 이름을 부여합니다.awscodestar-{brandi-test(프로젝트명)}-lambda-{HelloWorld(template함수ID)}-{NZ6YXLZ8XD0O(RANDOM_ID)}만약 함수 간의 호출이 필요할 때는 아래와 같이 함수 이름의 지정도 가능합니다.Resources: HelloWorld: Type: AWS::Serverless::Function Properties: FunctionName: !Sub '${ProjectId}-HelloWorld-${Stage}' Global 섹션Global 섹션을 이용하면 리소스마다 동일하게 적용할 항목들을 관리할 수 있습니다.Globals: Function: Runtime: python3.6 Environment: Variables: TZ: 'Asia/Seoul' VpcConfig: SubnetIds: - subnet-a1111111 - subnet-b2222222 SecurityGroupIds: - sg-c2222222 로컬 개발환경에서의 SAM 실행API Gateway 환경 실행sam local start-api 람다 함수 직접 실행echo ‘{}’ | sam local invoke —parameter-values=‘ParameterKey=ProjectId,ParameterValue=brandi-test’ HelloWorld Conclusion지금까지 CodeStar 초기 설정에 도움이 될 내용들을 살펴봤습니다. 강력한 기능들과 함께 업무를 진행한다면 조금이라도 더 나은 개발 환경을 구축할 수 있을 거라 생각합니다.글이상근 실장 | R&D DO실[email protected]브랜디, 오직 예쁜 옷만
조회수 2239

Dropwizard와 Asynchronous HBase 적용기

Background워크인사이트 서비스는 루비 온 레일즈 기반으로 작성된 웹 애플리케이션입니다. 주로 사용하는 데이터의 대부분은 HBase에 저장되어 있습니다. HBase는 자바로 작성된 API를 기본으로 제공하므로, 레일즈가 직접 HBase의 데이터에 접근할 수 없습니다. 따라서 데이터를 효과적으로 읽어들이기 위해서는 두 가지 방법 중 하나를 선택해야 합니다. 첫 번째는 HBase Java API를 이용하기 위해 웹 애플리케이션 역시 자바 기반의 프레임워크로 재작성하는 것이고, 두 번째는 HBase 스토리지 측 데이터 형식과 레일즈 웹서비스 측 데이터 형식을 서로 연결해주는 RPC 중개자를 도입하는 것입니다. 첫 번째 방법은 프로그래밍 언어를 통일함으로써 데이터 통신의 일관성은 물론 시스템 안정성이나 성능 측면에서 좀 더 낫다는 장점이 있는 반면에, 현재까지 작성한 레일즈 애플리케이션을 전부 자바 기반의 프레임워크로 재작성해야한다는 단점이 있습니다. 두 번째 방법은 보다 범용성을 지향하는 방식으로 향후 시스템의 확장에 좀 더 유용하지만, 첫 번째 방법보다 시스템 전체 성능은 뒤떨어진다는 단점을 갖고 있습니다.당시에는 이미 워크인사이트의 개발이 상당히 진척된 상태였기 때문에, 레일즈 프레임워크를 그대로 유지하면서 자바와 소통할 수 있는 JRuby를 사용하는 것이 최선인 것 같았습니다. 하지만 실험 결과 JRuby는 실 사용할 수 없을 정도의 성능을 냈습니다. 무엇보다도 레일즈 지원이 아직 미성숙한 상태였고, 사용중인 루비 젬 중에도 네이티브 C 구현 루비만 지원하는 젬이 상당 수 있었으며, 이러한 이유들로 인해 결국 JRuby는 대안에서 제외되었습니다. 루비 온 레일즈를 버리고 다른 자바 기반 프레임워크로 전면 재작성하기에는 너무 큰 소모비용과 위험요소가 있었기에 다른 방식을 고려하게 됩니다.그래서 조이는, 앞서 말한 크게 두 가지의 대안 중 두 번째, 범용 데이터 중개자를 도입하기로 결정하고, Thrift를 선택하기로 결정하였습니다. Thrift는 페이스북에서 처음 개발하였고, 현재는 아파치 재단에서 관리하고 있는 범용 RPC 프레임워크입니다. 비슷한 기능을 가진 다른 프레임워크로는 구글의 Protocol Buffer나 아파치 Avro등이 있습니다만, Thrift를 선택한 이유는 지원하는 프로그래밍 언어의 종류가 가장 다양하다는 것이었고, 워낙 많은 사용 사례가 있어 신뢰성이 검증되었다는 판단을 했기 때문입니다. Thrift는 그 규모에 걸맞게 다양한 플랫폼별 배포판을 지원하고 있으며, 조이는 현재 사용중인 하둡 시스템 관리용 Cloudera manager를 지원하는 배포판을 이용하여 디플로이하였습니다. 레일즈와의 연동도 thrift젬을 이용하여 손쉽게 할 수 있었습니다. 테스팅 결과도 문제 없었고, 이것으로 모든 것이 잘 돌아가는 줄 알았습니다.그림1. Thrift를 적용한 ZOYI Back-end SystemProblem워크인사이트는 런칭 이후 지금까지 가파른 성장세를 이어오고 있습니다. 서비스 초기에 느긋한 속도로 성장하던 적용 매장 증가 추세는, 2015년 현재 기하급수적으로 증가하는 상승곡선을 그리고 있으며, 그에 따른 시스템의 스케일 업 & 아웃 이슈도 매 달 새롭게 발생하고 있습니다.그림2. 오픈 이후 워크인사이트가 구동 중인 실제 매장 수문제없이 잘 굴러갈 것만 같았던 Thrift서비스 역시 조이의 성장세에 따라 점차 부하가 걸리기 시작했는데, 당초에 기대했던 범용 RPC 프레임워크가 보장하는 확장성과 동시성과는 조금 다른 성격의 문제가 발생하게 되었습니다. 시스템에 대규모 질의가 집중되는 시점에 병목 현상이 발생하고, 이것이 CPU와 메모리의 한도를 초과하면 그대로 다운되는 현상이었습니다. 특히 메모리 사용량이 복구되지 못하고 계속 쌓여만 가는 누수 현상이 치명적이었습니다. 게다가 이렇게 다운된 Thrift가 재시작된 경우, 레일즈와의 연결을 복구하지 못하는 현상도 비주기적으로 발생하였습니다.조이의 하둡 클러스터는 본래부터 확장성을 고려하여 설계되었기 때문에, Thrift에서 발생한 이러한 문제는 생소한 것이었습니다. 다각도에서 테스트를 해 봤지만, 처음에는 원인을 파악하기가 쉽지 않았습니다. 리부트된 클러스터도 자가 복구가 되지 않아, 개발팀이 직접 클라우데라 매니저를 주시하고 있다가 Thrift 서버의 다운 시점에 수동으로 재시작을 해 줘야 하기도 했습니다. 데이터 변환 프로토콜의 문제인지 검토해 보았으나, Thrift 프로토콜이 갖는 본질적 결함은 더더욱 아니었습니다. 자바 언어가 갖고 있는 내재적 결함도 아니었습니다. HBase가 제공하는 자바 API의 문제도 아니었습니다.하지만 심도 있는 검증 과정을 통해, Thrift의 가비지 컬렉션이 제대로 작동하지 않는 문제를 발견하였고, 이는 단순히 Thrift의 최적화의 문제가 아니라는 결론에 이르렀습니다.Dropwizard그렇게 고심하던 개발팀은, 2014년의 워크인사이트 첫 런칭 시점으로 되돌아가서 생각해보기로 합니다. 당시의 조이가 먼저 생각했던 방식은 JVM 기반의 프레임워크였는데, 자바를 이용하여 서비스 레벨도 구현하면 Thrift에서의 데이터 변환 과정에서 야기되는 문제를 원천적으로 방지할 수 있음에 다시금 주목하게 되었습니다. 많은 프로그래밍 언어간의 데이터 통신을 위해 설계된 Thrift는 사실 레일즈와 자바로 균일하게 구축된 조이 시스템에는 필요 이상으로 무겁고 큰 프레임워크였습니다. 조이가 겪은 이런 Thrift의 문제를, 해외의 여러 기업들도 경험하였고 각기 다른 방법으로 최적화를 진행한 것도 알게 되었습니다. 이렇게 된 이상 바꿀 수 밖에 없었던 것입니다.그래서 다음 대안을 찾기 위해 많은 리서치와 스터디를 진행했습니다. 넘쳐나는 프레임워크들의 홍수 속에서 가볍고 안정적이며 구현이 편리한 프레임워크를 찾기란 쉽지 않았습니다만, 결국 Dropwizard라는 자바 프레임워크를 도입하기로 결정하게 됩니다. Dropwizard는 이미 잘 알려져 있는 Spring이나 Play 등과 같은 풀 스택 자바 프레임워크와는 다른, 경량 REST API 프레임워크입니다. Dropwizard는 모듈화가 잘 되어 있고, 숙성된 자바 생태계의 안정적인 라이브러리(Jetty, Jersey, Jackson)들을 사용하였으며, 모던 자바에 걸맞는 방식(리플렉션, 동시성 등)을 사용하기 쉽게 패키징되어있습니다. 국내에는 잘 알려지지 않았지만, 해외에서는 이미 Airbnb 등 유수의 스타트업들이 실제 서비스에 사용함으로써 그 유용성을 입증하고 있는 프레임워크입니다.그림3. Dropwizard로 새로 구성된 ZOYI Back-end System다만, 처음 사용하는 프레임워크에 조이의 모든 서비스를 포팅하는 것은 불가능에 가까웠고, 설령 하더라도 엄청난 리스크를 감당할 가치가 있는 지 의문이었습니다. 레일즈가 보장해주는 빠른 기능 구현과 쉬운 배포 및 강력한 ORM 등은 루비 온 레일즈가 주는 최대의 강점이기에, 이를 포기하기는 쉽지 않았습니다. 생산성과 성능, 어느 한 쪽도 놓치고 싶지 않았습니다.그래서 조이는 두 마리 토끼를 다 잡아 보기로 결정합니다. 레일즈의 장점을 유지하면서, Dropwizard의 최대 장점인 HBase 데이터 접근의 유연성도 살릴 수 있는 방법을 찾기로 하였고, 결론적으로 Dropwizard는 기존의 Thrift가 담당하던 데이터 중개자의 역할만을 수행하게 되었습니다. Dropwizard의 잘 나뉘어진 모듈화는 이를 가능하게 해 주었습니다. 모든 모듈을 사용하면 풀 스택 프레임워크에 버금가는 규모의 시스템도 구축할 수 있지만, 필요한 것만 선택하여 사용하면 가볍고 빠르게 작동하는 미들웨어 역할도 가능한 것입니다.Asynchronous HBase, and Java 8Dropwizard가 HBase 연결에 사용한 클라이언트 모듈은 AsyncHBase입니다. Asynchronous HBase는, 타임스탬프 기반 데이터베이스인 OpenTSDB를 만든 팀이 자신들의 제품에 HBase를 연동하기 위해 기존의 HBase 클라이언트인 HTable을 대체하는 모듈을 재작성한 것으로, 완전한 비동기 방식과 넌블록킹 및 스레드 안전성 보장이 강점이라 할 수 있습니다. AsyncHBase와 Dropwizard를 연동하는 것은 매우 수월했습니다. 테스트 결과, 간결한 코드로도 초당 수 만 단위의 동시성을 안정적으로 처리할 수 있음을 검증했습니다. 조이는 OpenTSDB를 실시간 데이터 분석에 사용하고 있어, 해당 팀이 제공하는 제품의 품질과 전망에 대해 신뢰를 갖고 있었습니다. 테스트 결과는 이 신뢰를 더욱 뒷받침해주었고, 본 모듈을 Dropwizard의 HBase 연결 모듈로 선정하게 되었습니다.또한, Dropwizard와 AsyncHBase의 도입과 함께 처음으로 자바 버전 8로의 이식도 진행하게 되었습니다. 자바 8은 람다와 스트림 등 함수형 프로그래밍의 여러 기법을 대거 도입하였고, 자바 특유의 장황한 문법을 조금 더 간결하게 축약할 수 있는 장점이 있습니다. Dropwizard와 AsyncHBase 모두 자바 8과 순조롭게 연동되었으며, 이 결과에 만족한 조이는 기존의 하둡 맵 리듀스 프로젝트 역시 자바 8로 버전업하기로 결정하였습니다.PerformanceDropwizard의 성능 테스트 결과는 만족스러웠습니다. AsyncBase도 기대를 저버리지 않는 결과를 보여 주었습니다. HBase Java API와의 매끄러운 연동은, 성능 측면에서 기존과는 비교할 수 없을 정도의 향상을 보여주었고, 이 덕분에 기존 맵 리듀스 워크플로우 중 일부를 실시간 처리로 옮겨, 좀 더 유연하고 동적인 분석이 가능하게 되었습니다.다음의 비교는 Thrift와 Dropwizard의 각각의 벤치마크 테스트를 100개 동시 작업, 단위당 10000개의 요청으로 수행한 경우의 결과를 나타낸 것입니다.그림4. Thrift 테스트 시의 메모리 사용량Concurrency Level: 100 Time taken for tests: 514.315 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 32090000 bytes HTML transferred: 27600000 bytes Requests per second: 19.44 [#/sec] (mean) Time per request: 5143.151 [ms] (mean) Time per request: 51.432 [ms] (mean, across all concurrent requests) Transfer rate: 60.93 [Kbytes/sec] received Thrift 벤치마크 결과. 전체 수행에 514초가 걸렸습니다.그림5. Dropwizard 테스트 시의 메모리 사용량Concurrency Level: 100 Time taken for tests: 4.599 seconds Complete requests: 10000 Failed requests: 121 (Connect: 0, Receive: 0, Length: 121, Exceptions: 0) Non-2xx responses: 121 Total transferred: 23288000 bytes HTML transferred: 21559452 bytes Requests per second: 2174.25 [#/sec] (mean) Time per request: 45.993 [ms] (mean) Time per request: 0.460 [ms] (mean, across all concurrent requests) Transfer rate: 4944.72 [Kbytes/sec] received Dropwizard 벤치마크 결과. 전체 수행에 4초가 걸렸습니다!그림6. 초당 처리량 (높을수록 좋음)벤치마크 테스팅 시 스레드 분산이 최적화 된 경우에는 최대 100배 이상의 속도 향상이 있었습니다. Dropwizard는 기존 Thrift에 비하여 성능 향상은 물론, 안정성 면에서도 시스템이 다운된 이후에 자동 복구되지 않는 현상도 사라졌습니다. 무엇보다도 짧은 코드만으로 대규모의 질의에도 견고하고 신속하게 반응하는 서비스를 구현할 수 있다는 점이 Dropwizard의 가장 큰 장점입니다.Conclusion유용한 오픈소스 프로젝트는 시장에 너무나도 많이 널려 있습니다. 이 중에 어떤 것을 선택하는가는 소프트웨어 기업에게 중요한 안건이며, 잘못된 결정은 프로젝트 자체는 물론 회사의 생사를 결정하기까지 합니다. 조이는 적합성, 성능, 안정성, 생산성 등 모든 면에서 조이의 서비스와 알맞는 제품을 찾으려고 항상 노력하고 있으며, 가능한 한 넓고 깊은 검증, 분석 및 연구를 통해 최적의 대안을 찾아내고 있습니다. 그 결과, 이번 Dropwizard와 Asynchronous HBase를 도입하여 기존의 Thrift를 대체하는 프로젝트는 예상보다 훨씬 좋은 성과를 낼 수 있었습니다. 국내에는 Dropwizard의 실무 사용기 등을 찾아보기 힘들고, 한글화된 문서 자체도 찾기 쉽지 않은데, 이 글이 앞으로의 선택을 고민하는 분들, 드롭위자드에 관심이 있는 분들께 도움이 될 수 있으면 좋겠습니다.#조이코퍼레이션 #개발팀 #개발자 #개발환경 #업무환경 #인사이트 #경험공유
조회수 1198

[인공지능 in IT] 올림픽의 주인공은 여전히 인간이다

2011년 7월 7일 새벽 오전0시 18분, 남아프리카 공화국 더반 컨벤션센터에서 평창이라는 단어가 적힌 흰색 쪽지가 뽑힐 당시, 대한민국 국민 모두의 염원이 현실로 이뤄졌다. 88 서울 올림픽, 2002 한일 월드컵 이후 우리나라에서 열린 전세계 규모의 가장 큰 축제가 평창에서 개최됨을 알리는 순간이었다. 동계올림픽은 인종과 국가, 정치 및 이념을 초월하고 전인류의 평화와 화합을 증진시키기 위한 글로벌 겨울 축제이자 세계 젊은이들의 힘과 기록의 제전이라 할 수 있다. 1924년 프랑스 샤모니 대회를 시작으로 2018년까지 총 22회 개최, 고대 그리스의 올림피아 경기부터 시작된 '올림픽'보다 그 역사가 현저히 짧지만, 올림픽이라는 의미만은 분명하다.< 2018>올림픽에 참가하는 선수들은 그들의 육체적, 정신적인 능력의 최대치를 겨룬다. 올림픽은 인간이 가진 힘을 시험하는 장이고, 이에 관중들은 우월한 선수들의 능력에 환호성을 자아낸다. 물론, 기술이 발달하며 선수들이 입는 유니폼이나 여러 도구들이 진화를 거듭했지만, 올림픽이라는 것은 인간 본연의 모습에 큰 초점을 맞춘 시험대다. 이렇듯 'Raw'하다고 볼 수 있는 선수들의 경쟁을 비 선수 입장에서 지켜볼 수 있는 것만으로도 엄청난 기회라고 생각한다.이번 평창 동계올림픽에서 발견한 재미있는 요소 중 하나는 관중을 위한 기술의 접목이라 할 수 있다. 과학기술정보통신부는 동계올림픽과 패럴림픽 진행 중 다양한 기술을 누릴 수 있도록 '평창 ICT 올림픽 가이드북'을 발간했다. 해당 가이드북을 살펴보면, ICT 올림픽은 이번 평창 올림픽의 5대 목표 중 하나다. 인공지능, 5G, UHD, IoT, 가상현실(VR) 등을 어떻게 이용할 수 있는지 국문과 영문으로 된 가이드북을 제작하고, 평창 ICT 체험관과 인천공항 등 오프라인을 포함한 온라인 상에도 게시한다.< 2018>5대 ICT 서비스 중 가장 관심이 가는 영역은 인공지능이다. 한국을 찾는 외국인들이 느낄 수 있는 언어장벽을 완화시키기 위해 인공지능 기반 통번역서비스를 제공하고, 올림픽이 열리는 동안 경기 정보 및 교통상황을 알아보기 위해 인공지능 콜센터를 통해 24시간 문의할 수 있다. 혹자는 단순히 편의성을 위해 간단한 기술을 도입했다 할 수 있을 것이다. 하지만, 인공지능 기술은 그 자체로도 어려운 과제임과 동시에, 선수가 아닌 관람객을 위해 도입했다는 것에서 의미를 부여하고 싶다.다시 한번 언급하지만, 올림픽은 오랜 훈련을 통해 능력을 입증하고 싶은 선수들은 물론, 경기를 지켜보고 이들을 응원하는 관람객들까지 모두 '참여'하는 축제다. 한국어를 전혀 모르는 핀란드의 방송사 관계자가 통번역서비스를 활용해 아무 문제없이 스피드 스케이팅 경기를 중계할 수 있을 것이고, 대구에서 평창까지 봅슬레이 경기를 보러 가는 가족들이 편안한 운전을 위해 새벽 1시에라도 교통 정보를 얻을 수 있는 것이다.최근 인공지능을 활용한 통번역 서비스는 날이 갈수록 진화를 거듭하고 있다. 대부분의 기업에서 'NMT(Neural Machine Translation)'라고 부르는 인공신경망 기계번역을 활용하는데, NMT는 인공지능 기술을 적용해 어마어마한 양의 번역 결과 데이터를 스스로 학습하고, 다른 번역 케이스에도 적용하는 기술이다. 우리에게 많이 알려진 NMT 탑재 번역 서비스는 구글 번역과 네이버 파파고가 대표적이다. 이번 평창올림픽에서 사용되는 통번역서비스는 한글과컴퓨터가 한국전자통신연구원(ETRI)과 공동 개발한 '말랑말랑 지니톡'이다. 실제로 지니톡에는 올림픽 종목, 강원도 지역 관광지, 음식 등 동계올림픽에 관련된 데이터베이스 10만 건을 적용, 상당한 정확도를 보인다고 한다.한글과컴퓨터가 한국전자통신연구원(ETRI)과 공동 개발한 말랑말랑 지니톡, 출처: 한컴24시간 상담할 수 있는 콜센터도 인공지능은 핵심적인 역할을 담당한다. 여기서의 인공지능은 대량의 텍스트 기반 데이터를 학습하는 것이 중요하다. (1) 인식률이 높은 'DNN(Deep Neural Network)'을 토대로, (2) 한국어를 음성으로 인식할 수 있는 'STT(Speech To Text)'를 구축한 뒤(굉장히 어려운 과제다), (3) 콜센터로 들어오는 상담 내용을 분석하고, (4) 이를 제대로 학습할 수 있는 인공지능 기술을 접목해야 한다. 이 모든 것을 만족해야 콜센터에 상담을 요청하는 고객들이 인간 상담원과의 기계간의 괴리감을 적게 느낄 수 있기 때문이다.인공지능 상담 영역은 CS가 가장 활발히 이루어지는 업계 중 하나인 금융, 특히 보험 업계에서 많이 적용 중이다. AIA생명 한국지점은 인공지능을 도입해 고객과 상담하는 챗봇과 전화로 응답하는 로보텔러 서비스를 실시한다. 고객이 자주하는 문의에 대해서는 채팅 형태로 24시간 365일 인공지능 챗봇이 1차 상담을 진행하고, 대기시간 없이 바로 연결할 수 있어 생산성과 효율성, 정확도 등을 높일 수 있다. 또한, 고객에게 판매된 보험계약에 대해 로보텔러가 직접 전화를 걸어 완전 판매를 모니터링하는 업무도 진행한다. 여기는 인공지능 상담사가 학습한 대화를 기반으로 고객과 대화를 진행해 계약정보를 확인하고 계약을 확정하는 음성서비스를 적용했다.이번 평창 동계올림픽을 통해 인공지능은 그 범위를 가리지 않고 곳곳에 적용될 수 있다는 새로운 잠재력을 평가 받고 있다. 사실 최근 몇 년 사이에 IT 뿐만이 아니라 유통, 제조 등을 막론하고 기업들은 사활을 걸고 인공지능에 투자 중이다. 고도의 기술을 축적하고, 이를 적용해 새로운 사업 모델로 확장하거나, 단순반복적인 일을 대체해 비용을 줄일 수 있다는 점은, 인공지능을 하나의 패러다임으로 이끌고 있다. 물론, 이번 평창 동계올림픽에서 선보인 인공지능 기술들은 여러 기업들이 사업적인 측면을 고려한 사안일 것이다. 하지만, 인간이 주체가 되어 열리는 올림픽이라는 축제 속에서 인공지능이 한자리를 차지한다는 것은, 이제 더이상 놀랄 일은 아닐지 모른다. 결국, 인공지능도 인간의 삶을 이롭게 하기 위해 탄생된 기술이라는 것을 잊지 말자.이호진, 스켈터랩스 마케팅 매니저조원규 전 구글코리아 R&D총괄 사장을 주축으로 구글, 삼성, 카이스트 AI 랩 출신들로 구성된 인공지능 기술 기업 스켈터랩스에서 마케팅을 담당하고 있다#스켈터랩스 #기업문화 #인사이트 #경험공유 #조직문화 #인공지능기업 #기술기업
조회수 1132

테이블을 내 마음대로! 컬럼 추가와 삭제, 테이블 분리

Overview이전까지는 단일 테이블에서 INDEX를 적용하는 효과적인 방법들을 살펴봤습니다. 아직 못 본 개발자를 위해 친절히 링크도 준비했습니다. 이 글을 보기 전에 아래의 글들을 먼저 보는 것이 좋습니다.단일 TABLE을 SELECT하자!: 올바른 SELECT문 작성하기순서대로 척척, ORDER BY: ORDER BY 조건 처리 알아보기원하는 대로 뭉치는 GROUP BY: GROUP BY 조건 처리 알아보기이번 글에서는 테이블에서 컬럼을 추가 또는 삭제하고, 테이블을 분리하는 방법까지 알아보겠습니다.Let’s do it먼저 아래의 컬럼을 추가해봅시다.ALTER TABLE test.TB_MBR_BAS ADD COLUMN AREA_NM    VARCHAR(10)    COMMENT '지역 명'; 그리고 테스트 자료를 넣습니다.UPDATE test.TB_MBR_BAS SET     AREA_NM =         CASE FLOOR(RAND()*15)             WHEN 0    THEN '서울특별시'             WHEN 1    THEN '부산광역시'             WHEN 2    THEN '인천광역시'             WHEN 3    THEN '대전광역시'             WHEN 4    THEN '대구광역시'             WHEN 5    THEN '광주광역시'             WHEN 6    THEN '울산광역시'             WHEN 7    THEN '경기도'             WHEN 8    THEN '강원도'             WHEN 9    THEN '충청남도'             WHEN 10    THEN '충청북도'             WHEN 11    THEN '전라남도'             WHEN 12    THEN '전라북도'             WHEN 13    THEN '경상남도'             WHEN 14    THEN '경상북도'             WHEN 15    THEN '제주도'         END WHERE AREA_NM IS NULL ; 자료를 확인하면 아래와 같이 나옵니다.SELECT     * FROM test.TB_MBR_BAS ; AREA_NM 컬럼을 추가해 지역이 나오도록 했습니다. AREA_NM을 보면 중복되는 지역명이 있습니다. 이럴 때 보통 AREA_NM을 별도의 테이블을 만들어 ID OR 코드를 부여해 처리합니다. 위의 UPDATE 문을 참조하여 ID를 만들면 아래와 같이 만들 수 있습니다.0    : ‘서울특별시’1    : ‘부산광역시’2    : ‘인천광역시’3    : ‘대전광역시’4    : ‘대구광역시’5    : ‘광주광역시’6    : ‘울산광역시’7    : ‘경기도’8    : ‘강원도’9    : ‘충청남도’10    : ‘충청북도’11    : ‘전라남도’12    : ‘전라북도’13    : ‘경상남도’14    : ‘경상북도’15    : ‘제주도’먼저 AREA_NM과 ID를 다룰 테이블을 만들겠습니다.CREATE TABLE test.TB_AREA_BAS  (     AREA_ID        TINYINT UNSIGNED NOT NULL    COMMENT '지역 아이디 '     ,AREA_NM     VARCHAR(10)             NOT NULL    COMMENT '지역 명'     ,PRIMARY KEY (AREA_ID)  ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='TB 지역 기본' ; 테이블을 만들었으면 자료를 넣어줍니다. INSERT INTO test.TB_AREA_BAS  (     AREA_ID      ,AREA_NM  ) VALUES (0,'서울특별시')  ,(1,'부산광역시')  ,(2,'인천광역시')  ,(3,'대전광역시')  ,(4,'대구광역시')  ,(5,'광주광역시')  ,(6,'울산광역시')  ,(7,'경기도')  ,(8,'강원도')  ,(9,'충청남도')  ,(10,'충청북도')  ,(11,'전라남도')  ,(12,'전라북도')  ,(13,'경상남도')  ,(14,'경상북도')  ,(15,'제주도')  ; 자료를 확인하면 아래와 같이 나옵니다.SELECT     * FROM test.TB_AREA_BAS ; 테이블을 만들었다면 test.TB_MBR_BAS 테이블에 AREA_ID 를 추가하여 자료를 넣은 후 AREA_NM 컬럼을 삭제하면 됩니다.이제 AREA_ID를 추가합니다.ALTER TABLE test.TB_MBR_BAS ADD COLUMN AREA_ID TINYINT UNSIGNED NOT NULL COMMENT '지역 아이디'; AREA_NM을 참조하여 AREA_ID를 넣습니다.UPDATE test.TB_MBR_BAS SET     AREA_ID =         CASE AREA_NM             WHEN '서울특별시'    THEN 0             WHEN '부산광역시'    THEN 1             WHEN '인천광역시'    THEN 2             WHEN '대전광역시'    THEN 3             WHEN '대구광역시'    THEN 4             WHEN '광주광역시'    THEN 5             WHEN '울산광역시'    THEN 6             WHEN '경기도'    THEN 7             WHEN '강원도'    THEN 8             WHEN '충청남도'    THEN 9             WHEN '충청북도'    THEN 10             WHEN '전라남도'    THEN 11             WHEN '전라북도'    THEN 12             WHEN '경상남도'    THEN 13             WHEN '경상북도'    THEN 14             WHEN '제주도'    THEN 15         END ; 자료를 확인하면 아래와 같이 나오는데요.SELECT     * FROM test.TB_MBR_BAS ; 최종적으로 AREA_NM 컬럼을 삭제합시다.ALTER TABLE test.TB_MBR_BAS DROP COLUMN AREA_NM; 삭제했다면 자료를 확인해봅시다.SELECT     * FROM test.TB_MBR_BAS ; 이제 두 개의 테이블을 연결해서 조회해보겠습니다. JOIN을 사용하면 되고, Quey 문은 아래와 같습니다.SELECT     T101.MBR_ID      ,T101.MBR_INDFY_NO      ,T101.MBR_NM      ,T101.AGE      ,T101.AREA_ID      ,T102.AREA_NM FROM test.TB_MBR_BAS T101      INNER JOIN test.TB_AREA_BAS T102          ON T102.AREA_ID = T101.AREA_ID  ; 정리하며위에서 보여드린 예시는 두 가지 다른 점이 있습니다. 첫째는 TABLE 뒤에 T101, T101 과 같은 얼라이스를 준 것, 둘째는 INNER JOIN 문장이 들어간 것입니다.만약 테이블이 2개 이상이라면 사용할 테이블 컬럼을 써야 하는데 테이블명을 그대로 쓴다면 너무 길어집니다. 그래서 얼라이스로 테이블을 간단하게 표시하는 것이죠.INNER JOIN은 JOIN 중 가장 기본이 되는 문장입니다. 플랜을 보면 T101 즉 test.TB_MBR_BAS를 차례대로 전부 읽는데, 그때마다 T102인 test.TB_AREA_BAS 를 AREA_ID 를 기준으로 값을 읽습니다. T101에 해당하는 내용과 T102에 해당하는 내용을 보여주는 것이죠. 저는 Database를 쓰는 이유가 바로 JOIN 때문이라고 생각하는데요. 여러분의 생각은 어떤가요. 조금 헷갈린다면 다음에는 JOIN에 대해서 알아보도록 하겠습니다. (자연스러운 결말..!)글한석종 부장 | R&D 데이터팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 4809

“디자인과 기술을 이어주는 존재, 마크업 개발자를 함께 알아볼까요?” - 유저플로우셀 오혜진

'마크업 개발자', 아직은 우리들에게 다소 생소한 직군이죠. '마크업 개발자'는 디자이너와 개발자 사이에서 '오작교' 같은 역할을 하는 아주 중요한 포지션이에요. 오늘은 코인원의 마크업 개발자로 활약 중인 혜진님과 이야기를 나눠보려 해요. 자신의 위치에서 묵묵히 유저 친화적인 웹 환경을 만들어나가고 있는 혜진님을 만나러 가보시죠!사실 이미 혜진님은 지난 4월 13일(토), 테크 업계 여성들의 목소리에 집중하는 소중한 행사 ‘Women Techmakers Seoul 2019’에서 ‘스타트업에서 마크업 개발자로 살아남기’를 주제로 자신의 이야기를 널리 알리고 왔답니다. 스타트업 그리고 코인원에서 마크업 개발자로 살아남는 혜진님만의 방법은 무엇일까요? :-)Q. 혜진님 안녕하세요, 자기소개 부탁드립니다.안녕하세요, 코인원 유저플로우셀에서 마크업 개발자로 일하고 있는 오혜진입니다. 유저플로우셀은 암호화폐 거래와 프로차트와 같은 트레이딩 영역을 제외한 전반적인 서비스 영역을 담당하고 있어요. 특히 ‘셀'이라는 목적조직으로 개편된 이후 PM, 디자이너, 개발자가 한곳에 모여 누구나 코인원에서 거래를 하고 싶은 마음이 들도록 매력적인 곳으로 탄생시키고 있답니다. 저는 셀안에서 마크업 개발자로 일하며 디자이너와 프론트엔드 개발자를 이어주는 다리 역할을 하고 있습니다.Q. 지난 ‘Women Techmakers Seoul 2019’에서 마크업 개발자를 널리 알리는 발표를 했다고 들었어요. 어떤 내용인지 소개해주세요!감사하게도 ‘스타트업에서 마크업 개발자로 살아남기' 라는 주제로 300명이 넘는 관중들 앞에서 발표를 하고 왔습니다. (사실, 많은 분들이 와주셔서 땀이 좀 나기도 했고요;) 마크업 개발자는 스타트업에서 발견하기 힘든 직군이기도 해요. 보통은 웹 에이전시에 많이 속해 있거든요. 제가 마크업 개발자로 일한지 6년이라는 시간 동안 스타트업에서 어떤 방식으로 일해왔는지 알리고 싶었어요. 그래서 지금까지 이런 일들을 해왔고, 앞으로도 더 활발하게 할 것이라고 속시원하게 말하고 왔습니다.Q. 마크업 개발자는 구체적으로 어떤일들을 하나요?마크업 개발자는 한마디로 디자인(Design)과 기술(Tech)의 오작교 같은 존재입니다. 디자인의 의도가 개발과 충돌하는 부분은 없는지 파악하고, 개발에 잘 녹아들 수 있도록 프론트엔드의 앞단을 맡고 있어요. 코인원 웹 서비스에서 제공하는 신규 기능의 마크업 개발을 담당하고, 운영하면서 생긴 이슈들을 처리합니다. 또한 마크업 레거시에 대한 유지보수 작업도 병행하죠.예를 들어, 코인원의 회원가입 페이지를 제작할 때 디자인 작업을 먼저 들어갑니다. 그럼 디자인 작업을 바탕으로 개발자들이 기능을 만들어 넣게 돼요. 이 때, 기능적인 개발을 제외하고 UI(User Interface)적인 부분을 제가 담당합니다. 회원가입 페이지에는 이메일 인증, 휴대폰 인증 등 여러가지 개발요소들이 많아요. 그래서 개발하기 전에 기능이 들어가는 기본적인 레이아웃을 만들어 개발자에게 전달합니다. 마크업 작업이 바탕이 되어 그 위에 기능 개발이 이뤄진다고 보시면 돼요.디자이너가 레시피를 만드는 사람이라면, 마크업 개발자는 레시피 재료를 세팅해 주는 사람이에요. 개발자들은 세팅된 레시피를 끓이고 버무려 요리를 완성시키고요. 저는 좋은 요리가 탄생할 수 있도록 중간과정을 도와주는 역할인거죠. ▲ 'Women Tachmakers 2019'에서 발표에 열중한 혜진님!Q. 디자인과 기술의 중간 역할을 담당하고 계시군요, 사실 중간자의 역할이라고 하면 이어주는 과정에서 고충(?)이 생길 것 같아요.아무래도 디자이너와 개발자, 양쪽과 다 소통해야하는 부분입니다. 디자이너 입장에서는 ‘왜 프론트엔드에서 이 디자인이 안되는걸까?’ 라는 불만이 생길때도 있고, 프론트엔드에서는 ‘왜 디자인이 이렇게 들어가야 하는걸까?’ 라고 이해를 못할 때도 있어요. 서로의 이해관계를 잘 전달해야 한다는 점이 나름의 고충이죠. 코인원에서는 ‘디자이너 - 마크업 개발자 - 프론트 개발자’의 협업 프로세스를 정립해서 각자가 맡은 분야에 집중 할 수 있는 초석을 다졌어요. 무엇보다도 배경이 다른 세 개의 직군이 원활하게 소통할 수 있는 체계가 잡혀 고충이 해결되고 있습니다 :) Q. 그렇다면 마크업 개발자는 어떤 부분을 기여한다고 볼 수 있나요?코인원 메인 화면에 기능 개발을 추가하지 않고도 마크업단에서의 처리만으로도 쉽게 변화를 줄 수 있습니다. 메인화면의 배너 이미지는 유저들이 코인원에 접속해 제일 먼저 마주하는 부분이죠. 그래서 유저들이 코인원의 시각화된 정보를 빠르게 접할 수 있도록 이미지를 교체합니다. 웹 페이지의 운영 측면에서 비주얼 개편을 빠르게 할 수 있는 환경을 만들어 놓고 대응하는거에요.곧 코인원 마이페이지 화면이 개편될겁니다. 웹 페이지를 새로 만든다는 것은 무에서 유를 창조하는 과정과 같아요. 제가 마크업 개발을 잘 해놓으면 다른 직군에게도 도움이 됩니다. 개발 속도도 더 잘 붙고, 디자인에서도 빈공간이 없는 페이지가 탄생하는거죠. 최대한 밑바탕을 꼼꼼하게 만들어 모두가 일에 더 집중할 수 있는 환경을 만든다고 보시면 돼요.Q. 코인원 마이페이지에서 새롭게 바뀌는 부분은?기존의 마이페이지는 유저들이 보기에 정리가 잘 안되어있다는 느낌이 있었어요. 어떤 인증과정을 끝마쳐야 하는지 한눈에 들어오지 않는 부분이 있었거든요. 이번에 개편될 마이페이지는 좀 더 명확해졌습니다. 이전의 인증페이지가 도돌이표의 느낌이었다면, 이번에는 UX(User experience)를 생각해서 flow 개선도 많이 이뤄졌습니다. 편리한 암호화폐 거래 경험을 코인원에서 느낄 수 있어요. (새롭게 바뀔 마이페이지 많은 기대 부탁드립니다! 물론 편리한 암호화폐 거래도 언제나 코인원!)Q. 유저들에게 편리한 거래경험을 선사하기 위해 어떤 가치를 가장 중요시 여기나요? 저는 중간자이므로 유저들 뿐만 아니라 개발까지 두 가지 측면을 모두 고려합니다. 유저의 입장에서 사용성과 접근성이 용이한 마크업을 짜려고 하고, 개발측면에서는 유지보수가 편리한 마크업을 최대한 짜려고 해요. 개발하기 편한것과 사용하기 편한 것은 다른 맥락이거든요. 요새는 코인원 디자인시스템을 적용하고 있어요. 디자이너 분들이 정리해주신 디자인 시스템을 잘 적용시켜서 코드적으로도 재사용성이 용이하게 관리가 되도록 하고, UI도 정돈이 되어가는 과정을 진행 중입니다. 이런 과정을 계속 거치면 유저들에게 편리한 거래 경험을 선사하는 부분은 놓치지 않을 것 같아요.▲ 마크업에 열중하고 있는 혜진님 (약간의 설정샷 +_+)Q. 코인원 크루로 일하면서 장점을 뽑자면?유저플로우셀은 코인원이 셀이라는 목적조직으로 개편되고나서 만족도가 높은 셀이라고 알고 있어요. 업무도 많은 편인데, 톱니바퀴처럼 잘 맞물린다는 느낌이거든요. 특히 일에 대해서 선긋지 않고, 이슈가 발생했을 때 해결할 수 있는 부분들을 빠르게 파악해주는 부분들이 정말 좋아요. 속도랑 효율성 측면에서 이만큼 해낼 수 있는 팀은 앞으로 만나지 못할 것 같아요. 항상 원활한 업무 소통을 위해 힘써주시는 셀원들에게 감사 드립니다!Q. 앞으로 이루고 싶은 목표가 무엇인가요?회사 안 뿐만 아니라, 바깥에서의 활동도 꿈꾸고 있어요. 마크업 개발자들이 모두 모여 이야기할 수 있는 CSS 컨퍼런스를 열어 좀 더 커뮤니티를 활성화 시키고 영향력을 높이고 싶습니다. 아직 마크업 개발자들만이 모여서 이야기 할 수 있는 곳이 부족하거든요. 저의 이야기도 차곡차곡 쌓아서 여러 창구를 통해 들려드리고 싶고요.코인원에서는 지금 하는 것 이상으로 마크업 개발도 열심히 할거에요. 우선 단기적인 목표로, 프론트엔드에서 사용하고 있는 angular에 대한 이해력을 높일 겁니다. 마크업 컴포넌트 단위에 최적화 된 CSS로 개편해서 사용하지 않는 스타일 리소스가 최소화가 되도록 만들거에요.▲ 마크업 개발자에 많은 관심 부탁드려요 :)디자이너가 디자인에 집중할 수 있게, 개발자가 개발에 집중할 수 있게 ‘일잘러’로 통한다는 혜진님. 혜진님의 인터뷰를 통해 ‘마크업 개발자’에 좀 더 친해지는 시간이길 바라봅니다. 그리고 이렇게 멋진 코인원 크루와 함께 성장하고 싶지 않으세요?  현재 코인원은 멋진 크루들과 함께 크립토갤럭시를 헤쳐나갈 분들을 기다리고 있습니다 :-)
조회수 1380

반복적인 모니터링 프로세스 구축

IT 서비스에 장애가 발생 할 경우 모니터링 프로세스는 장애를 찾는 것으로 끝나지 않습니다. 장애를 발견하는 것은 모니터링 프로세스의 시작 점이며 최종적으로 모니터링을 통해 장애의 근본 원인을 찾아낼 수 있어야 합니다. 그리고 찾아낸 원인들은 예측과 추론에서 확인까지 이르는 하나의 프로세스로 정착되어 다시금 모니터링 과정에 포함되어져야 합니다. 이렇게 서비스를 운영하는 과정에서 근본적인 장애를 찾기 위해 모니터링을 어떻게 이해해야 하는지 알아보겠습니다. 우리가 모니터링 해야 하는 지표어플리케이션 지표(WORK METRICS)- 처리량 지표(THROUGHPUT)- 성공 지표(SUCCESS)- 에러 지표(ERROR)- 성능 지표(PERFORMANCE)시스템 지표(RESOURCE METRICS)- 가동률(UTILIZATION)- 포화상태(SATURATION)- 에러 지표(ERROR)- 이용률(AVAILABILITY)이벤트(EVENTS)- 코드 변경(CODE CHANGES)- 경고 알림(ALERTS)- 규모 변경(SCALING EVENT)- 기타(ETC)IT 서비스를 운영하는 과정에서 발생하는 문제의 근본원인을 추적하기 위한 모니터링 데이터는 크게 3가지로 나눌 수 있습니다. 어플리케이션 지표(Work metrics)서비스의 흐름(트렌젝션)을 측정하여 시스템의 최상위 레벨의 이슈를 보여줍니다. 시스템 지표(Resource metrics)이용률, 상태, 에러 또는 시스템 의존적인 리소스의 이용률을 수량화합니다.이벤트(Events)코드변경, 내부 경고, 확장 이벤트와 같이 드물게 발생하는 불연속적이 이슈를 보여줍니다.일반적으로 IT 모니터링의 핵심 이슈는 어플리케이션 지표를 통해 확인할 수 있습니다. 하지만 다른 지표들 또한 어플리케이션의 지표에서 나타난 문제의 원인을 찾기 위한 중요한 요소이기 때문에 같이 모니터링 해야 합니다. 시스템 지표를 통한 모니터링인프라스트럭쳐는 대부분 시스템의 자원으로 구성됩니다. 최상위 수준에서 유용한 작업을 하는 각각의 시스템들은 다른 시스템들과 연동하기도 하는데요. 예를 들어, 여러분의 아파치 서버가 MySQL 데이터베이스를 자원으로 사용하여 요청을 처리하는 작업을 지원할 수 있습니다. 연관된 작업을 따라 들어가보면 MySQL은 제한된 커넥션 풀을 관리하기 위한 리소스를 가지고 있고 MySQL이 실행되는 서버의 물리적인 리소스 레벨에서는 CPU, Memory, Disk 같은 지표를 보게 됩니다.어플리케이션이 서비스를 제공하는 데 있어서 각각의 리소스가 그 작업을 지원한다면 우리는 장애가 발생한 경우에, 필요한 원인을 얻는 좋은 방법을 시스템을 통해서도 찾아볼수 있습니다. 이런 프로세스를 만들어 간다면 시스템에서 발생한 경고를 통해 장애의 원인을 체계적인 조사하는데 도움이 될 것입니다. 1. 최상위 어플리케이션 지표에서 시작하기첫번째 해야 하는 질문은 "발생한 장애를 설명할 수 있는가?" 이다. 처음부터 문제를 명확하게 정의하지 못하면 이슈를 분석하기 위해 파고들어가야 하는 시스템 패스를 잃어버릴 확률이 높다.다음으로 문제가 있을 것으로 보여지는 최상위 시스템의 작업 지표를 검사해라. 이 지표들은 종종 문제의 원인을 알아내거나 또는 적어도 추적해야 하는 방향을 알려 줄 것이다. 예를 들어 성공적으로 진행된 작업의 성공율이 한계치 이하로 떨어졌다면 에러 지표를 찾아보고 반환된 에러의 형러의 타입을 살펴봄으로써 문제의 방향을 찾아나갈 것이다. 반면에, 대기시간이 길고 외부 시스템에 의해서 요청된 작업처리량이 매우 높다면 시스템 과부하로 인한 문제일 확률이 높다. 다만 와탭의 어플리케이션 분석 서비스를 사용한다면 약간 방법을 달리해도 된다. 와탭의 성능 분포도(어플리케이션 히트맵)와탭의 어플리케이션 성능 분포도를 통해 문제가 발생한 트랜잭션을 드래그하여 선택하게 되면 실제 어플리케이션에서 발생하는 스탭들을 추적하여 문제 해결에 바로 도달할 수도 있다. 하지만 더 복잡한 형태의 장애라면 시스템의 리소스 정보를 찾아봐야 합니다.  2. 리소스 찾아보기최상위 work metrics를 조사하여 문제의 원인을 알수 없다면, 다음으로 시스템이 사용하는 리소스(물리적인 요소 뿐만 아니라 시스템의 리소스 역할을 하는 소프트웨어 또는 외부 서비스)들을 조사합니다. 해당 리소스가 높다면 리소스를 사용하는 하위 Application 지표를 찾아보는 방식으로 찾아나갑니다. 와탭의 데시보드(CPU, MEMORY)3. 변경 내용 찾아보기다음으로 지표에 연관된 경고와 다른 이벤트들을 살펴봅니다. 문제가 발생하기 직전 코드가 릴리즈 되었거나, 내부 경고가 발생하고나 다른 이벤트가 등록되었다면 문제와 연관된 부분을 찾아봐야 합니다. 4. 수정하기 (잊지 말기)문제의 원인을 찾았다면 문제의 원인이 되는 상태를 수정해보고 증상이 사라지는 것을 확인합니다. 증상이 더이상 나오지 않는다면 향후 유사한 문제를 피하기 위해 시스템을 어떻게 변경할지 고민해야 합니다.  서비스가 중단된 상황이 오면 1분이 중요합니다. 문제를 찾는 속도를 높이기 위해 눈앞에서 벌어진 상황에 대한 높은 집중력을 유지하면서 대쉬보드를 상황에 맞춰 재 조정합니다. 최상위 어플리케이션 데쉬보드와 각각의 서브시스템들을 위한 대시보드를 하나씩 설정합니다. 시스템 대시보드는 시스템 지표의 하위 시스템의 키 메트릭스와 함께 어플리케이션 메트릭을 확인 할 수 있어야 합니다. 이벤트 데이터도 이용가능한 상황이라면 연관 분석 차트에서 관련된 이벤트가 올라가 있어야 합니다. 와탭의 알림 서비스정리하기   서비스에 장애는 무조건 발생하지만 우리는 모니터링을 통해 빠르게 해결 할 수 있습니다. 이를 위해 표준화된 모니터링 프로세스를 만들고 대시보드로 연관관계를 만들어 놓는다면 문제를 빠르게 추적 조사할 수 있습니다. 가능하면 모든 지표는 어플리케이션 지표에서 부터 찾을 수 있도록 대시보드를 구성합니다.인프라스트럭처를 통해서도 문제를 분석할 수 있습니다. 시스템에 대해 대시보드를 설정하고 주요 지표들을 올려놓아야 합니다. 문제의 원인을 조사하는 것은 증세가 나타나는 최상위 시스템에서 부터 시작합니다. 문제가 되는 리소스가 발견되면 문제를 발견하고 수정할 때가지 리소스에서 발견되는 패턴을 조사하고 적용시키는작업을 반복해야 합니다. #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지

기업문화 엿볼 때, 더팀스

로그인

/