스토리 홈

인터뷰

피드

뉴스

조회수 9090

왜 SQLite 에서 Realm 으로 옮겼는가?

SQLite 와 Realm잔디 앱은 2015년 중반부터 앱 내에 Offline Caching 기능이 포함되면서 본격적으로 Local-Databae 를 사용하기 시작했습니다.당시에 Realm 과 SQLite 를 검토하는 과정에서 다음과 같은 사유로 Realm 을 포기하였습니다.1.0 이 아직 되지 않은 미성숙된 상태의 라이브러리사용 사례에서 리포팅되는 버그들 (CPU 지원 등)Data 의 상속을 지원하지 않는 문제Robolectric 미지원 (안드로이드 팀 당시 테스트 프레임웍은 Robolectric 이었으며 현재 Android Test Support Library 입니다.)위의 문제로 인해 SQLite 를 선택하였고 여러 SQLite-ORM Library 를 검토한 후 ORMLite 를 선택하였습니다.누구보다 가볍고 빠르게2016년 6월경 앱의 핵심 데이터에 대해 개선작업이 되면서 그에 따라 기존의 Cache Data 로직도 많은 부분이 변경되었습니다. 그에 따라 실시간성으로 DB 를 대상으로 Read-Write 동작이 발생하게 되었습니다. Locking 등에 대한 처리가 되면서 성능에 대한 이슈가 계속적으로 발생할 수 밖에 없었습니다.간헐적인 성능 이슈는 사용자에게 나쁜 UX 로 다가갈 수 있기 때문에 다음과 같은 병목지점들에 대해 성능 향상을 꾀하였습니다.서버와의 통신 향상비지니스 로직 개선내부 DB 로직 향상서버와의 통신 향상병목 지점이 되는 것으로 판단되는 API 를 찾아 원인을 분석하여 개선요청을 서버팀에서 개선할 수 있도록 하였습니다.비지니스 로직 개선불필요한 객체 생성, 비동기로 처리해도 되는 동작들에 대해서는 로직 수정, 최소한의 검증 후에만 앱 실행, 네트워크 동작 최소화, 캐싱 활용 등 다양한 전략을 시도하였습니다.내부 DB 로직 향상SQLite 를 대상으로 빈번한 쿼리 작업을 최소한으로 하기 위해 2~3개의 쿼리로 이루어진 부분에 대해서 최소한의 쿼리만으로 동작하도록 여러 시도를 하였습니다.ORMLite 의 한계점ORMLite 를 대상으로 여러가지 시도를 하였습니다. 쿼리를 최소한으로 하고 1:N, N:M 동작에 대해서 로직 중간에 Query 가 발생하지 않도록 애초에 Join Query 를 하도록 하는 등 여러가지 전략을 시도하였으나 궁극적으로 ORMLite 자체에 대한 성능을 개선하는 것은 불가능하다는 결론이 도출하였습니다.여러 시도를 하였으나 고작 10~20% 정도의 성능향상밖에 없었으며 이는 사용자 관점에서 여전히 느릴 수 있다고 느끼기 충분한 수준이었습니다. 기존에 목표했던 100ms 이하의 쿼리를 기대하기엔 어려운 상황이었습니다.그래서 GreenDAO, Requery 라이브러리를 검토하였습니다.GreenDAO 의 문제점GreenDAO 를 검토하는 과정에서 겪은 가장 큰 문제점은 실제 Object 코드에 GreendDAO 코드가 생성이 붙으면서 유지보수에 큰 걸림돌이 될 수 있다는 것이 예상되었습니다.Requery 의 문제점성능면에서 ORMLite 에 비해서 큰 개선을 가져오지 못했습니다. Requery 는 JPA 를 가장 잘 채용한 것으로 알려져 있지만 그렇다고 SQLite 자체의 성능을 극적으로 개선했다고 보기엔 어려운 부분들이 있었습니다.SQLite vs RealmSQLite 가 가진 자체적인 성능 이슈를 SQLite 기반 라이브러리 범위안에서는 개선할 수 없다는 결론에 도달하였습니다.검토 방법 : 기존의 Object 를 대상으로 ORMLite 와 Realm 을 대상으로 성능을 검토합니다.데이터는 1:N / 1:1 관계가 되어 있는 여러 Object 의 집합으로 구성되어 있다.Database 에서 데이터를 가져올 때는 Eager Loading 방식으로 택한다.Write : 20회, Read : 20회 를 수행했고 그에 대한 평균 성능을 비교한다. SQLiteRealm성능 향상Write4039ms1142ms3.5xRead6010ms2450ms2.5x(Realm 의 벤치마크 정보와 너무 상이하여 재테스트한 결과 수정하였습니다.)위의 비교차트에서 봤듯이 Realm 은 무시무시한 성능이 입증되었습니다.도입 검토시에 Realm 버전은 2.0 이었기 때문에 충분히 신뢰할 수 있을 만큼 성숙되었다고 판단하고 최종적으로 도입을 결정하였습니다.Realm 도입 과정에서 문제점Realm 을 도입한다고 해서 여전히 잠재적인 문제가 해결된 것은 아니었습니다.파악된 다음 문제를 해결 해야 했습니다.Primitive 타입에 대해 Collection 저장을 지원하지 않는다.RealmObject 에 대한 호출 Thread 를 유지해야 한다.상속을 지원하지 않는다.Primitive 타입에 대한 Collection 관리를 해결하기이 문제는 ORMLite 에서 이미 겪었기 때문에 의외로 쉽게 구할 수 있었습니다. long, int 등에 대한 Wrapper 를 만들고 Json Convert 등의 과정에서 Post Processing 과정에서 Wrapper 로 데이터를 이관하도록 처리하였습니다.// example class Data extends RealmObject { private transient List refs; private List refIds; } class RealmLong extends RealmObject { private long value; } RealmObject 에 대한 호출 Thread 분리Realm 은 Object 에 대해 query 후 객체를 받는다 하더라도 실제로 객체 내 데이터르 접근할 때는 다시 Query 로 접근하기 때문에 실제로 Object 전체에 대해서 Eager Loading 방식으로 접근해야 합니다.Jandi 는 싱글톤 객체를 통해 데이터베이스에 접근하며, Background Thread 에서 진행하고 UI Thread 에서 객체 내 변수에 접근해서 UI 에 그리는 작업이 빈번하기 때문에 Thread 독립을 반드시 해야했습니다.Realm 에서는 Eager-Loading 을 지원하고 있습니다. Realm.copyFromObject() 를 사용하면 Return 값이 Eager-Loading 된 Object 가 반환됩니다.단, Realm 의 가장 큰 특징이로 보는 ZeroCopy 를 포기하는 것이기 때문에 신중하게 생각해야 합니다.// example public Chat getChat(long chatId) { return execute((realm) -> { Chat it = realm.where(Chat.class) .equalTo("id", chatId) .findFirst(); if (it != null) { return realm.copyFromRealm(it); } else { return null; } }); } 상속을 지원하지 않는다.가장 큰 문제였는데 해결방법을 찾을 수 없어 결국 상속을 포기하고 모든 Data 를 1개의 Object 에 표현하기로 하였습니다.위의 3가지 문제를 이렇게 해결해서 안드로이드팀에서는 1차적으로 도입을 완료하였습니다.결론현재까지 Realm 전환에 있어서 성공적인 도입으로 판단되어 차후에 다른 데이터에 대해서도 하나씩 DB 이전을 할 예정입니다.Realm 은 이제 충분히 신뢰할 수 있을만큼 성숙되었다고 생각이며 Realm 에서 처음부터 강조하던 성능또한 믿기 어려울 정도로 빨라졌습니다. 더 빠른 Mobile Database 를 원하신다면 Realm 을 적극 추천합니다.#토스랩 #잔디 #JANDI #개발 #개발환경 #업무환경
조회수 2171

CSS animation으로 프로토타이핑하기

들어가며Framer, Flinto, Origami, Invision. 많은 프로토타이핑 도구가 존재합니다. 디자인에 활력을 불어넣고 개발팀과의 커뮤니케이션을 위해 필수라고 하는 프로토타이핑. 어떻게 하기는 해야겠는 데 어려운 도구나 코드르 공부하기엔 시간이 없고, 막상 열심히 공부하면 새로운 버전이 나오고, 더 좋은 도구가 나오고. 이런 경험을 많이 하셨을 겁니다. 프로토타이핑 도구로 멋지고 완결된 시나리오를 가진 결과물을 만들 수도 있습니다. 하지만 우리에게 당장 필요한 것은 지금 당장 떠오르는 아이디어를 보여줄 수 있는 아이콘의 간단한 모션, 쓱 움직이는 화면 전환 같은 것이 아닐까요? 오늘 배워서 바로 쓸 수 있는 CSS animation으로 하는 간단한 프로토타이핑 방법을 소개합니다.https://codepen.io/yunkimoon/embed/preview/BZEYgY?default-tabs=css,result&embed-version=2&height=600&host=https://codepen.io&referrer=https://blog.stibee.com/media/c7c8adfdea76b3b98829ecce41fee7d7?postId=e5bb1630afb5&slug-hash=BZEYgY<iframe data-width="800" data-height="600" width="700" height="525" data-src="/media/c7c8adfdea76b3b98829ecce41fee7d7?postId=e5bb1630afb5" data-media-id="c7c8adfdea76b3b98829ecce41fee7d7" data-thumbnail="https://i.embed.ly/1/image?url=https://s3-us-west-2.amazonaws.com/i.cdpn.io/1370087.BZEYgY.small.f06b1cb1-09d2-4285-b8b5-eb8f5b9cea7a.png&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://blog.stibee.com/media/c7c8adfdea76b3b98829ecce41fee7d7?postId=e5bb1630afb5" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 525px;">어디서, playground코딩을 공부하려면 텍스트 에디터도 설치해야 하고, 각종 패키지도 설치해야 합니다. 또한, 결과물이 담길 파일도 생성해야 하고, 여러 파일이 연결되니까 폴더 구조도 고민해야 하죠. 이런 고민을 하다 보면 시작조차 하기 싫어집니다. 그래서 브라우저에서 바로 작성하고 확인하고 공유할 수 있는 온라인 코딩 플레이 그라운드가 있습니다. 대표적으로 jsbin과 codepen이 있습니다. 그냥 해당 서비스에서 가서 각 섹션(html 또는 css)에 맞게 코드를 입력하기만 하면 됩니다. 우리는 html과 css섹션만 사용할 예정입니다. js와 같은 다른 섹션은 최소화(minimize)해주세요.codepen.io어떻게 시작할까html에 내용을 담고, css에 디자인(스타일)을 담을 겁니다. 당장 직접 작성하기는 어려우니 예제(https://codepen.io/yunkimoon/pen/BZEYgY)의 html과 css코드를 그대로 복사합니다. 코드의 주석(회색글씨)을 확인해 봅니다. 요약하면 아래와 같습니다.가장 바깥의 파란 배경 상자이미지와 그걸 담고 있는 상자파란 배경 상자에 hover(마우스 오버 이벤트)를 하면,left 포지션을 2%에서 80%로 변경여기서 중요한 건 .box상자에 설정된 transition이라는 속성입니다. transition은 딱딱한 움직임을 부드럽게 해줍니다. 여기서는 position left를 2%에서 80%로 부드럽게 바꿔주었습니다. 위치뿐만 아니라 색상(color, background), 크기(width, height)도 자연스럽게 바꿔주는 속성입니다. “all 3s”라는 값을 가지고 있는데 “모든 변경사항에 대해 3초 동안 움직여라”라는 의미입니다.꼭 알아야할 3가지css 애니메이션의 맛을 잠깐 보았습니다. transition을 통해 부드러운 움직임을 줄 수 있습니다. 하지만 더 복잡하고 멋진 움직임을 위해서는 많은 속성들을 이해하고 응용할 수 있어야 합니다. 하지만 모든 속성을 다 알아볼 수는 없으므로 가장 중요한 3가지를 알아보도록 하겠습니다. 미리 살펴본 transition과 transform, keyframe(s) 입니다.1. transition위에서 살펴본 것처럼 대상의 위치, 크기, 색상 등에 부드러운 움직임을 줍니다.2. transform대상의 위치, 크기, 방향 등을 상대적으로 변경합니다. 예제를 통해 알아보겠습니다.<iframe data-width="800" data-height="600" width="700" height="525" data-src="/media/43617ca3eab01b6f86f50b25a362c5a1?postId=e5bb1630afb5" data-media-id="43617ca3eab01b6f86f50b25a362c5a1" data-thumbnail="https://i.embed.ly/1/image?url=https://s3-us-west-2.amazonaws.com/i.cdpn.io/1370087.BZErpP.small.5ebe332d-41b1-4a16-8253-6e2df7b347d0.png&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://blog.stibee.com/media/43617ca3eab01b6f86f50b25a362c5a1?postId=e5bb1630afb5" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 525px;">2.1. rotate대상에 각도 값을 설정합니다. 즉, 주어진 값만큼 회전합니다. 첫 번째 예제와 조금 다른 부분은 :hover { }에 작성된 내용입니다. transform:rotate(360deg)에서 rotate는 회전을 뜻하고, 360deg는 각도입니다. 즉, 360도(한 바퀴)만큼 회전하라는 의미입니다. 미리 transition이 걸려있었기 때문에 부드럽게 회전하는 모습을 볼 수 있습니다.2.2. translate대상의 이동 값을 설정합니다. 주어진 값만큼 이동합니다. 값은 좌푯값으로 x축, y축 값을 나눠서 줍니다. transform: translate(100px, 100px)에서 translate는 이동을 뜻하고, 이후에 나오는 값은 순서대로 x축의 이동값, y축의 이동 값입니다. 그런데 y축 이동 값이면 위로 올라가야 할 것 같은데, 그림은 아래로 이동합니다. 그 이유는 스크린에서 좌측 위가 기준점이기 때문입니다.2.3. scale대상의 크기를 설정합니다. 즉, 주어진 값만큼 늘어나거나 줄어듭니다. 값은 가로 값, 세로 값을 차례로 줍니다. transform:scale(1.5, 2)에서 scale은 크기를 뜻하고, 1.5와 2는 각각 가로값, 세로값을 뜻합니다. 가로는 1.5배가 늘어나고 세로는 2배가 늘어납니다. 그래서 그림은 세로로 긴 비율로 보입니다.이제 우리는 css만으로 대상의 위치, 크기, 회전 애니메이션을 줄 수 있습니다 :)3. keyframes마우스 오버 액션에 대한 애니메이션을 보아왔습니다. 이렇게 사용자의 특정 반응(마우스 오버)이 없어도 자동으로 움직이도록 할 수는 없을까요? 앞의 두 예제보다 조금 복잡하지만 keyframes를 사용하면 가능합니다. keyframes는 미리 움직임을 지정해두고, 대상에 해당 애니메이션의 속성을 부여하는 방식으로 작성됩니다. 예제를 확인해 보겠습니다.<iframe data-width="800" data-height="600" width="700" height="525" data-src="/media/fc6ef62f3a79def6442479e60dcba75d?postId=e5bb1630afb5" data-media-id="fc6ef62f3a79def6442479e60dcba75d" data-thumbnail="https://i.embed.ly/1/image?url=https://s3-us-west-2.amazonaws.com/i.cdpn.io/1370087.vZMRdd.small.22bea369-dda5-4454-9f16-f5ad68f9b292.png&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://blog.stibee.com/media/fc6ef62f3a79def6442479e60dcba75d?postId=e5bb1630afb5" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 525px;">3.1. spin앞서 살펴 본 transform의 rotate를 미리 애니메이션을 만들어 놓고 대상에 animation이라는 속성을 설정했습니다.@keyframes spin 처름 spin이라는 애니메이션을 설정합니다. 그 안에는 from과 to가 있는데 각각 시작과 끝을 뜻합니다. 즉, 시작할 때는 회전이 0(rotate(0deg))이고 끝날 때는 회전이 360도(rotate(360deg))입니다.대상과 keyframes를 연결할 때는 대상에 animation: spin 8s infinite linear;와같이 애니메이션 속성을 줍니다. spin은 keyframes의 이름, 8s는 8초 동안, infinite는 무한 반복을 뜻합니다. 여기서 linear는 easing을 나타내는데, 우선은 조금 딱딱한 애니메이션이라고 해둡시다.3.2. leftAndRighttransform의 translate를 활용해서 우측으로 이동했다 돌아오는 애니메이션을 반복시키는 예제입니다. from과 to대신 조금 상세한 타임라인을 가지고 있습니다. 0%, 50%, 100%는 타임라인을 구성하는 속성들로 전체 애니메이션 시간 동안 해당하는 타이밍에 맞게 속성이 변경됩니다. 역시 infinite 속성이 있어 계속 반복되고 있습니다. 그리고 마지막에 linear대신 ease라는 속성을 넣어서 조금 부드러운 움직임 표현했습니다.3.3. hideAndShow앞서 다루지 않은 opacity(투명도)를 활용했습니다. 1이 100%이고 0은 보이지 않는 상태입니다. 1 → 0 → 1을 반복하며 보였다 안 보였다 하는 애니메이션을 보여줍니다.이제 우리는 css만으로 대상의 위치, 크기, 회전 애니메이션 반복적으로 사용할 수 있게 되었습니다. 그리고 무한 반복 애니메이션도 만들 수 있습니다.마무리 예제<iframe data-width="800" data-height="600" width="700" height="525" data-src="/media/f95d4317209e7a3488242568bbdcd5a3?postId=e5bb1630afb5" data-media-id="f95d4317209e7a3488242568bbdcd5a3" data-thumbnail="https://i.embed.ly/1/image?url=https://s3-us-west-2.amazonaws.com/i.cdpn.io/1370087.OgeMEY.small.ab075079-b3bb-443e-a11e-d707c5a6a198.png&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://blog.stibee.com/media/f95d4317209e7a3488242568bbdcd5a3?postId=e5bb1630afb5" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 525px;">앞서 살펴본 예제들을 활용한 마무리 예제를 만들어 보았습니다. 앞서 공부한 내용을 바탕으로 소스를 분석해 보시기 바랍니다. 각 버튼에는 transiton으로 부드러운 hover 전환 효과를 주었고, 녹색의 메시지는 keyframes를 주어 상하로 계속 움직이도록 했습니다. frame에 마우스가 올라가면 메시지는 프레임 바깥으로 밀려나고 사용자 메뉴가 프레임 안으로 이동하도록 했습니다. 메뉴는 하위 메뉴가 펼쳐지는 인터렉션을 가지고 있습니다.마치며전문 프로토타이핑 도구보다 결과물이 투박하고, 지금 당장 만들 수 있는 장면도 제한적입니다. 자바스크립트 같은 동적 언어가 들어가 있지 않아 표현할 수 있는 화면도 많지 않습니다. 기본적으로 제공되는 템플릿이나 자원이 없으므로 하나하나 html로 코딩하거나 공개 소스를 넣어가면서 만들어야 하는 수고로움도 존재합니다.하지만 실행만 해도 막막한 도구들을 바라보며 “언제 한 번 해보나”하는 생각을 할 시간에 간단히 익혀 한 번이라도 써먹을 수 있다면 그 자체로 의미가 있지 않을까요? 물론 탄탄한 시나리오와 설계를 가지고, 제대로 만든다면 전문 프로토타이핑 도구보다 절대 뒤쳐지지 않을 것입니다. 그리고 우리가 만든 코드들은 커뮤니케이션을 위한 전달용이 아니고 실제로 쓰일 수도 있는 코드라는 점에서도 의미가 있습니다. 간단한 프로토타이핑이라도 지금 시작해 보면 어떨까요?참고https://www.w3schools.com/css/css3_animations.aspttps://www.w3schools.com/css/css3_transitions.asphttps://www.w3schools.com/css/css3_2dtransforms.asphttp://report.stibee.com/2017 by 조은지 디자이너#슬로워크 #스티비 #CSS #퍼블리셔 #개발 #디자인 #인사이트 #꿀팁 #조언
조회수 1139

iOS Passbook의 signpass 분석

Passbook이란?iOS 6에서 새로 추가된 Passbook은 여러 장의 디지털 티켓, 쿠폰, 매장 카드를 담고 편리하게 이용할 수 있는 애플리케이션입니다. Passbook 안에 들어가는 티켓, 쿠폰, 매장 카드들을 Pass라고 부릅니다. Pass는 기존 앱보다 손쉽게 개발, 배포할 수 있으며, 위치나 시간에 따라 최적의 사용 시간에 맞춰 알림을 주기 때문에 더 높은 사용성을 보장할 수 있습니다. 이번 블로그 글에선 Pass를 만드는 과정에서 이용되는 signpass의 소스코드를 분석해보도록 하겠습니다.signpass?signpass는 애플이 Pass 개발에 이용할 수 있게 제공한 툴킷으로, 형식에 맞춰 개발된 Pass 디렉터리를 인증서와 함께 하나의 파일(*.pkpass)로 묶어주는 프로그램입니다. Xcode Project 형태로 소스코드와 함께 Passbook Materials에 포함되어있습니다. Passbook Materials는 애플 개발자 계정이 있으면 무료로 다운받으실 수 있습니다.왜 분석하는가?signpass가 하는 일은 Pass를 만들어서 배포하는 데 있어 필수적인 과정이지만, 많은 Pass 배포환경이 리눅스 운영체제 위에 각자 고유의 서버 시스템 위에 구축될 것이므로, 해당 커맨드 라인 툴을 그대로 이용하기보단 기반 서비스 플랫폼에 맞춰서 새로 구현해야 할 필요가 있습니다. 다행히 signpass가 하는 일은 간단하며, 애플도 signpass를 주석이 포함된 소스코드 형태로 배포하였기 때문에 어렵지 않게 분석할 수 있습니다. 이 글은 signpass를 분석해야 할 분들에게 더욱 편하게 처리 과정을 이해할 수 있도록 돕고자 합니다.signpass의 처리 과정signpass는 크게 3가지 일을 수행합니다.파일명과 hash를 key/value 형태로 담은 manifest.json을 제작manifest.json을 인증서로 인코딩한 signature 제작기존 파일과 앞의 1, 2 번에서 제작한 파일을 함께 zip 압축signpass에는 이 외에도 validation 기능과 몇 가지 커맨드 라인 처리 기능이 포함되어있지만, pkpass를 만드는 과정만 보고 싶으시다면 PassSigner.m 의 +(void)signPassWithURL:(NSURL *)passURL certSuffix:(NSString*)certSuffix outputURL:(NSURL *)outputURL zip:(BOOL)zip 메서드만 참조하시는 것만으로 충분합니다.manifest.json 제작형식에 맞춰 제작된 *.pass 디렉터리의 파일을 훑으면서 json 형태로 저장합니다. key는 파일이름, value는 파일 내용에 대한 SHA1 해싱이 들어가며 ([fileData SHA1HashString]), 완성된 json 파일은 보통 아래와 같은 내용을 하게 됩니다.{ "[email protected]" : "bd5442b4b08aa4dde333ec9ef0269e7fd93140b3", "icon.png" : "ba47a8021c8d74d2146d7244c8a0566be37df43b", "pass.json" : "1cdbac541c1736420e7fbd7455c98d0735a71a9e", "logo.png" : "780540b3a324bf66aeaee2d352283371356e9502", "[email protected]" : "a718ffd4e611e404dd3eb701454bcaefdabbe311" } signature 제작manifest.json 파일을 애플에서 받은 Pass 인증서를 이용해 인코딩합니다. (CMSEncodeContent()) 인증서를 통해 manifest를 인코딩함으로써, 쿠폰 발급자가 아닌 다른 사용자가 임의로 Pass를 편집하는 것을 방지합니다. 인코딩된 파일은 signature라는 이름의 파일로 Pass의 Top-level 디렉터리에 manifest.json과 함께 저장합니다.CMSEncodeContent는 PKCS #7 기반의 Cryptographic Message Syntax RFC 3852 표준으로, 해당하는 과정을 다른 플랫폼에 포팅할 때 표준 문서와 애플 인증서 형태를 함께 참조하시기 바랍니다.zip 압축위의 두 파일과 나머지 파일들을 모두 포함하여 zip파일로 압축합니다. 단순한 과정이므로 부가적인 설명은 생략합니다. :-)마치며signpass의 구현 과정을 이해하였다면, Pass 배포 서비스를 구축 시 기반 플랫폼 의존도가 낮은 시스템을 구축할 수 있습니다. 또한, 이 분석을 통해, 애플이 Pass를 어떠한 방식으로 안전하게 보호하는지를 이해할 수 있으므로, 여러 회사에서 Pass의 도입 여부를 고민할 때 보안 측면에서 좋은 참고 자료가 될 것으로 기대합니다.#스포카 #개발 #개발자 #iOS #iOS개발 #앱개발 #Signpass #인사이트
조회수 1869

파이썬의 개발 “환경”(env) 도구들

안녕하세요. 스포카 프로그래머 홍민희입니다.파이썬 패키징 생태계에서 개발 환경을 구성하기 위해 널리 쓰이는 virtualenv나 pyvenv, virtualenvwrapper 같은 각종 도구가 왜 필요한지 (또는 자신에게는 큰 도움이 안 되는지) 알려면 그 이전의 파이썬 라이브러리 배포 방식에 대한 이해가 많은 도움이 됩니다. 여기서는 필요한 몇 가지 역사적 사실과 파이썬 패키징 개념 중 현재의 생태계 이해에 필요한 것들을 위주로 정리하고, 최종적으로 각자의 필요에 따라 어떤 도구를 활용하면 될지 지침을 제안합니다.sys.path패키징이고 뭐고 아무것도 없던 90년대 말에는 라이브러리 소스 코드 파일들을 타르볼(tarball)로 압축해서 배포했습니다. 쓰는 사람은 그걸 자신의 애플리케이션 소스 트리 안에 풀어서 사용했습니다.파이썬에는 지금도 sys.path라는 인터프리터 전역적인 상태가 존재합니다. PATH 환경 변수가 실행 바이너리를 찾을 디렉터리 경로들의 목록인 것과 비슷하게, sys.path도 import foo를 하면 foo.py (또는 foo/__init__.py) 파일을 찾을 디렉터리 경로들의 목록을 담습니다. 그리고 기본 동작으로 그 목록의 맨 처음에는 현재 디렉터리(./)가 들어갑니다. 따라서 라이브러리 타르볼을 애플리케이션 소스 트리에 풀어두면 import해서 쓸 수 있습니다.하지만 자신이 작성한 애플리케이션 코드와 남이 작성한 라이브러리 코드를 같은 소스 트리에서 관리하는 것은 여러모로 불편합니다. 따라서 라이브러리는 애플리케이션 소스 트리와는 별도의 디렉터리(예: ../libs/)에 풀어서 관리하고, 애플리케이션 소스 코드 맨 위에 아래와 같이 써두는 패턴이 많았습니다.import sys sys.path.append('../libs') 또는 sys.path를 소스 코드를 건드리지 않고 조작하기 위해 PYTHONPATH 환경 변수를 활용하는 경우가 많았습니다.세기말, 파이썬 1.5를 쓰던 때의 이야기입니다.site-packages새 천 년이 밝았고 파이썬 2.0이 나왔습니다. 표준적인 라이브러리 배포 방식 및 설치 방식이 제안되었고, 표준 라이브러리에 distutils도 들어왔습니다. (지금도 setuptools는 distutils에 의존하고, pip는 setuptools에 의존합니다.) 제안된 방식은 이랬습니다.애플리케이션 코드가 아닌 라이브러리 소스 코드는 모두 /usr/local/lib/pythonX.Y/site-packages/ 디렉터리 안에 둡니다. X.Y는 파이썬 인터프리터 버전이고, 경로는 인터프리터를 빌드할 때 (./configure) 정합니다. 데비안 계열은 site-packages 대신 dist-packages라는 이름으로 바꿔서 빌드하는 등, 파이썬 인터프리터의 설치 방식에 따라 달라집니다. 어떻게 정하든 이를 site-packages 디렉터리라고 부릅니다. 파이썬 인터프리터를 빌드할 때 경로가 결정되므로, 파이썬 인터프리터 별로 각자의 site-packages 디렉터리를 갖게 됩니다. (한 시스템에서 여러 파이썬 버전을 설치했을 때 pip 역시 pip2.7, pip3.6 등과 같이 버전 별로 명령어가 생기는 것도 같은 이유입니다.)기본적으로 sys.path 목록에는 맨 앞에 현재 위치(./), 뒤쪽에는 site-packages 경로가 들어있습니다. import를 하면 현재 위치에서 찾고, 없으면 site-packages를 찾아본다는 뜻입니다.표준 라이브러리의 distutils.core.setup() 함수는 라이브러리 파일들을 시스템의 site-packages 디렉터리에 복사해주는 함수입니다. 라이브러리 타르볼 파일 맨 바깥에는 이 함수를 이용해 라이브러리를 시스템 site-packages에 설치해주는 스크립트를 setup.py라는 파일명으로 포함하는 관례가 있었습니다. pip 같은 게 없던 때에는 라이브러리 타르볼을 받아서 푼 다음 python setup.py install 명령을 실행하는 것이 일반적인 라이브러리 설치법이었습니다. 지금도 pip는 *.whl 파일이 아닌 *.tar.gz/*.zip 파일인 패키지를 설치할 때 내부적으로 python setup.py install 스크립트를 실행합니다.참고로 이때 정립된 파이썬 패키징 표준은 리눅스에서 쓰이는 dpkg나 RPM 같은 일반적인 패키징 방식을 의식하며 만들어졌습니다.1 당시는 도커는 커녕 가상화 자체가 보편적이지 않던 때로, 한 시스템에 여러 애플리케이션을 함께 설치해서 쓰는 멀티테넌시 환경이 일반적이었기 때문입니다.workingenv파이썬으로 작성한 애플리케이션 여럿이 한 시스템에 설치되면 공통으로 의존하는 라이브러리의 버전을 결정하는 게 문제가 됩니다. A 애플리케이션은 foo >= 1.0.0, < 2>에 의존하고 B 애플리케이션은 foo >= 1.5.0에 의존하면 시스템에 설치할 수 있는 foo의 버전은 >= 1.5.0, < 2>으로 한정됩니다. 만약 C 애플리케이션을 설치하려는데 foo > 2.0.0에 의존한다면, A나 C 중 하나는 포기해야 합니다.시스템에 파이썬 애플리케이션을 단 하나만 설치한다 해도, 설치하는데 시스템 관리자 권한이 필요하다는 것도 문제였습니다. 일반적으로 site-packages 디렉터리는 시스템 관리자만 수정할 수 있고 나머지는 읽기만 가능한 /usr 아래 어딘가로 정해졌기 때문입니다. 이를 우회하려고 사용자가 시스템에 설치된 파이썬 인터프리터를 쓰지 않고 직접 파이썬 인터프리터를 빌드해서 사용하는 편법도 쓰였습니다.이런 문제를 해결하기 위해, 애플리케이션·프로젝트마다 별도의 site-packages 디렉터리를 두는 방식이 제안됐습니다. 나중에 virtualenv을 만들게 되는 이안 비킹이 그 전신인 workingenv를 만들어 이 아이디어를 실현했습니다. 현재의 virtualenv 사용 방식은 workingenv에서 만들어진 것입니다.애플리케이션마다 별도의 “환경”(env)을 만듭니다.애플리케이션을 실행하기 전에 우선 그 “환경”을 “활성화”(. bin/activate 또는 Scripts\activate.bat)합니다.workingenv가 만들어주는 활성화 스크립트는 PATH와 PYTHONPATH 환경 변수를 재정의하여 시스템에 설치된 파이썬 인터프리터의 실행 바이너리 디렉터리 및 site-packages 디렉터리를 가리키는 대신, “환경” 내의 bin/ 및 site-packages 디렉터리를 바라보도록 해줍니다. 이안 비킹은 이렇게 분리된 실행 파일들(bin/)과 site-packages 등을 묶어서 “환경”이라고 명명했는데, workingenv 이후로 파이썬 패키징 및 배포 분야에서 이 용어가 정착됩니다.최근에 만들어진 신생 언어의 패키지 관리자는 대부분 파이썬과 달리 애플리케이션·프로젝트마다 별도의 환경을 두고 설치되는 경우가 많습니다. 예를 들어 npm은 -g 옵션을 일부러 켜지 않는 한 현재 디렉터리를 기준으로 ./node_modules 디렉터리에 라이브러리를 설치하게 되어 있고, 별도의 “활성화” 없이도 노드 인터프리터가 해당 경로에서 라이브러리를 찾습니다. 하지만 파이썬의 패키징 표준은 앞서 언급한 것처럼 멀티테넌시 환경이 일반적이었던 시대에 만들어졌고, 또 많은 라이브러리가 실행 파일도 함께 제공하기 때문에2 PYTHONPATH 뿐만 아니라 PATH 환경 변수도 재정의해야 해서 activate 과정이 필요합니다.workingenv는 파이썬 웹 프로그래머 사이에서 빠르게 퍼지기 시작했습니다. 웹 애플리케이션은 정통적인 CLI 및 GUI 애플리케이션과 달리 FHS 표준 같은 것에 크게 구애될 필요가 없었고, 웹 애플리케이션의 배포도 점차 가상화 기술을 통해 완전히 격리된 시스템에 설치되는 식으로 보안 문제에서 많이 자유로워졌기 때문입니다.무엇보다 workingenv는 프로그래머가 여러 프로젝트를 동시에 작업하는 경우 골치 아팠던 라이브러리 버전 충돌 문제를 우회했기 때문에, 배포 도구보다는 개발 도구로 정착되는 면이 컸습니다.virtualenv이안 비킹은 PYTHONPATH를 조작하여 별도의 site-packages 공간을 두는 workingenv의 방식이 복잡하게 패키징된 기존 라이브러리 및 프로젝트에서 호환되지 않는 문제로 골머리를 썩이다, 아예 PYTHONPATH를 이용하지 않는 방식으로 새 도구를 만듭니다.새로운 방식은 아예 파이썬 인터프리터 실행 바이너리를 복사한 뒤, sys.path 기본값에 박힌 시스템 site-packages 경로를 환경 내 site-packages 경로로 바꿔버리는 것이었습니다. 이러한 동작 원리의 차이는 이용자 입장에서 크게 중요한 것은 아닙니다.하여튼 이안 비킹은 virtualenv라는 이름으로 새 도구를 만들었고, workingenv를 빠르게 대체했습니다.virtualenvwrapper앞서 언급한 것처럼, workingenv와 그 후계자인 virtualenv는 저자의 의도와 무관하게 애플리케이션 배포보다는 개발 용도로 더 널리 쓰입니다. 파이썬 프로그래머가 새로운 프로젝트를 시작할 때는 항상 “환경”도 생성합니다. 또 개발을 시작할 때마다 “활성화” 과정도 거칩니다. 너무나 반복적이기 때문에 당연히 이를 자동화하는 도구도 만들어졌습니다. virtualenvwrapper는 바로 그런 목적으로 만들어진 bash/zsh/fish 스크립트 모음입니다.여러 단축 명령을 제공하지만, 핵심 기능은 다음의 두 가지입니다.A라는 프로젝트 작업을 시작할 때마다 cd ~/projects/a; . .venv/bin/activate라고 쳐줘야 했던 것을 workon a 명령으로 줄여줍니다.프로젝트 디렉터리마다 .venv/ 또는 .env/ 등의 이름으로 환경 디렉터리를 생성해두고 버전 관리 시스템에서는 제외되도록 .gitignore 목록에 해당 디렉터리를 넣었어야 했습니다. 예를 들어 ~/projects/a/.venv/, ~/projects/b/.venv/ 같은 식이었습니다.virtualenvwrapper를 쓰면 환경 디렉터리들을 일정한 위치로 모아줍니다. 위치는 기본값이 없으며 virtualenvwrapper 설치할 때 WORKON_HOME 환경 변수를 통해 입맛대로 정할 수 있습니다. 예를 들어 WORKON_HOME을 ~/.virtualenvs/ 디렉터리로 정했다면, 프로젝트별 환경은 ~/.virtualenvs/a/, ~/.virtualenvs/b/ 같은 식으로 저장됩니다.pyvenv파이썬 3.3부터는 virtualenv가 아예 파이썬에 내장됐습니다. 환경을 만드는 명령어는 virtualenv가 아닌 pyvenv로 좀 다르지만, 그 이후의 과정은 같습니다. 파이썬 3만 사용한다면 이제 virtualenv를 따로 설치할 필요가 없어진 것입니다.참고로 아래에서 설명할 pyenv와는 다른 도구입니다. 철자의 “v”에 주의해주세요.pyenv애플리케이션을 개발할 때는 하나의 파이썬 버전을 정하면 되지만, 라이브러리는 여러 파이썬 버전과 호환되어야 합니다. 그러다 보니 라이브러리 개발자는 여러 버전의 파이썬을 시스템에 동시에 설치할 필요가 있습니다. 데드스네이크스 PPA나 데드스네이크스 홈브루 탭 같은 것을 이용해서 설치할 수도 있지만, 보통은 pyenv를 많이 씁니다.pyenv는 동시에 여러 버전의 파이썬을 시스템에 설치해주며, 이렇게 설치된 파이썬은 시스템의 패키지 시스템(데비안·우분투의 APT나 맥OS의 홈브루 등)을 통해 설치되는 것이 아니라, pyenv가 다운로드와 빌드 및 설치를 직접 하여 별도로 관리합니다. 설치된 파이썬들은 PEP 394에 따라 일정한 형식으로 이름지어진 명령어(예: python2.7, python3.6)로 실행할 수 있게 됩니다.또한, 여러 파이썬 버전 중에 하나의 시스템 기본 파이썬 버전도 선택 가능하며, 특정 프로젝트 디렉터리 안에서만 기본 파이썬의 버전이 달라지게 할 수도 있습니다.pyenv-virtualenvpyenv가 여러 파이썬 버전을 동시에 설치해주기는 하지만, 그렇다고 자동으로 site-packages가 프로젝트마다 격리되는 것은 아닙니다. 예를 들어 pyenv로 파이썬 3.6을 설치한 뒤, 파이썬 3.6으로 두 프로젝트를 한 시스템에서 개발할 경우 두 프로젝트는 시스템 site-packages를 함께 쓰게 됩니다.따라서 pyenv를 쓰더라도 virtualenv는 따로 써야 하는데, 따로 사용할 수도 있지만 pyenv-virtualenv를 쓰면 pyenv virtualenv 명령으로 프로젝트에 쓸 파이썬 버전 지정과 가상 환경 생성을 한 번에 할 수 있게 됩니다.비슷하게 pyenv와 virtualenvwrapper를 통합해주는 pyenv-virtualenvwrapper 같은 도구도 있습니다.마치며여러 파이썬 개발 환경 관리 도구를 소개했지만, 여기 있는 모든 도구를 꼭 써야 하는 것도 아니고, 가장 최근에 나온 도구로 하루빨리 갈아타야 하는 것도 아닙니다. 글을 쓴 저 자신도 pyenv 같은 도구가 나온 지 몇 년이나 지났고 주변에서 쓰는 사람이 많음에도 쓰지 않고 있습니다. virtualenvwrapper를 대체하는 Pipenv 같은 실험적인 방식3도 생겨나고 있지만, 어느 쪽이든 동시에 여러 파이썬 프로젝트를 작업하는 사람이 아니라면 굳이 쓸 필요가 없는 도구입니다. 각자의 용도에 따라 필요한 수준의 도구를 이용하면 됩니다. 2017년 10월 현재, 아래의 지침으로 정리할 수 있겠습니다.파이썬 프로그래머가 아니지만, 파이썬 애플리케이션을 설치해서 이용합니다.시스템에서 제공하는 패키지 관리자(APT나 홈브루 등)를 통해 애플리케이션을 설치하세요.파이썬 프로그래머가 아니지만, 파이썬 애플리케이션을 유난히 많이 이용합니다.pipsi를 이용해 파이썬 애플리케이션을 설치하는 것을 권합니다.파이썬 프로그래머이고, 하나의 애플리케이션을 개발합니다.파이썬 3.3 이상을 이용할 경우 pyvenv로 개발 환경을 만들어서 개발하세요. 그 이전의 파이썬 버전을 이용할 경우 virtualenv를 활용하세요.파이썬 프로그래머이고, 여러 애플리케이션을 개발합니다.virtualenvwrapper를 활용하세요.파이썬 프로그래머이고, 여러 애플리케이션을 다양한 파이썬 버전으로 개발합니다.pyenv-virtualenvwrapper를 활용하세요.파이썬 프로그래머이고, 라이브러리를 개발합니다.pyenv와 tox를 활용하세요.파이썬으로 만든 애플리케이션을 distutils를 통해 패키징한 뒤, RPM 기반의 리눅스 배포본 용으로 python setup.py bdist_rpm 명령을 통해 *.rpm 파일을 제공하기도 했습니다. 이를 통해 애플리케이션을 설치할 경우, 각 파일들은 리눅스 FHS 표준과 해당 시스템 설정에 따라 흩어지게 됩니다. ↩예를 들어 파이썬에서 가장 많이 쓰이는 국제화 라이브러리인 바벨은 pybabel 명령어를, 구문 강조 라이브러리인 파이그먼츠는 pygmentize 명령어를, 장고는 django-admin 명령어를 제공합니다. ↩저는 2017년 4월에 한 번 써보았으나, 아직은 실무에서 쓰기에는 이르다는 결론을 내렸습니다. 이에 관한 그때의 제 감상은 별도의 글로 다루었습니다. ↩#스포카 #파이썬 #개발팀 #개발자 #인사이트 #후기 #일지
조회수 1059

[사람이 서비스다] #4 JD, 안드로이드앱 개발 담당

셀잇은 기존 중고거래 시장에서 이용자들이 겪는 불편과 불안감을 해소하기 위해 등장한 서비스라는 자부심을 가지고 구매자와 판매자를 잇는 접점이 되고자 합니다. 이를 위해 서비스를 기획하고 실행하는 저희 구성원들에 대한 이야기를 간간히 들려드리고자 합니다. 좋은 서비스든 아이디어든 결국 사람이 하는 일이니까요-저희가 어떤 생각을 품고 어떤 마음가짐으로 살아가는지에 대해 진솔하게 풀어보고자 합니다. 이 청년들의 이야기, 한 번 들어보실래요? Interviewee: JD (제이디, 개발팀 / 안드로이드앱 개발 담당)Interviewer: Austin (오스틴, 마케터)  우선 자기소개부터 간단히 해주시죠. 흔해 빠진 소개일랑 집어치우고! 최대한 자신을 우리에게 알려봐요! 정~ 뭐라고 쓸지 모르겠으면 자기 이름으로 삼행시라도 해보세요. 우선 저에게 이런 귀찮은 일을 안겨준 브라이언에게 감사의 인사를 전하는 바입니다. 덕분에 독무대에 이어 다시 한번 불면증에 시달리게 되었어요. 그건 브라이언에게 개인적으로 앙갚음(?)을 해주시고, 본인 소개부터 해주세요. 저도 바쁘답니다. 안녕하십니까? 저는 전남 해남의 작은 시골 마을에서 2남 중 장남으로 태어나 안드로이드 개발을 하고 있는 JD라고 합니다. 원래는 게임 개발이 하고 싶어서 프로그래밍 공부를 시작하였지만 어쩌다 보니 앱을 개발하고 있네요. (뭐, 뭐지? 이 ‘신입사원의_패기.wav’ 같은 느낌은?) 그럼 현재 셀잇에서 개발자로 일하시겠군요. 그럼 본인이 하는 일 중에서 이건 나만의 스페셜티다! 하는 부분은 무엇인가요? 당연히 안드로이드 개발입니다. 우리 회사에서 저 밖에 못하는 거죠~(찡긋) (찡...찡긋?) 하하하;; 네네 그렇군요. (셀잇이 잘 되는 이유가 이거였군. 정상적인 놈이 없는...) 그게 다인가요? 개발하시다가 잘 안풀리거나 열 받을 때는 어떻게 하나요? 자세한 건 ‘영업비밀’이니까- 전 안풀리면... 음- (한참을 생각한다)잠을 잡니다. (역시 오늘도 산으로 가는건가…) 아…(포기한 듯) 얼마나 자나요? 한 20분 정도 짧게 자요. 사실 잔다기보다는 자는 척을 하면서 생각을 하는거죠. 읭? 굳이 자는 척을 해야 될 필요가 있나요? 그냥 대놓고 생각하면 안되는건가요? 안됩니다! 온전한 집중을 위해서 자는 척을 해야 해요. (정적) 인터뷰 하는 중에 월드시리즈까지 끝나버렸네요... 올해 모든 야구가 끝나버렸어요 ㅠ (후우... 내가 이걸 왜 시작했을까...) 그럼 일 얘긴 그만하고(더 할 수도 없겠어;;) 업무 외의 시간에는 주로 뭘 하시나요? 듣자하니 야구를 좋아하는 것 같은데- 야구를 봅니다. 한국 야구는 기아를 응원하고, 메이저리그는 한국 선수들이 진출한 팀들을 응원하고 있어요. 주말에는요? 주말이면 아침에 일어나서 메이저리그 두 경기 정도 보고 오후에는 한국 야구를 보면서 하루를 보냅니다. 이제 야구 시즌도 다 끝나서 다가오는 겨울이 두렵습니다ㅠ 차라리 야구선수로 전향하시는게- 만약 실력이 문제라면 사회인 야구팀이라도 해보시는건요? 그건 돈도 많이 들고, 일단 귀찮고-부상 위험도 크고, 일단 귀찮고-그냥 친구랑 캐치볼 하는 것으로 만족합니다. 그리고 일단 귀찮고- 커피나 한 잔 하실래요? 커피나 마시면서 다른 얘기로 넘어가죠~ 괜찮습니다. 저는 카페인 마시면 안되서- 아, 그럼 그냥 계속 하죠. (여자랑은 술 마시고 나랑은 커피도 안 마시냐?-_- 쳇, 근데 이해되네...) 중고에 대해서 어떻게 생각하세요? 중고를 바라보는 가치관 같은게 있으시면 말씀해 주세요. 제가 환경 문제에 관심이 많습니다. 중고거래가 보편적으로 활성화 된다면 상대적으로 공산품의 생산량이 줄어들게 되고, 이는 지구의 자연 환경에 도움이 되지 않을까 합니다. (응? 뭔가 익숙한데?) 제가 예전에 쓴 글을 보신건가요?… 네~ 꼭 중고 거래가 활성화되서 지구 환경을 지켜주세요… 그럼 마지막으로 셀잇에서 이루고 싶은 것은 무엇인가요? 주로 컴퓨터 부품들을 중고 거래를 이용해 구매했던 적이 있는데요. 항상 직거래를 했지만 정상 작동하는지 불안했던 기억이 있습니다. 집에 와서 컴퓨터에 장착해 보고서야 안심을 하곤 했었는데, 셀잇을 이용하면 최소한 이런 걱정 없이 믿고 안심하며 거래할 수 있는 서비스로 자리 잡을 수 있었으면 합니다. 아니 이건 셀잇이 나아갈 방향이고~ 저는 제이디 본인 개인의 목표에 대해서 물은거예요. 셀잇이 곧 저입니다. 화, 화이팅...! (후우...) 이런 자리가 부끄럽죠? 가슴 속에 뜨거운 뭔가가 있는게 보이지만 굳이 밝히지 않으시겠다면 앞으로 안드로이드 앱을 통해 그 뭔가를 제가 찾아보겠습니다. (빨리 끝내려 애쓴다;;) 인터뷰는 이 정도로 마치는 것으로 하고~ 셀잇에서 칭찬하고 싶은 사람 한 명만 꼽아주세요. 이유도 함께 말해주세요. 전 네이쓴을 칭찬하고 싶습니다. 특유의 친화력과 유머러스함으로 주변 사람들을 기분 좋게 만들어주는 아주 훌륭한 팀원이기 때문입니다. 로봇입니까? 네? 아닙니다. 그럼 오늘 수고하셨습니다. 아! 최근에 셀잇 앱 2.0이 배포됐는데 감회가 남다를 것 같 같은~ 어떠세요? 딱히 이렇다할 소감은 없습니다만 이용자분들이 이전보다 더 편하게 서비스를 이용할 수 있었으면 하는 바람입니다. (로봇 맞네...) 넵- 수고하셨습니다. (하아... 네이쓴이라... 다음엔 우주로 가겠구만...)#셀잇 #번개장터 #인터뷰 #팀소개 #팀인터뷰 #팀원소개 #기업문화 #조직문화 #회사문화 #사내문화
조회수 3228

사물인터넷(IOT) 란? 

안녕하세요?크몽(www.kmong.com) 개발자SEAN입니다.오늘은 요즘 말이 많이 나오고있는 IT용어중에서 사물인터넷(IOT)에 대해서 적어 봅니다. 위키피디아의 사물인터넷의 정의는 '사물 인터넷(Internet of Things, 약어로 IoT)은 각종 사물에 센서와 통신 기능을 내장하여 인터넷에 연결하는 기술을 의미한다. 여기서 사물이란 가전제품, 모바일 장비, 웨어러블 컴퓨터 등 다양한 임베디드 시스템이 된다. 사물 인터넷에 연결되는 사물들은 자신을 구별할 수 있는 유일한 아이피를 가지고 인터넷으로 연결되어야 하며, 외부 환경으로부터의 데이터 취득을 위해 센서를 내장할 수 있다.모든 사물이 해킹의 대상이 될 수 있어 사물 인터넷의 발달과 보안의 발달은 함께 갈 수밖에 없는 구조이다.'라고 정의가 되어있습니다.예를 들자면 아침에 집에서 알람이 울리면 그와 동시에 토스트기계가 반응하여 저절로 빵이 구워진다든지 집을 나서기 직전 문앞에서 오늘의 날씨를 알려준다든지, 모든 전자기기에 접목을 시킬수있습니다. 또다른 예를 들자면 카페의자에 센서를 달아서 카페마다 자리가 몇자리 남았는지 몇명이 있는지 등을 파악하여 굳이 찾아가지 않아도 내가 쉽게 자리가 있는 카페를 찾을 수 있습니다.  사물인터넷은 매우 좋지만 쉽게 대중화 되지 못하는 점은 위의 정의와 같이 보안에 매우 취약합니다.예를 들어 위의 카페의 정보를 잘못 보이도록하여 그 카페에 못가게 한다든지, 집안의 기계를 다른사람이 조종을 한다든지의 개인 프라이버시 침해가 발생 할 수있다는 것이겠지요. 그리고 또다른 문제점으로는 비용이 많이 든다는 겁니다. 이 점도 무시를 할 수없겠지요. 조그마한 장비라도 여기저기 붙여야하고 그 기기를 연결도 시켜야하고 쉬운 문제는 아닐 듯합니다.언젠가는 대중화 되는 날이 있겠지요?저도 기대해봅니다.이상 크몽 개발자 SEAN이었습니다.#크몽 #개발자 #개발팀 #팀원소개 #기업문화 #조직문화
조회수 4879

웹서버 로그 수집과 모니터링 설정

우리는 고객이 무엇에 관심 있어 하고 무엇에 관심 없어하는지, 어떤 것을 보았을 때 클릭해 들어가고 어떤 것을 보았을 때 사이트에서 이탈하는지 궁금해 합니다. 이러한 정보를 얻기 위해 봐야 할 것은 역시 웹서버의 접속 로그입니다.처음에는 매일 생성되는 로그 파일을 일일이 파싱해서 원하는 정보를 DB에 쌓는 방법을 이용했지만, 이러한 방식은 한계가 있었습니다. 저장할 수 있는 데이터의 양에 심각한 제한이 있었고, 따라서 처음에 얻고자 했던 데이터 이상의 것을 새로 추출할 수도 없었습니다.그래서 지금은 웹서버 로그를 하둡(Hadoop) 클러스터에 쌓고 있습니다. Google Analytics 같은 외부 분석툴을 사용하기도 하지만, 아무래도 데이터를 우리 손에 직접 들고 있는 것이 더 유연한 분석을 제공할 수 있지요. 클러스터에서 로그를 분석하려면 가장 먼저 로그 수집 시스템을 만들어야 합니다.이번 포스팅에서는 이 로그 수집 시스템이 어떻게 만들어져 있는지, 그리고 그보다 더 중요한 시스템의 모니터링을 어떻게 하고 있는지 설명하려고 합니다.Flume 에이전트 설정하기Apache FlumeApache Flume은 로그와 같은 데이터의 흐름(streaming)을 제어할 수 있게 해주는 도구입니다. 단순하면서도 확장성 높은 구조로 되어 있기 때문에 많은 시스템에서 채택하는 도구가 되었고, 리디북스에서도 Flume 을 사용하게 되었습니다.Flume 의 기본 구조는 단순합니다.기본적인 에이전트 구성 (이미지 출처: Apache Flume 홈페이지)에이전트(agent)는 Source, Channel, Sink 로 이루어진 자바 프로세스이다.소스(source)는 외부에서 이벤트를 입력받아 채널(channel)로 전달하고, 채널은 이벤트를 저장하고 있다가 싱크(sink)로 전달한다. 싱크는 이벤트를 외부로 출력한다.한 에이전트의 Sink와 다른 에이전트의 Source가 같은 타입이면, 에이전트 간에 이벤트를 전달할 수 있다.굉장히 간단하지만 강력한 모델입니다. Flume 은 Avro, Thrift, Exec, HDFS, Kafka 등 다양한 라이브러리를 적용한 소스와 싱크를 미리 제공하고 있기 때문에, 사용자는 자기 입맛에 맞게 이를 조합해서 시스템을 구성할 수 있습니다.예를 들면 아래와 같습니다.좀 더 복잡한 에이전트 구성 (이미지 출처: Apache Flume 홈페이지)초기 에이전트 구성: Avro를 통해 클러스터에 로그 전송저희가 맨 처음 설정한 Flume 에이전트의 구성은 다음과 같습니다.초기 에이전트 구성각 웹서버ExecSource: exec 명령으로 실행된 프로세스의 표준 출력을 이벤트로 입력받음. (tail -F <로그파일>)MemoryChannel: 메모리상의 큐(queue)로 구현된 채널AvroSink: 클러스터에 상의 에이전트가 실행하는 Avro RPC 서버로 이벤트를 전송하둡 클러스터AvroSource: 웹서버의 에이전트가 Avro RPC 로 보내는 이벤트를 수신MemoryChannelHDFSSink: HDFS 상의 지정된 경로의 파일에 이벤트 내용을 출력각 웹서버에는 에이전트가 하나씩 실행되어서, 로그 파일에 새로 추가되는 로그를 클러스터에 전송합니다. 클러스터 상의 에이전트는 단 한 개 존재하는데, 웹서버로부터 전송받은 로그를 HDFS(Hadoop File System) 에 파일로 출력하는 역할을 합니다. 웹서버 에이전트와 클러스터 에이전트 간의 통신은 Avro RPC 로 하게 하였습니다. Flume 에서 기본적으로 AvroSource 와 AvroSink 를 구현하여 제공해 주는 것을 이용했습니다.사실은 클러스터 상의 에이전트가 Avro 서비스를 통해 데이터를 모아 주지 않고, 웹서버 상의 에이전트가 HDFSSink 를 이용해서 직접 클러스터에 파일을 쓰게 하더라도 대부분의 경우는 상관없습니다. 하지만 리디북스의 경우는 그렇게 할 수 없었는데, 왜냐하면 웹서버와 하둡 클러스터가 서로 다른 네트워크 상에 있기 때문입니다.리디북스의 웹서버는 국내 IDC에 존재하지만 하둡 클러스터는 Miscrosoft Azure 클라우드 내의 가상머신으로 실행되고 있습니다. 따라서 하둡의 네임노드(namenode)가 인식하는 각 노드의 사설 IP 주소를 웹서버들이 쉽게 접근할 수 없습니다. 이를 우회하는 다양한 방법을 시도해 보았지만 최종적으로는 Avro 서비스를 중간에 두어 해결하였습니다.모니터링 알람 설정하기JSON 리포팅 사용다음은 에이전트 프로세스를 모니터링하는 문제가 있었습니다. 예기치 않은 에러로 에이전트가 종료되어서 로그가 수집되지 않고 있는데 며칠 동안 모르고 있어서는 안되겠지요.Flume 에서는 모니터링 인터페이스도 여러가지를 제공하고 있는데, 그 중 가장 이용하기 간편한 것은 HTTP 를 통한 JSON reporting 이었습니다. 에이전트 자체가 HTTP 서비스로 작동해서, 특정 포트로 요청을 보내면 에이전트의 상태를 JSON 으로 정리하여 응답을 주게 되어 있습니다. 에이전트 실행시에 옵션 몇 개만 추가하면 바로 설정할 수 있기 때문에 매우 간단합니다.Health 페이지를 이용한 모니터링그런데 이 리포팅이 제대로 나오지 않으면 어떻게 알림을 받을 수 있을까요? 각 서버마다 JSON 리포팅을 요청해서 응답이 제대로 오지 않으면 이메일을 보내는 스크립트를 만들어서 cron 으로 5분마다 실행하는 방법도 있습니다. 하지만 이 스크립트가 제대로 동작하지 않거나, 이게 실행되는 서버가 다운되면?결국 스스로를 믿지 못하고 택한 방법은 외부 서비스 Pingdom을 이용하는 것이었습니다. 단, 외부 서비스가 각각의 웹서버에 직접 접근하여 리포팅을 요청하는 방식은 보안상 문제가 될 수 있어서 아래와 같이 보완하였습니다.웹 서비스 상에 health 페이지 구현. 이 페이지는 각 웹서버의 에이전트의 JSON reporting 포트로 요청을 보내서, 결과를 종합해서 다시 JSON 으로 보여줌.모든 에이전트가 정상적으로 리포트를 보내면 {“status”: “OKAY”} 를, 아니면 {“status”: “ERROR”} 를 보여줌.이 health 페이지의 내용을 모니터링하도록 Pingdom 설정. {“status”: “OKAY”} 가 응답에 없으면 알람 메일이 오도록 함.{ "status": "OKAY", "metrics": { "192.168.0.101": { "SOURCE.log_src": { ... }, "SINK.avro_sink": { "BatchCompleteCount": 562110, "ConnectionFailedCount": 294, "EventDrainAttemptCount": 56246850, "ConnectionCreatedCount": 31, "Type": "SINK", "BatchEmptyCount": 16, "ConnectionClosedCount": 30, "EventDrainSuccessCount": 56243927, "StopTime": 0, "StartTime": 1459135471379, "BatchUnderflowCount": 610 }, "CHANNEL.mem_channel": { ... } }, "192.168.0.102": { ... } } }Health 페이지의 Json내용JSON 리포팅의 문제이렇게 설정해 놓고, 며칠간 로그가 HDFS 상에 잘 수집되는 것을 확인하고 만족해 했습니다. 그런데 며칠간 신경을 쓰지 않은 사이, 다시 에이전트를 확인해 보니 모든 웹서버 에이전트가 죽어 있었습니다. HDFS에 로그도 쌓이지 않았구요.확인해 보니, MemoryChannel 의 설정 문제였습니다. byteCapacity 값을 실수로 너무 작게 설정해서, 채널 큐가 메모리 부족으로 터져나간 것이죠. 해당 문제는 byteCapacity 값을 늘려서 간단하게 해결했습니다.문제는 알람이 오지 않았다는 것이었습니다. 문제를 재현해 본 결과, 채널이 터져서 에이전트 실행이 중단되어도, 에이전트 프로세스는 죽지 않고 ExecSource 에서 실행한 자식 프로세스(tail -F)만 죽어 있었습니다. 이렇게 되면 JSON 리포팅도 정상적으로 나오기 때문에, 결국 JSON 리포팅으로는 이런 유형의 에러를 잡지 못한다는 결론이 나왔습니다.클러스터에 모니터링 설정하기결국 웹서버상에서 모니터링하는것 보다는 데이터를 최종 전달받는 하둡 클러스터 상에서 모니터링하는 것이 안정적이라 판단하였습니다. 다행히도, 하둡 클러스터에서 사용할 수 있는 꽤나 좋은 모니터링 도구가 이미 있었습니다.CDH 의 알람 트리거리디북스에서는 기본 하둡 패키지가 아닌, Cloudera에서 제공하는 하둡 배포판인 Cloudera CDH를 사용하고 있습니다. CDH는 클러스터 상에서 사용되는 서비스마다 각종 테스트를 자동으로 실행하여, 테스트가 통과되지 않을 때마다 메일로 알람을 보내줍니다. 그리고 웬만한 필수 테스트는 기본적으로 설정되어 있지만, 사용자가 커스텀 서비스를 직접 제작할 수도 있습니다. CDH가 각 에이전트의 소스, 채널, 싱크마다 초당 전송한 이벤트 개수 등의 측정치(metric)을 모두 기록하고 있기 때문에, 이 값들이 일정 수준 이상/이하가 될 때마다 알람이 트리거되도록 설정할 수 있습니다.CDH의 알람 트리거 편집 화면웹서버마다 알람 설정하기그런데 이것으로 끝이 아닙니다. 클러스터 에이전트는 각 서버에서의 트래픽이 모두 모이는 곳이기 때문에, 여기에서 모니터링을 하는 것은 웹서버 상에서 모니터링하는 것보다 기준이 애매해집니다.10대의 웹서버 중에 한 대만 문제가 생겼을 경우, 클러스터 에이전트가 받는 트래픽은 0으로 줄어드는 것이 아니라 90%로 줄어듭니다. 알람을 트리거하는 역치(threshold)를 평소 트래픽의 90%로 잡아야 한다는 것이지요. 그런데 트래픽이라는 것이 원래 날짜와 시간에 따라 달라지기 때문에, 이 역치값을 고정된 값으로 정할 수가 없습니다. 트래픽이 높은 때를 기준으로 하면, 트래픽이 낮아지는 새벽 시간마다 가짜 알람(false alarm)이 오게 되겠지요. 그렇다고 트래픽이 낮은 때를 기준으로 하면, 트래픽이 높은 때 웹서버 에이전트가 죽더라도 새벽이 될 때까지 알 수 없습니다.결국 클러스터 단에서도 각 웹서버마다 트래픽을 구분해 주어야 한다는 결론이 나옵니다. 다행히 한 에이전트가 여러 개의 채널과 싱크를 가질 수 있고, 이벤트 헤더의 내용에 따라 소스가 어느 채널로 이벤트를 보낼지 결정해 주는 채널 셀렉터 (Channel Selector)라는 것이 있습니다.웹서버 에이전트의 소스에서는 각 이벤트 헤더에 자기 호스트명을 달아 준다. (Interceptor 는 각 이벤트에 원하는 헤더를 달아주는 역할을 한다. HostInterceptor 이용)클러스터 에이전트는 1개의 소스와, 웹서버 대수만큼의 채널 및 싱크가 있다.클러스터의 소스는 이벤트의 host 헤더를 보고 그에 해당하는 채널로 이벤트를 전달한다. (MultiplexingSelector 사용)각 채널은 자신에게 대응되는 싱크에 이벤트를 전달하고, 싱크는 각자의 HDFS 경로에 이벤트를 파일로 출력한다.최종 에이전트 구성: 채널 셀렉터로 트래픽 나누기최종적으로 나온 에이전트의 구성은 다음과 같습니다.최종 에이전트 구성그리고 에이전트 설정 파일은 아래와 같이 작성했습니다.... log_to_avro.sources.log_src.type = exec log_to_avro.sources.log_src.command = tail -F /path/to/log/file log_to_avro.sources.log_src.restart = true log_to_avro.sources.log_src.channels = mem_channel log_to_avro.sources.log_src.interceptors = ts_ic host_ic # 호스트 인터셉터 설정 log_to_avro.sources.log_src.interceptors.ts_ic.type = timestamp # 이벤트 헤더에 timestamp 삽입 (날짜별 구분을 위해) log_to_avro.sources.log_src.interceptors.host_ic.type = host # 이벤트 헤더에 호스트명 삽입 (호스트별 구분을 위해) log_to_avro.sources.log_src.interceptors.host_ic.useIP = true # 호스트명 대신에 IP 사용 log_to_avro.channels.mem_channel.type = memory log_to_avro.channels.mem_channel.capacity = 10000 log_to_avro.channels.mem_channel.transactionCapacity = 10000 log_to_avro.channels.mem_channel.byteCapacityBufferPercentage = 20 log_to_avro.channels.mem_channel.byteCapacity = 10485760 log_to_avro.sinks.avro_sink.type = avro log_to_avro.sinks.avro_sink.channel = mem_channel log_to_avro.sinks.avro_sink.hostname = hostname.of.cluster.agent log_to_avro.sinks.avro_sink.port = 4141 ...웹서버 에이전트 설정파일... avro_to_hdfs.sources.avro_src.type = avro avro_to_hdfs.sources.avro_src.bind = 0.0.0.0 avro_to_hdfs.sources.avro_src.port = 4141 avro_to_hdfs.sources.avro_src.channels = c_101 c_102 avro_to_hdfs.sources.avro_src.selector.type = multiplexing # Multiplexing Selector 설정 avro_to_hdfs.sources.avro_src.selector.header = host # 호스트 이름으로 채널 나누기 avro_to_hdfs.sources.avro_src.selector.mapping.192.168.0.101 = c_101 # 192.168.0.101 에서 온 이벤트는 c_101 채널로 avro_to_hdfs.sources.avro_src.selector.mapping.192.168.0.102 = c_102 # 192.168.0.102 에서 온 이벤트는 c_102 채널로 # 채널 c_101 설정 avro_to_hdfs.channels.c_101.type = memory avro_to_hdfs.channels.c_101.capacity = 10000 avro_to_hdfs.channels.c_101.transactionCapacity = 10000 avro_to_hdfs.channels.c_101.byteCapacityBufferPercentage = 20 avro_to_hdfs.channels.c_101.byteCapacity = 10485760 # 싱크 k_101 설정 avro_to_hdfs.sinks.k_101.type = hdfs avro_to_hdfs.sinks.k_101.channel = c_101 avro_to_hdfs.sinks.k_101.hdfs.fileSuffix = .log.gz avro_to_hdfs.sinks.k_101.hdfs.path = hdfs://namenode/path/to/logs/dir/%Y%m%d/%{host} # 날짜별, 호스트별로 다른 디렉토리에 avro_to_hdfs.sinks.k_101.hdfs.rollSize = 104857600 avro_to_hdfs.sinks.k_101.hdfs.rollInterval = 7200 avro_to_hdfs.sinks.k_101.hdfs.rollCount = 0 avro_to_hdfs.sinks.k_101.hdfs.fileType = CompressedStream avro_to_hdfs.sinks.k_101.hdfs.codeC = gzip # 채널 c_102 설정 avro_to_hdfs.channels.c_102.type = memory avro_to_hdfs.channels.c_102.capacity = 10000 avro_to_hdfs.channels.c_102.transactionCapacity = 10000 avro_to_hdfs.channels.c_102.byteCapacityBufferPercentage = 20 avro_to_hdfs.channels.c_102.byteCapacity = 10485760클러스터 에이전트 설정파일p.s. Flume 설정 파일은 변수 또는 외부 파일 include 등을 지원하지는 않아서, 위와 같이 반복되는 설정을 여러 번 써 주어야 합니다.호스트마다 CDH 알람 트리거 설정그리고 CDH 상에서도 웹서버 호스트의 개수만큼 알람 트리거를 만들어 줍니다. 초당 이벤트 개수가 0에 가깝게 떨어지면 알람이 오도록 해 주면 됩니다. 채널/싱크 중 어느 것을 기준으로 해도 크게 상관은 없는데, 저희는 싱크가 초당 이동완료한 이벤트 개수를 기준으로 했습니다.CDH에서의 알람 트리거 상태 화면이렇게 해 놓으면 또 한가지 좋은 점은, CDH가 알아서 차트를 그려 주기 때문에, 웹서버마다 트래픽 추이를 한눈에 볼 수 있다는 것입니다.HDFSSink의 초당 이벤트 개수 그래프맺음말지금까지 Apache Flume 과 CDH 를 사용해 로그 수집 시스템을 구성하고 모니터링을 설정한 후기를 살펴 보았습니다. 이 과정에서 느낀 점들을 한번 정리해 보겠습니다.첫째, 일견 간단해 보이는 기능이었지만 의외로 많은 시행착오를 거쳐야 했습니다. 아무리 간단해 보이더라도 각자의 상황에 맞추어 시스템을 설계하는 데에는 그에 맞는 고민을 거쳐야 합니다.둘째, 처음에는 로그가 일단 수집되게 하는 것이 가장 중요하다고 생각했는데, 실제로 겪어보니 모니터링이 훨씬 어렵고 중요한 문제라는 것을 알게 되었습니다. 어떤 기능이 일단 실행되도록 설정을 해 놓더라도, 그것이 매일 문제없이 실행됨을 보장받는 것은 또 다른 문제입니다.셋째, Health 페이지와 Pingdom을 이용한 웹서버 측의 모니터링은 JSON 리포팅의 문제 때문에 큰 쓸모가 없게 되었습니다. 하지만 꽤 유용한 테크닉이라는 생각이 들고, 어딘가에서는 비슷하게 이용할 수 있을 것 같습니다.마지막으로 CDH 쓰면 좋습니다. 많은 것들이 편해집니다.P.S. 리디북스 데이터팀에서는 이러한 로그 시스템을 함께 고민하고 만들어나갈 분들을 찾고 있습니다. 많은 관심 부탁드립니다.#리디북스 #개발 #서버 #서버개발 #모니터링 #로그 #Flume #CDH #로그수정 #인사이트
조회수 1098

비트윈의 HBase 스키마 해부

비트윈에서는 HBase를 메인 데이터베이스로 이용하고 있습니다. 유저 및 커플에 대한 정보와 커플들이 주고받은 메시지, 업로드한 사진 정보, 메모, 기념일, 캘린더 등 서비스에서 만들어지는 다양한 데이터를 HBase에 저장합니다. HBase는 일반적인 NoSQL과 마찬가지로 스키마를 미리 정의하지 않습니다. 대신 주어진 API를 이용해 데이터를 넣기만 하면 그대로 저장되는 성질을 가지고 있습니다. 이런 점은 데이터의 구조가 바뀔 때 별다른 스키마 변경이 필요 없다는 등의 장점으로 설명되곤 하지만, 개발을 쉽게 하기 위해서는 데이터를 저장하는데 어느 정도의 규칙이 필요합니다. 이 글에서는 비트윈이 데이터를 어떤 구조로 HBase에 저장하고 있는지에 대해서 이야기해 보고자 합니다.비트윈에서 HBase에 데이터를 저장하는 방법¶Thrift를 이용해 데이터 저장: Apache Thrift는 자체적으로 정의된 문법을 통해 데이터 구조를 정의하고 이를 직렬화/역직렬화 시킬 수 있는 기능을 제공합니다. 비트윈에서는 서버와 클라이언트가 통신하기 위해 Thrift를 이용할 뿐만 아니라 HBase에 저장할 데이터를 정의하고 데이터 저장 시 직렬화를 위해 Thrift를 이용합니다.하나의 Row에 여러 Column을 트리 형태로 저장: HBase는 Column-Oriented NoSQL로 분류되며 하나의 Row에 많은 수의 Column을 저장할 수 있습니다. 비트윈에서는 Column Qualifier를 잘 정의하여 한 Row에 여러 Column을 논리적으로 트리 형태로 저장하고 있습니다.추상화된 라이브러리를 통해 데이터에 접근: 비트윈에서는 HBase 클라이언트 라이브러리를 직접 사용하는 것이 아니라 이를 래핑한 Datastore라는 라이브러리를 구현하여 이를 이용해 HBase의 데이터에 접근합니다. GAE의 Datastore와 인터페이스가 유사하며 실제 저장된 데이터들을 부모-자식 관계로 접근할 수 있게 해줍니다.트랜잭션을 걸고 데이터에 접근: HBase는 일반적인 NoSQL과 마찬가지로 트랜잭션을 제공하지 않지만 비트윈에서는 자체적으로 제작한 트랜잭션 라이브러리인 Haeinsa를 이용하여 Multi-Row ACID 트랜잭션을 걸고 있습니다. Haeinsa 덕분에 성능 하락 없이도 데이터 무결성을 유지하고 있습니다.Secondary Index를 직접 구현: HBase에서는 데이터를 Row Key와 Column Qualifier를 사전식 순서(lexicographical order)로 정렬하여 저장하며 정렬 순서대로 Scan을 하거나 바로 임의 접근할 수 있습니다. 하지만 비트윈의 어떤 데이터들은 하나의 Key로 정렬되는 것으로는 충분하지 않고 Secondary Index가 필요한 경우가 있는데, HBase는 이런 기능을 제공하지 않고 있습니다. 비트윈에서는 Datastore 라이브러리에 구현한 Trigger을 이용하여 매우 간단한 형태의 Secondary Index를 만들었습니다.비트윈 HBase 데이터 구조 해부¶페이스북의 메시징 시스템에 관해 소개된 글이나, GAE의 Datastore에 저장되는 구조를 설명한 글을 통해 HBase에 어떤 구조로 데이터를 저장할지 아이디어를 얻을 수 있습니다. 비트윈에서는 이 글과는 약간 다른 방법으로 HBase에 데이터를 저장합니다. 이에 대해 자세히 알아보겠습니다.전반적인 구조¶비트윈에서는 데이터를 종류별로 테이블에 나누어 저장하고 있습니다. 커플과 관련된 정보는 커플 테이블에, 유저에 대한 정보는 유저 테이블에 나누어 저장합니다.각 객체와 관련된 정보는 각각의 HBase 테이블에 저장됩니다.또한, 관련된 데이터를 하나의 Row에 모아 저장합니다. 특정 커플과 관련된 사진, 메모, 사진과 메모에 달린 댓글, 기념일 등의 데이터는 해당 커플과 관련된 하나의 Row에 저장됩니다. Haeinsa를 위한 Lock Column Family를 제외하면, 데이터를 저장하기 위한 용도로는 단 하나의 Column Family만 만들어 사용하고 있습니다.각 객체의 정보와 자식 객체들은 같은 Row에 저장됩니다.또한, 데이터는 기본적으로 하나의 Column Family에 저장됩니다.이렇게 한 테이블에 같은 종류의 데이터를 모아 저장하게 되면 Region Split하는 것이 쉬워집니다. HBase는 특정 테이블을 연속된 Row들의 집합인 Region으로 나누고 이 Region들을 여러 Region 서버에 할당하는 방식으로 부하를 분산합니다. 테이블을 Region으로 나눌 때 각 Region이 받는 부하를 고려해야 하므로 각 Row가 받는 부하가 전체적으로 공평해야 Region Split 정책을 세우기가 쉽습니다. 비트윈의 경우 커플과 관련된 데이터인 사진이나 메모를 올리는 것보다는 유저와 관련된 데이터인 메시지를 추가하는 트래픽이 훨씬 많은데, 한 테이블에 커플 Row와 유저 Row가 섞여 있다면 각 Row가 받는 부하가 천차만별이 되어 Region Split 정책을 세우기가 복잡해집니다. RegionSplitPolicy를 구현하여 Region Split 정책을 잘 정의한다면 가능은 하지만 좀 더 쉬운 방법을 택했습니다.또한, 한 Row에 관련된 정보를 모아서 저장하면 성능상 이점이 있습니다. 기본적으로 한 커플에 대한 데이터들은 하나의 클라이언트 요청을 처리하는 동안 함께 접근되는 경우가 많습니다. HBase는 같은 Row에 대한 연산을 묶어 한 번에 실행시킬 수 있으므로 이 점을 잘 이용하면 성능상 이득을 얻을 수 있습니다. 비트윈의 데이터 구조처럼 특정 Row에 수많은 Column이 저장되고 같은 Row의 Column들에 함께 접근하는 경우가 많도록 설계되어 있다면 성능 향상을 기대할 수 있습니다. 특히 Haeinsa는 한 트랜잭션에 같은 Row에 대한 연산은 커밋시 한 번의 RPC로 묶어 처리하므로 RPC에 드는 비용을 최소화합니다. 실제 비트윈에서 가장 많이 일어나는 연산인 메시지 추가 연산은 그냥 HBase API를 이용하여 구현하는 것보다 Haeinsa Transaction API를 이용해 구현하는 것이 오히려 성능이 좋습니다.Column Qualifier의 구조¶비트윈은 커플들이 올린 사진 정보들을 저장하며, 또 사진들에 달리는 댓글 정보들도 저장합니다. 한 커플을 Root라고 생각하고 커플 밑에 달린 사진들을 커플의 자식 데이터, 또 사진 밑에 달린 댓글들을 사진의 자식 데이터라고 생각한다면, 비트윈의 데이터들을 논리적으로 트리 형태로 생각할 수 있습니다. 비트윈 개발팀은 Column Qualifier를 잘 정의하여 실제로 HBase에 저장할 때에도 데이터가 트리 형태로 저장되도록 설계하였습니다. 이렇게 트리 형태로 저장하기 위한 Key구조에 대해 자세히 알아보겠습니다.Column Qualifier를 설계할 때 성능을 위해 몇 가지 사항들을 고려해야 합니다. HBase에서는 한 Row에 여러 Column이 들어갈 수 있으며 Column들은 Column Qualifier로 정렬되어 저장됩니다. ColumnRangeFilter를 이용하면 Column에 대해 정렬 순서로 Scan연산이 가능합니다. 이 때 원하는 데이터를 순서대로 읽어야 하는 경우가 있는데 이를 위해 Scan시, 최대한 Sequential Read를 할 수 있도록 설계해야 합니다. 또한, HBase에서 데이터를 읽어올 때, 실제로 데이터를 읽어오는 단위인 Block에 대해 캐시를 하는데 이를 Block Cache라고 합니다. 실제로 같이 접근하는 경우가 빈번한 데이터들이 최대한 근접한 곳에 저장되도록 설계해야 Block Cache의 도움을 받을 수 있습니다.비트윈에서는 특정 커플의 사진이나 이벤트를 가져오는 등의 특정 타입으로 자식 데이터를 Scan해야하는 경우가 많습니다. 따라서 특정 타입의 데이터를 연속하게 저장하여 최대한 Sequential Read가 일어나도록 해야 합니다. 이 때문에 Column Qualifier가 가리키는 데이터의 타입을 맨 앞에 배치하여 같은 타입의 자식 데이터들끼리 연속하여 저장되도록 하였습니다. 만약 가리키는 데이터의 타입과 아이디가 Parent 정보 이후에 붙게 되면 사진 사이사이에 각 사진의 댓글 데이터가 끼어 저장됩니다. 이렇게 되면 사진들에 대한 데이터를 Scan시, 중간중간 저장된 댓글 데이터들 때문에 완벽한 Sequential Read가 일어나지 않게 되어 비효율적입니다.이렇게 특정 타입의 자식들을 연속하게 모아 저장하는 묶음을 컬렉션이라고 합니다. 컬렉션에는 컬렉션에 저장된 자식들의 개수나 새로운 자식을 추가할 때 발급할 아이디 등을 저장하는 Metadata가 있습니다. 이 Metadata도 특정 Column에 저장되므로 Metadata를 위한 Column Qualifier가 존재합니다. 이를 위해 Column Qualifier에는 Column Qualifier가 자칭하는 데이터가 Metadata인지 표현하는 필드가 있는데, 특이하게도 메타데이터임을 나타내는 값이 1이 아니라 0입니다. 이는 Metadata가 컬렉션의 맨 앞쪽에 위치하도록 하기 위함입니다. 컬렉션을 읽을 때 보통 맨 앞에서부터 읽는 경우가 많고, 동시에 Metadata에도 접근하는 경우가 많은데, 이 데이터가 인접하게 저장되어 있도록 하여 Block Cache 적중이 최대한 일어나도록 한 것입니다.Datastore 인터페이스¶비트윈에서는 이와 같은 데이터 구조에 접근하기 위해 Datastore라는 라이브러리를 구현하여 이를 이용하고 있습니다. HBase API를 그대로 이용하는 것보다 좀 더 쉽게 데이터에 접근할 수 있습니다. GAE의 Datastore와 같은 이름인데, 실제 인터페이스도 매우 유사합니다. 이 라이브러리의 인터페이스에 대해 간단히 알아보겠습니다.Key는 Datastore에서 HBase에 저장된 특정 데이터를 지칭하기 위한 클래스입니다. 논리적으로 트리 형태로 저장된 데이터 구조를 위해 부모 자식 관계를 이용하여 만들어 집니다.Key parentKey = new Key(MType.T_RELATIONSHIP, relId);Key photoKey = new Key(parentKey, MType.T_PHOTO, photoId); // 특정 커플 밑에 달린 사진에 대한 키Datastore는 Key를 이용해 Row Key와 Column Qualifier를 만들어 낼 수 있습니다. Datastore는 이 정보를 바탕으로 HBase에 새로운 데이터를 저장하거나 저장된 데이터에 접근할 수 있는 메서드를 제공합니다. 아래 코드에서 MUser 클래스는 Thrift로 정의하여 자동 생성된 클래스이며, Datastore에서는 이 객체를 직렬화 하여 HBase에 저장합니다.MUser user = new MUser();user.setNickname("Alice");user.setGender(Gender.FEMALE);user.setStatus("Hello World!"); Key userKey = new Key(MType.T_USER, userId);getDatastore().put(userKey, user);user = getDatastore().get(userKey);getDatastore().delete(userKey);또한, Datastore는 Key를 범위로 하여 Scan연산이 할 수 있도록 인터페이스를 제공합니다. Java에서 제공하는 Try-with-resource문을 이용하여 ResultScanner를 반드시 닫을 수 있도록 하고 있습니다. 내부적으로 일단 특정 크기만큼 배치로 가져오고 더 필요한 경우 더 가져오는 식으로 구현되어 있습니다.try (CloseableIterable> entries = getDatastore().subSibling(fromKey, fromInclusive, toKey, toInclusive)) { for (KeyValue entry : entries) { // do something }}Secondary Index 구현 방법¶HBase는 데이터를 Row Key나 Column Qualifier로 정렬하여 저장합니다. 이 순서로만 Sequential Read를 할 수 있으며 Key값을 통해 특정 데이터를 바로 임의 접근할 수 있습니다. 비트윈에서는 특정 달에 해당하는 이벤트들을 읽어오거나 특정 날짜의 사진들의 리스트를 조회하는 등 id 순서가 아니라 특정 값을 가지는 데이터를 순서대로 접근해야 하는 경우가 있습니다. 이럴 때에도 효율적으로 데이터에 접근하기 위해서는 id로 정렬된 것 외에 특정 값으로 데이터를 정렬할 수 있어야 합니다. 하지만 HBase에서는 이와 같은 Secondary Index 같은 기능을 제공하지 않습니다. 비트윈 개발팀은 이에 굴하지 않고 Secondary Index를 간단한 방법으로 구현하여 사용하고 있습니다.구현을 간단히 하기 위해 Secondary Index를 다른 데이터들과 마찬가지로 특정 타입의 데이터로 취급하여 구현하였습니다. 따라서 Index에 대해서도 Column Qualifier가 발급되며, 이때, Index에 해당하는 id를 잘 정의하여 원하는 순서의 Index를 만듭니다. 이런 식으로 원하는 순서로 데이터를 정렬하여 저장할 수 있으며 이 인덱스를 통해 특정 필드의 값의 순서대로 데이터를 조회하거나 특정 값을 가지는 데이터에 바로 임의 접근할 수 있습니다. 또한, Index에 실제 데이터를 그대로 복사하여 저장하여 Clustered Index처럼 동작하도록 하거나, Reference만 저장하여 Non-Clustered Index와 같이 동작하게 할 수도 있습니다. Datastore 라이브러리에는 특정 데이터가 추가, 삭제, 수정할 때 특정 코드를 실행할 수 있도록 Trigger 기능이 구현되어 있는데, 이를 통해 Index를 업데이트합니다. 데이터의 변경하는 연산과 Index를 업데이트하는 연산이 하나의 Haeinsa 트랜잭션을 통해 원자적으로 일어나므로 데이터의 무결성이 보장됩니다.못다 한 이야기¶각 테이블의 특정 Row의 Column들에 대한 Column Qualifier외에도 Row에 대한 Row Key를 정의 해야 합니다. 비트윈에서는 각 Row가 표현하는 Root객체에 대한 아이디를 그대로 Row Key로 이용합니다. 새로운 Root객체가 추가될 때 발급되는 아이디는 랜덤하게 생성하여 객체가 여러 Region 서버에 잘 분산될 수 있도록 하였습니다. 만약 Row Key를 연속하게 발급한다면 특정 Region 서버로 연산이 몰리게 되어 성능 확장에 어려움이 생길 수 있습니다.데이터를 저장할 때 Thrift를 이용하고 있는데, Thrift 때문에 생기는 문제가 있습니다. 비트윈에서 서버를 업데이트할 때 서비스 중지 시간을 최소화하기 위해 롤링 업데이트를 합니다. Thrift 객체에 새로운 필드가 생기는 경우, 롤링 업데이트 중간에는 일부 서버에만 새로운 Thift가 적용되어 있을 수 있습니다. 업데이트된 서버가 새로운 필드에 값을 넣어 저장했는데, 아직 업데이트가 안 된 서버가 이 데이터를 읽은 후 데이터를 다시 저장한다면 새로운 필드에 저장된 값이 사라지게 됩니다. Google Protocol Buffer의 경우, 다시 직렬화 할 때 정의되지 않은 필드도 처리해주기 때문에 문제가 없지만, Thrift의 경우에는 그렇지 않습니다. 비트윈에서는 새로운 Thrift를 적용한 과거 버전의 서버를 먼저 배포한 후, 업데이트된 서버를 다시 롤링 업데이트를 하는 식으로 이 문제를 해결하고 있습니다.저희는 언제나 타다 및 비트윈 서비스를 함께 만들며 기술적인 문제를 함께 풀어나갈 능력있는 개발자를 모시고 있습니다. 언제든 부담없이 [email protected]로 이메일을 주시기 바랍니다!
조회수 1737

StyleShare Engineering Blog?!

변정훈님 강의 모습생각해보기한 번도 생각해본 적이 없다! 왜 글을 작성해야 하는가?! 왜냐하면, 우리는 글로 먹고사는 사람도 아니고, 수려한 글솜씨도 없기 때문에?! 하지만, 이미 우리 사회는 PR의 시대를 뛰어넘어 미디어의 홍수에서 살아가고 있고, 매우 쉽게 무의식적으로 많은 글을 읽고 있다.하지만, 우리가 글을 작성하기 위해서 얼마나 많은 준비가 되어 있을까? 이 글을 쓰고 있는 필자 역시 글을 써본 경험이 거의 없다. 특히 회사의 이름을 걸고 글을 쓴다는 것은 매우 부담스러운 일이다.그래서 우리는 변정훈[Outsider’s Dev Story]님을 초대하여, 그분이 생각하는 블로그 일상과 엔지니어링 블로그에 대한 생각 공유의 시간을 가져보았다.엔지니어링 블로그회사 블로그 운영을 해보았는가?아쉽게도 변정훈 님도 회사 블로그를 운영해본 적은 없다고 하신다. 그 원인을 다음과 같은 이유로 해석을 하였다.주제가 많지 않다.개발보다 우선순위가 떨어진다.누구나 처음부터 글을 잘 적을 수 있는 건 아니다.그렇다. 이 글을 쓰는 중에도 위와 공감할 수 있는 부분이 적지 않다.우선 글을 많이 써보지 못한 필자로서도 어떤 글을 적어야 할지 난감하게 느껴지고, 업무 중에서도 중요도가 떨어지는 것은 분명하다. 마지막으로 주제 선정부터 매우 어렵게 느껴진다.그런 이유로 글쓰기를 즐겨 하시는 변정훈 님 조차 회사 블로그 운영을 잘 이끌어 본 적이 없다고 하신다.변정훈 님의 블로그는 2007년 부터 총 1,300여개 글이 게시되어 있다고 한다.왜 우리는 블로그를 운영하려 하는가?여러 가지가 있겠지만, 필자가 가장 공감한 부분은 이 부분이었다.팀 내 지식/경험 공유잠재적 입사자에게 기술 스택 및 문화 공유팀 전체의 실력 향상그동안 개발일을 해오면서, 몇 년 동안 풀리지 않는 큰 숙제 중에 하나가 좋은 개발자를 찾는 것이었다. 항상 사람을 찾는 것이 어렵다. 좋은 사람의 기준이 높아서인지 더 좋은 기업이 많아서인지 알 수는 없지만, 일하면서 느낀 가장 어려운 문제 중에 하나이다. 결국, 내부의 인력을 더 좋은 사람으로 만들고, 외부의 좋은 사람과도 교류의 장을 만들 수 있다는 희망을 품을 수 있게 되는 것이다.글 작성의 문턱을 어떻게 낮춰야 할까?좋은 점은 쉽게 공감이 되지만, 언제나 가장 어려운 것은 실천이 아닐까 싶다. 특히 회사에서 업무로 이런 일이 발생된다면, 많은 사람들이 엄청난 부담감을 가질 것이고, 결국 회사 엔지니어링 블로그는 대문만 남은 유명무실한 블로그가 될 가능성이 높다.그래서 변정훈 님은 이렇게 제안하셨다.월 1개 보다 적어도 된다.주제를 계속해서 제안하고 만들어 내야 한다.돌아가면서 작성한다.챙겨주는 사람이 필요하다.글도 리뷰하는게 좋다.부담감은 의도적으로 줄여야 …특히 변정훈님은 부담감을 줄이는 방법에 대해서, 팀 공유를 해주셨는데 잠시 소개하면 다음과 같다.나보다 모르는 사람 — 나 — 나보다 잘하는 사람언제나 어떤 기술에 대하여, 나보다 잘 모르는 사람과 나보다 잘 하는 사람이 있다는 것을 인지하고, 나보다 못하는 사람을 위해서 글을 작성한다는 것이다. 그러다 나보다 잘하는 누군가가 어쩌다 피드백을 준다면 오히려 매우 감사하게 새로운 지식을 터득하게 된다는 것이다. 역시 모든일에는 긍정적인 마인드가 중요하다. 세상 어딘가에는 나의 작은 지식이라도 필요로하는 사람들이 분명히 있을테니, 작은 용기를 가지고 세상에 누군가를 위해서 작성한다면, 세상은 분명 아름다워질 것이다.좋은 글, 좋은 주제란 무엇일까?사실 가장 어려운 이야기일지도 모른다. 변정훈님은 이런 내용을 좋다고 표현하셨는데, 잠시 정리하면 다음과 같다.개발팀의 문화어떻게 일하는가?프로젝트 수행 회고실패기개인적으로 실패기가 가장 적기 어려운 글이라고 생각한다. 하지만, 누군가에게는 가장 소중한 경험이 될지도 모르니, 간접경험의 공유는 특히 소중한 것일 수 있다.만약 회사 블로그의 글이 회사 내/외부의 사람들에게 지식 공유와 전달이 목적이라면, 그리고 좋은 문화를 계속 가지고 유지하기 위해서라면, 실패기가 어쩌면 가장 소중한 경험의 공유가 아닐까 생각해본다.질문과 답변을 통한 소통의 시간마지막으로 이 날 소개 받은 좋은 글의 흐름이란 다음과 같았다.하고자 했던 일 (Context)경험한 문제 사황 정리(격리된 상황)시도해 본 방법(내가 아는 지식)왜 동작이 안되는가? 왜 동작하는가?(가설)문제 상황 재현예제 코드관련 링크개념 설명지금까지 적은 이 글을 위의 원칙대로 다시 한번 살펴본다. 부족한 부분이 있는지, 수정할 부분이 있는지…이제 부터 회사 블로그를 더욱더 적극적으로 운영해보자!!!#스타일쉐어 #개발팀 #조직문화 #블로그 #기업문화 #사내복지
조회수 4557

개발자 직군 파헤치기 4 | 빅 데이터 엔지니어

빅 데이터 엔지니어는 무엇을 하나요?빅 데이터가 부상하면서 그와 관련된 직업군도 함께 주목받기 시작했습니다. 빅 데이터 엔지니어, 빅 데이터 애널리스트, 빅 데이터 사이언티스트 등 다양한 직업군이 생겼습니다. 오늘은 개발자 직군 중 데이터와 관련된 빅 데이터 엔지니어에 관해 이야기해 볼 것입니다. 빅 데이터 엔지니어는 무엇을 할까요? 빅 데이터 엔지니어가 무엇을 하는지 알기 위해서는 먼저 빅 데이터가 뭔지 알필요가 있겠습니다.빅 데이터는 기존 데이터베이스 관리도구의 능력을 넘어서는 대량의 정형 또는 심지어 데이터베이스 형태가 아닌 비정형의 데이터 집합조차 포함한 데이터로부터 가치를 추출하고 결과를 분석하는 기술입니다(위키 참조).빅 데이터의 특징은 방대한 데이터와 더불어 비정형 데이터까지 포함한다는 것입니다. 많은 양의 데이터와 정형화 되지 않은 데이터를 수집하는 일은 보통 일이 아닙니다. 빅 데이터를 통한 새로운 알고리즘를 만들거나 인사이트를 발견하기 위해서는 빅 데이터가 존재해야 합니다. 빅 데이터 엔지니어는 이러한 빅 데이터를 수집하고 관리하는 프로그래머입니다. 일반적인 데이터 수집과 달리 수십테라 정도의 정보를 수집 하게 됩니다. 또 그런 데이터를 어떻게 효율적으로 관리할지 고민해야합니다.데이터는 미래의 석유라고 합니다. 빅 데이터 엔지니어는 빅 데이터 분석가나 과학자들에게 이러한 석유를 가져다 주는 송유관을 설치하고 관리하는 역할을 한다고 볼 수 있습니다. 빅 데이터 활용을 위해서라면 빅 데이터 엔지니어의 역량이 반드시 필요합니다.데이터 과학자와 데이터 엔지니어는 다르다.위에서 빅 데이터 엔지니어는 데이터를 수집하고 관리하는 업무를 한다고 했습니다. 하지만 구체적으로 빅 데이터 과학자(Big Data Scientist)와 빅 데이터 엔지니어(Big Data Engineer)는 무엇이 다를까요?어떤 직업의 업무라는 것이 무 자르듯 쉽게 나눌 수 있는 것은 아니지만 확실히 그 직업만의 특징은 존재합니다. 각 직업 별로의 특징을 통해 빅 데이터 엔지니어가 빅 데이터 과학자와 어떻게 다른지 알아보도록 하겠습니다.1. 빅 데이터 엔지니어(Big Data Engineer)빅 데이터 엔지니어는 위에서도 언급 했지만, 데이터를 수집하고 관리하는 일을 합니다. 빅 데이터 엔지니어를 통해 '빅 데이터'가 만들어진다고 해도 무방하죠. 숫자나 규칙이 있는 정형 데이터는 물론이고 글자나 불규칙적인 비정형 데이터까지 수집하고 관리합니다. "그냥 데이터를 수집하고 관리하는 일인데 별거 있나?"라고 생각하실 수도 있습니다. 빅 데이터라는 개념 이전에도 데이터는 수집되었고 분석을 통해 비즈니스 문제를 해결해 왔으니까요. 그렇지만, 빅 데이터라는 개념이 부상하고 실현 가능할 수 있었던 이유는 방대한 데이터를 수집할 수 있는 퍼널(funnel) 설계과 그 데이터를 관리하고 알맞게 사용할 수 있는 시스템을 구축할 수 있었기 때문입니다.그렇기 때문에 빅 데이터 엔지니어는 프로그래밍에 아주 능숙해야합니다. 빅 데이터를 수집하고 관리할 수 있는 방법을 짜야하니까요. 또한, 개별적인 정보가 아닌 큰 틀에서의 정보를 다루고 통합하고 나누어 볼 수 있는 설계 능력이 따라주어야 합니다.정교하게 짜여진 빅 데이터가 아니라면 빅 데이터 과학자가 그것을 분석하고 사용하는데 상당한 자원이 들거나 최악의 경우 아예 이용하지 못하게 될 것입니다.2. 빅 데이터 과학자(Big Data Scientist)빅 데이터 엔지니어가 빅 데이터를 수집하고 관리한다면 빅 데이터 과학자는 그것을 요리하는 역할을 합니다. 데이터보고 직면한 비즈니스 문제를 해결할 새로운 인사이트를 도출해 내는 것입니다. 혹은 현재 가지고 있는 프로세스를 개선할 알고리즘을 만들어 낼 수도 있습니다.빅 데이터 과학자는 데이터를 분석할 수 있는 통계학적 지식뿐만 아니라 그 데이터를 다룰 수 있는 프로그래밍적 지식도 요구됩니다. 일반적인 데이터가 아닌 '빅' 데이터다 보니 그것을 쉽게 운용하고 자유자재로 이용하게 해줄 툴을 익혀야합니다. 또한, 빅 데이터 과학자에게 요구되는 핵심 역량 중 하나는 바로 머신러닝에 대한 지식입니다. 이 또한 프로그래밍 지식과 알고리즘 지식이 필요합니다. 빅 데이터 엔지니어가 되기 위한 Key Skills그렇다면 빅 데이터 엔지니어가 되기 위해서는 어떤 기술 스택들을 익혀야할까요? 빅 데이터 엔지니어는 데이터와 관련된 직군인만큼 데이터베이스와 관련된 기술스택들이 중요합니다.1. SQL데이터 관리를 하시는 분들이면 다들 알고 계시는 SQL입니다.  SQL은 관계형 데이터베이스 관리 시스템의 데이터를 관리하기 위해 설계된 특수 목적의 프로그래밍 언어입니다(위키참조).2. MapReduce(맵리듀스)맵리듀스는 구글에서 대용량 데이터 처리를 분산 병렬 컴퓨팅에서 처리하기 위한 목적으로 제작하여 2004년에 발표한 프레임워크입니다.(위키참조).3. Apache Hadoop(아파치 하둡)Apache Hadoop은 대규모 데이터 세트를 효율적으로 처리하는 데 사용할 수 있는 오픈 소스 소프트웨어 프로젝트입니다. 하나의 대형 컴퓨터를 사용하여 데이터를 처리 및 저장하는 대신, 하둡을 사용하면 상용 하드웨어를 함께 클러스터링하여 대량의 데이터 세트를 병렬로 분석할 수 있습니다.4. Apache Cassandra(아파치 카산드라)Apache Cassandra 자유-오픈 소스 분산형 NoSQL 데이터베이스 관리 시스템의 하나로, 단일 장애점 없이 고성능을 제공하면서 수많은 서버 간의 대용량의 데이터를 관리하기 위해 설계되었습니다. 카산드라는 여러 데이터센터에 걸쳐 클러스터를 지원하며 마스터리스(masterless) 비동기 레플리케이션을 통해 모든 클라이언트에 대한 낮은 레이턴시 운영을 허용합니다(위키참조).5. Java(자바)빅 데이터 엔지니어는 기본적으로 프로그래머이기 때문에 프로그래밍 지식있어야 합니다. 빅 데이터 엔지니어를 목표로 처음 프로그래밍을 시작한다면 자바를 추천합니다. 물론, 다른 언어를 통해 프로그래밍 실력을 쌓아도 됩니다. 그렇지만, 아파치 하둡과 아파치 카산드라가 자바를 베이스로 만들어졌기 때문에 자바를 배운다면 이 기술스택들을 습득하는데 훨씬 효율적일 것입니다.다른 포스팅에서도 항상 말씀드려왔지만 기술스택만 익힌다고 해서 그 직업을 가질 수 있는 것은 아닙니다. 기술스택은 기본이고 개발자로써의 역량이 뒷받침 되어야 시장에서 환영받는 빅 데이터 엔지니어가 될 수 있습니다.Photo by Ehud Neuhaus on Unsplash빅 데이터 엔지니어가 되기 위한 학습 콘텐츠시중에서는 완성된 단계로써 빅 데이터 엔지니어를 양성하는 프로그램은 많지 않습니다. 따라서 개인이 빅 데이터 엔지니어에게 필요한 기술 스택들을 하나씩 익혀 나가야 합니다.무료 온라인 콘텐츠도 많겠지만, 비싸지 않으면서도 잘 정제된 콘텐츠를 소개하려고 합니다. 유튜브 강좌보다는 보기 편하고 학습 환경이 잘 갖춰져 있어서 공부하기에 좋은 콘텐츠를 추천합니다.1. SQL - SQL 프로그래밍 : SQL을 무료로 학습할 수 있는 사이트(한글)2. Hadoop - 유데미 The Ultimate Hands-On Hadoop - Tame your Big Data! (영어)3. Cassandra - 유데미 From 0 to 1: The Cassandra Distributed Database (영어)데이터 엔지니어는 예전부터 있었다.오늘은 빅 데이터 엔지니어에 대해 알아보았습니다. 사실, 빅 데이터 엔지니어는 어느 날 갑자기 생겨난 직업이 아닙니다. 데이터베이스를 관리하는 프로그래머가 더 나은 기술 스택을 익히고 더 좋은 방법으로 데이터를 수집하고 관리하면서 생겨난 것입니다.세상은 빠르게 변한다고 하지만 그 안을 들여보면 서서히 발전한 것들이 다르게 네이밍(Naming) 되면서 새롭게 다가오는 것이라 생각합니다. 그렇다고 해서 그것이 변하지 않는 것이 아닙니다. 새롭게 변하는 기술들을 익히고 자신의 역량을 갈고 닦아야만 새롭게 다가오는 변화에 휩쓸리지 않고 주도할 수 있는 것 같습니다.
조회수 3598

Node 서버로 Slack 메신저 자동화하기

Overview백엔드 업무를 하면 데이터 요청과 CS문의를 자주 받습니다. 날짜만 다를 뿐 같은 유형의 문의가 대부분이죠. 결국 반복적인 업무를 효율적으로 처리할 수 있는 방법을 고민했고, 사내 메신저로 사용하는 Slack의 몇 가지 API를 사용하기로 했습니다.1. 알림봇 만들기비즈니스 로직을 만들다 보면 정해진 시간에 맞춰 작업을 해야 하는 경우가 발생합니다. Slack 메신저에 로그온한 상태에서 스케줄러를 이용해 지정한 시간에 Slack 메세지를 전송해보겠습니다.1)Slack API 유저토큰 받기Slack API에 사용할 해당 계정의 토큰을 받아야 합니다. Slack 가입 절차 및 채널 생성은 생략하겠습니다.https://api.slack.com/custom-integrations/legacy-tokens 접속합니다.Legacy tokens 메뉴에서 아래로 스크롤을 내려 토큰 생성버튼을 누릅니다.계정 패스워드를 입력하여 확인하면 토큰을 생성할 수 있습니다.생성된 토큰을 복사하여 저장합니다.2)Node.js를 이용한 알림봇 구현2-1.Node.js 설치Node.js 다운로드 해당 사이트에서 운영체제 환경에 맞는 파일을 다운받아 설치2-2.프로젝트 생성해당 프로젝트 폴더로 이동 후 명령어 실행$ npm init --yes // package.json 파일 생성2-3.Slack 연동2-3-1. slack-node 모듈 설치$ npm install slack-node --save2-3-2. 유저토큰을 이용하여 해당채널에 메세지 전송const Slack = require('slack-node'); // 슬랙 모듈 사용 apiToken = "발급받은 유저토큰"; const slack = new Slack(apiToken); const send = async(message) => { slack.api('chat.postMessage', { username: 'dev-test', // 슬랙에 표시될 봇이름 text:message, channel:'#general' // 전송될 채널 및 유저 }, function(err, response){ console.log(response); }); } send('메세지 내용'); 지정한 채널에 메시지가 발송됩니다. 하지만 이와 같은 방법은 유저 토큰이 공개 코드에 노출되기 때문에 보안이 취약할 수 있습니다. 유저 토큰이 필요 없어도 해당 채널에 URL을 생성하는 WebHooks API를 이용하여 메시지를 전송해보겠습니다.3) Incoming WebHooks APIWebHooks는 유저 토큰 대신 Webhook URL을 생성해 HTTP 통신으로 Slack 메세지를 전송할 수 있습니다. 다양한 메시지 형식을 지원하고 게시할 사용자 이름 및 아이콘 등을 통합적으로 관리할 수 있는 장점을 가지고 있습니다.3-2. Webhook URL 생성하기Slack 해당채널에서 Add an app 클릭검색필터에 WebHooks 검색Incoming WebHooks 추가채널 선택 후 Incoming WebHooks 생성생성된 Webhook URL 복사하여 저장해당채널에 생성되었는지 확인봇이름 및 아이콘등 기본 설정 변경하여 저장curl 사용 예제$ curl -s -d "payload={'text':'메세지 내용'}" "Webhook URL"Webhook URL 사용 중인 모든 메시지는 통합적으로 기본 설정이 변경된 걸 확인할 수 있습니다.다양한 형식의 메세지를 전송해보겠습니다.const Slack = require('slack-node'); // 슬랙 모듈 사용 const webhookUri = "Webhook URL"; // Webhook URL const slack = new Slack(); slack.setWebhook(webhookUri); const send = async(message) => { slack.webhook({ text:"인터넷 검색 포털 사이트", attachments:[ { fallback:"링크주소: ", pretext:"링크주소: ", color:"#00FFFF", fields:[ { title:"알림", value:"해당링크를 클릭하여 검색해 보세요.", short:false } ] } ] }, function(err, response){ console.log(response); }); } 다양한 형태의 메시지를 전송할 수 있습니다.4) Schedule 연동이제 스케줄러를 이용하여 지정한 시간에 메세지를 전송해보겠습니다.4-1. node-schedule 모듈 설치node-schedule는 Node.js 작업 스케줄러 라이브러리입니다.$ npm install node-schedule --savenode-schedule 코드 작성const schedule = require('node-schedule'); // 스케줄러 모듈 사용 // rule-style 사용 var rule = new schedule.RecurrenceRule(); rule.dayOfWeek = new schedule.Range(3,4); rule.hour = 19; rule.minute = 50; schedule.scheduleJob(rule, function(){ console.log('rule 방식'); }); // cron-style 사용 schedule.scheduleJob('50 19 * * *', function(){ console.log('cron-style 방식'); }); 취향에 맞는 스타일로 사용하면 됩니다.5) 지정 시간에 메세지를 전송하는 알림봇을 작성해보겠습니다.const Slack = require('slack-node'); // 슬랙 모듈 사용 const schedule = require('node-schedule'); // 스케줄러 모듈 사용 const webhookUri = "Webhook URL"; // Webhook URL const slack = new Slack(); slack.setWebhook(webhookUri); const send = async(message) => { slack.webhook({ text:message, attachments:[ { fallback:"구글드라이브: ", pretext:"구글드라이브: ", color:"#00FFFF", fields:[ { title:"[알림]", value:"해당링크로 접속하여 작성해 주세요.", short:false } ] } ] }, function(err, response){ console.log(response); }); } schedule.scheduleJob('5 19 * * *', function(){ send('업무보고 보내셨나요?'); }); 업무보고 시간을 미리 알려주는 알림봇2. 대화봇 만들기업무 문서는 주로 구글 독스와 같은 온라인 문서로 관리하고 있습니다. 하지만 매번 구글 드라이브에서 문서를 찾는 건 정말 귀찮은 일입니다. 번거로운 건 딱 질색입니다. Slack API를 이용해 관련된 키워드를 입력하면 링크 주소를 바로 받을 수 있는 대화봇을 만들어 보겠습니다.1) Slack API Bots 토큰 받기Slack API에 사용될 Bots 토큰을 받아야 합니다.https://{App Name}.slack.com/apps 에 접속합니다.Bots 추가Bots Api 토큰을 복사해 저장합니다.설정한 봇이름으로 Apps 영역에 자동으로 추가됩니다.2) 구글독스 대화봇 코드 작성2-1. botkit 모듈 설치$ npm install botkit --save2-2. 코드 작성const botkit = require('botkit'); // 봇 모듈 사용 const Slack = require('slack-node'); // 슬랙 모듈 사용 const controller = botkit.slackbot({ debug: false, log: true }); const botScope = [ 'direct_message', 'direct_mention', 'mention' ]; controller.hears(['업무보고'], botScope, (bot, message) => { bot.reply(message, '업무보고 링크주소'); }); controller.hears(['가이드', 'guide', '튜토리얼'], botScope, (bot, message) => { bot.reply(message, '가이드 링크주소'); }); controller.hears(['api', '명세서'], botScope, (bot, message) => { bot.reply(message, 'api명세서 링크주소'); }); controller.hears(['일정', '일정관리'], botScope, (bot, message) => { bot.reply(message, '일정관리 링크주소'); }); controller.hears(['비품', '비품정리'], botScope, (bot, message) => { bot.reply(message, '비품관리 링크주소'); }); controller.spawn({ token: '발급받은 봇 토큰' }).startRTM(); 지정한 키워드를 입력하면 해당 링크가 수신 됩니다.3) 데이터문의 대화봇 코드 작성데이터 요청 시 결과 데이터를 보내주는 대화봇을 만들어 보겠습니다. 일단 먼저 데이터문의 전용 Bots을 생성합니다.3-1. Python 연동 요청한 데이터는 Mysql 데이터를 조회해서 전송합니다. 그러면 Mysql 을 연동해야겠죠? Node.js에서도 직접 mysql 연결할 수 있지만, 기존 프로젝트가 Python으로 구현되어 있어 Python을 실행해 필요한 데이터를 추출해보겠습니다.3-2. python-shell 모듈 설치Node.js에서 Python 실행가능하도록 모듈을 설치$ npm install python-shell --save3-3. Mysql Sample Table3-4. 회원테이블에 저장된 가입일시 기준으로 몇일전에 가입한 회원을 추출하여 전송하는 코드 작성해 보겠습니다.const botkit = require('botkit'); // 봇 모듈 사용 const Slack = require('slack-node'); // 슬랙 모듈 사용 const ps = require('python-shell'); // 파이썬 쉘 모듈 사용 // 몇일 전 날짜 구하기 function getDaysAgo(dayNo = 0) { let nowDate = new Date(); let tempDate = nowDate.getTime() - (dayNo * 24 * 60 * 60 * 1000); nowDate.setTime(tempDate); let getYear = nowDate.getFullYear(); let getMonth = nowDate.getMonth() + 1; let getDay = nowDate.getDate(); if (getMonth < 10 xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed> 3-5. Python 코드 작성 # -*- coding: utf-8 -*- import sys import pymysql // mysql 접속 db = pymysql.connect('hostname', user='', passwd='', db='', charset='utf8') cursor_db = db.cursor() exe_query = "SELECT MEMBER_NAME FROM MEMBER_INFO WHERE MEMBER_REGIST_DETE >= '{}' ORDER BY MEMBER_NO ASC ".format(sys.argv[1]) cursor_db.execute(exe_query) all_rows = cursor_db.fetchall() for idx, row in enumerate(all_rows): print(row[0])     지정한 며칠 전에 가입한 회원 이름이 전송됩니다.   로그도 정상적으로 출력됩니다. 3. Node.js 프로세스 관리를 위한 pm2 모듈 설치 Node.js 는 비동기 I/O를 지원하며 단일 스레드로 동작하는 서버입니다. 비동기식 방식이지만 처리하는 Event Loop는 단일 스레드로 이루어져 있어 처리 작업이 오래 걸리면 전체 서버에 영향을 줍니다. 그래서 pm2를 이용해 프로세스별로 상태를 관리해야 합니다. 1) pm2 모듈 설치$ npm install pm2 -g2) 자주사용하는 pm2 명령어 pm2 list -> 실행중인 프로세스 확인pm2 start {node 파일} -> 시작pm2 stop {id or App name} -> 중지pm2 delete {id or App name} -> 삭제pm2 show {id or App name} -> 상세정보pm2 restart {id or App name} -> 재시작pm2 kill -> pm2 종료pm2 logs {id} -> id 앱의 로그 확인 3) pm2 실행화면$ pm2 start bot.js   프로세스별로 앱 이름, 버전, 상태, cpu 및 memory 사용량이 표시됩니다.$ pm2 show 0   해당 프로세스의 상세 정보를 확인할 수 있습니다. Conclusion 지금까지 Node.js 로 유용한 Slack 메신져 API를 알아봤습니다. 반복적인 업무를 하나씩 줄이다 보면 분명 일의 능률을 높아집니다. 하지만 무분별한 자동화는 서버의 부하를 증가시키기 때문에 꼭 필요한지 확인하고 선택하길 바랍니다. 오늘은 여기까지 글곽정섭 과장 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만  

기업문화 엿볼 때, 더팀스

로그인

/