스토리 홈

인터뷰

피드

뉴스

조회수 2827

Radix? Redis!

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

블로그 운영 방법에서 엿보는 VCNC의 개발문화 - VCNC Engineering Blog

 VCNC에서 엔지니어링 블로그를 시작하고 벌써 새로운 해를 맞이하였습니다. 그동안 여러 글을 통해 VCNC 개발팀의 이야기를 들려드렸습니다. 이번에는 엔지니어링 블로그 자체를 주제로 글을 적어보고자 합니다. 저희는 워드프레스나 텀블러와 같은 일반적인 블로깅 도구나 서비스를 사용하지 않고 조금은 개발자스럽다고 할 수 있는 특이한 방법으로 엔지니어링 블로그를 운영하고 있습니다. 이 글에서는 VCNC 개발팀이 엔지니어링 블로그를 운영하기 위해 이용하는 방법들을 소개하고자 합니다. 그리고 블로그를 운영하기 위해 방법을 다루는 중간중간에 개발팀의 문화와 일하는 방식들에 대해서도 간략하게나마 이야기해보고자 합니다.블로그에 사용하는 기술들Jekyll: Jekyll은 블로그에 특화된 정적 사이트 생성기입니다. GitHub의 Co-founder 중 한 명인 Tom Preston-Werner가 만들었으며 Ruby로 작성되어 있습니다. Markdown을 이용하여 글을 작성하면 Liquid 템플릿 엔진을 통해 정적인 HTML 파일들을 만들어 줍니다. VCNC 엔지니어링 블로그는 워드프레스같은 블로깅 도구를 사용하지 않고 Jekyll을 사용하고 있습니다.Bootstrap: 블로그 테마는 트위터에서 만든 프론트엔드 프레임워크인 Bootstrap을 이용하여 직접 작성되었습니다. Bootstrap에서 제공하는 다양한 기능들을 가져다 써서 블로그를 쉽게 만들기 위해 이용하였습니다. 덕분에 큰 공을 들이지 않고도 Responsive Web Design을 적용할 수 있었습니다.S3: S3는 AWS에서 제공되는 클라우드 스토리지 서비스로서 높은 가용성을 보장합니다. 일반적으로 파일을 저장하는 데 사용되지만, 정적인 HTML을 업로드하여 사이트를 호스팅하는데 사용할 수도 있습니다. 아마존의 CTO인 Werner Vogels 또한 자신의 블로그를 S3에서 호스팅하고 있습니다. VCNC Engineering Blog도 Jekyll로 만들어진 HTML 파일들을 아마존의 S3에 업로드 하여 운영됩니다. 일단 S3에 올려두면 운영적인 부분에 대한 부담이 많이 사라지기 때문에 S3에 올리기로 하였습니다.CloudFront: 브라우저에서 웹페이지가 보이는 속도를 빠르게 하려고 아마존의 CDN서비스인 CloudFront를 이용합니다. CDN을 이용하면 HTML파일들이 전 세계 곳곳에 있는 Edge 서버에 캐싱 되어 방문자들이 가장 가까운 Edge를 통해 사이트를 로딩하도록 할 수 있습니다. 특히 CloudFront에 한국 Edge가 생긴 이후에는 한국에서의 응답속도가 매우 좋아졌습니다.s3cmd: s3cmd는 S3를 위한 커맨드 라인 도구입니다. 파일들을 업로드하거나 다운로드 받는 등 S3를 위해 다양한 명령어를 제공합니다. 저희는 블로그 글을 s3로 업로드하여 배포하기 위해 s3cmd를 사용합니다. 배포 스크립트를 실행하는 것만으로 s3업로드와 CloudFront invalidation이 자동으로 이루어지므로 배포 비용을 크게 줄일 수 있었습니다.htmlcompressor: 정적 파일들이나 블로그 글 페이지들을 s3에 배포할 때에는 whitespace 등을 제거하기 위해 htmlcompressor를 사용합니다. 또한 Google Closure Compiler를 이용하여 javascript의 길이도 줄이고 있습니다. 실제로 서버가 내려줘야 할 데이터의 크기가 줄어들게 되므로 로딩속도를 조금 더 빠르게 할 수 있습니다.블로그 관리 방법앞서 소개해 드린 기술들 외에도 블로그 글을 관리하기 위해 다소 독특한 방법을 사용합니다. 개발팀의 여러 팀원이 블로그에 올릴 주제를 결정하고 서로의 의견을 교환하기 위해 여러 가지 도구를 이용하는데 이를 소개하고자 합니다. 이 도구들은 개발팀이 일할 때에도 활용되고 있습니다.글감 관리를 위해 JIRA를 사용하다.JIRA는 Atlassian에서 만든 이슈 관리 및 프로젝트 관리 도구입니다. VCNC 개발팀에서는 비트윈과 관련된 다양한 프로젝트들의 이슈 관리를 위해 JIRA를 적극적으로 활용하고 있습니다. 제품에 대한 요구사항이 생기면 일단 백로그에 넣어 두고, 3주에 한 번씩 있는 스프린트 회의에서 요구사항에 대한 우선순위를 결정합니다. 그 후 개발자가 직접 개발 기간을 산정한 후에, 스프린트에 포함할지를 결정합니다. 이렇게 개발팀이 개발에 집중할 수 있는 환경을 가질 수 있도록 하며, 제품의 전체적인 방향성을 잃지 않고 모두가 같은 방향을 향해 달릴 수 있도록 하고 있습니다.VCNC 개발팀이 스프린트에 등록된 이슈를 얼마나 빨리 해결해 나가고 있는지 보여주는 JIRA의 차트.조금만 생각해보시면 어느 부분이 스프린트의 시작이고 어느 부분이 끝 부분인지 아실 수 있습니다.위와 같은 프로젝트 관리를 위한 일반적인 용도 외에도 엔지니어링 블로그 글 관리를 위해 JIRA를 사용하고 있습니다. JIRA에 엔지니어링 블로그 글감을 위한 프로젝트를 만들어 두고 블로그 글에 대한 아이디어가 생각나면 이슈로 등록할 수 있게 하고 있습니다. 누구나 글감 이슈를 등록할 수 있으며 필요한 경우에는 다른 사람에게 글감 이슈를 할당할 수도 있습니다. 일단 글감이 등록되면 엔지니어링 블로그에 쓰면 좋을지 어떤 내용이 포함되면 좋을지 댓글을 통해 토론하기도 합니다. 글을 작성하기 시작하면 해당 이슈를 진행 중으로 바꾸고, 리뷰 후, 글이 발행되면 이슈를 해결한 것으로 표시하는 식으로 JIRA를 이용합니다. 누구나 글감을 제안할 수 있게 하고, 이에 대해 팀원들과 토론을 하여 더 좋은 글을 쓸 수 있도록 돕기 위해 JIRA를 활용하고 있습니다.JIRA에 등록된 블로그 글 주제들 중 아직 쓰여지지 않은 것들을 보여주는 이슈들.아직 제안 단계인 것도 있지만, 많은 주제들이 블로그 글로 발행되길 기다리고 있습니다.글 리뷰를 위해 Pull-request를 이용하다.Stash는 Attlassian에서 만든 Git저장소 관리 도구입니다. GitHub Enterprise와 유사한 기능들을 제공합니다. Jekyll로 블로그를 운영하는 경우 이미지를 제외한 대부분 콘텐츠는 평문(Plain text)으로 관리 할 수 있게 됩니다. 따라서 VCNC 개발팀이 가장 자주 사용하는 도구 중 하나인 Git을 이용하면 별다른 시스템의 도움 없이도 모든 변경 내역과 누가 변경을 했는지 이력을 완벽하게 보존할 수 있습니다. 저희는 이런 이유로 Git을 이용하여 작성된 글에 대한 변경 이력을 관리하고 있습니다.또한 Stash에서는 GitHub와 같은 Pull request 기능을 제공합니다. Pull request는 자신이 작성한 코드를 다른 사람에게 리뷰하고 메인 브랜치에 머지해 달라고 요청할 수 있는 기능입니다. 저희는 Pull request를 활용하여 상호간 코드 리뷰를 하고 있습니다. 코드 리뷰를 통해 실수를 줄이고 개발자 간 의견 교환을 통해 더 좋은 코드를 작성하며 서로 간 코드에 대해 더 잘 이해하도록 노력하고 있습니다. 새로운 개발자가 코드를 상세히 모른다 해도 좀 더 적극적으로 코드를 짤 수 있고, 업무에 더 빨리 적응하는데에도 도움이 됩니다.어떤 블로그 글에 대해 리뷰를 하면서 코멘트로 의견을 교환하고 있습니다.코드 리뷰 또한 비슷한 방법을 통해 이루어지고 있습니다.업무상 코드 리뷰 뿐만 아니라 새로운 블로그 글을 리뷰하기 위해 Pull request를 활용하고 있습니다. 어떤 개발자가 글을 작성하기 위해서 가장 먼저 하는 것은 블로그를 관리하는 Git 리포지터리에서 새로운 브랜치를 따는 것입니다. 해당 브랜치에서 글을 작성하고 작성한 후에는 새로운 글 내용을 push한 후 master 브랜치로 Pull request를 날립니다. 이때 리뷰어로 등록된 사람과 그 외 개발자들은 내용에 대한 의견이나 첨삭을 댓글로 달 수 있습니다. 충분한 리뷰를 통해 발행이 확정된 글은 블로그 관리자에 의해 master 브랜치에 머지 되고 비로소 발행 준비가 끝납니다.스크립트를 통한 블로그 글 발행 자동화와 보안준비가 끝난 새로운 블로그 글을 발행하기 위해서는 일련의 작업이 필요합니다. Jekyll을 이용해 정적 파일들을 만든 후, htmlcompressor 통해 정적 파일들을 압축해야 합니다. 이렇게 압축된 정적 파일들을 S3에 업로드 하고, CloudFront에 Invalidation 요청을 날리고, 구글 웹 마스터 도구에 핑을 날립니다. 이런 과정들을 s3cmd와 Rakefile을 이용하여 스크립트를 실행하는 것만으로 자동으로 이루어지도록 하였습니다. VCNC 개발팀은 여러 가지 업무 들을 자동화시키기 위해 노력하고 있습니다.또한, s3에 사용하는 AWS Credential은 IAM을 이용하여 블로그를 호스팅하는 s3 버킷과 CloudFront에 대한 접근 권한만 있는 키를 발급하여 사용하고 있습니다. 비트윈은 특히 커플들이 사용하는 서비스라 보안에 민감합니다. 실제 비트윈을 개발하는데에도 보안에 많은 신경을 쓰고 있으며, 이런 점은 엔지니어링 블로그 운영하는데에도 묻어나오고 있습니다.맺음말VCNC 개발팀은 엔지니어링 블로그를 관리하고 운영하기 위해 다소 독특한 방법을 사용합니다. 이 방법은 개발팀이 일하는 방법과 문화에서 큰 영향을 받았습니다. JIRA를 통한 이슈 관리 및 스프린트, Pull request를 이용한 상호간 코드 리뷰 등은 이제 VCNC 개발팀의 문화에 녹아들어 가장 효율적으로 일할 수 있는 방법이 되었습니다. 개발팀을 꾸려나가면서 여러가지 시행 착오를 겪어 왔지만, 시행 착오에 대한 반성과 여러가지 개선 시도를 통해 계속해서 더 좋은 방법을 찾아나가며 지금과 같은 개발 문화가 만들어졌습니다. 그동안 그래 왔듯이 앞으로 더 많은 개선을 통해 꾸준히 좋은 방법을 찾아 나갈 것입니다.네 그렇습니다. 결론은 저희와 함께 고민하면서 더 좋은 개발문화를 만들어나갈 개발자를 구하고 있다는 것입니다.
조회수 1324

docker the cloud

당신의 기획안을 통과시키는 마법의 단어, 클라우드안녕, 여러분! 다들 다망하신 와중에 이렇게 지면으로 찾아뵙게 되어 굉장히 반갑습니다. 저는 spoqa의 노예 xym입니다. 어느덧 벌써 연말이네요. 온갖 골든 위크로 시작했던 4/4분기, 이제 한창 주말 외에는 법정공휴일이 없는 데스마치를 진행중이시리라 생각되는데요, 안 그래도 다들 크리스마스만 바라보고 미친듯이 달리고 계시죠?네, 그래서 제가 이렇게 잠시 여러분 머리를 식혀드리기 위해 한 번 재밌는 이야기를 하고자 찾아뵙게 되었습니다. 개발자가 아닌 분들에게도 별로 어렵지 않게 쓰고자 노력했으니 한번쯤 “오 이런 신기한 게 있구나”하고 읽어보시고 머리 좀 식히고 가세요.업계 분들이나, 이쪽 업계에 소식이 빠삭한 분들은 아시겠지만 몇년 전부터 이 바닥은 새롭게 몰아치는 파도를 맞고 있습니다. 2, 3년 전부터 올해 중순까지 업계 뜨거운 감자였던 키워드들에 대해서 기억하고 계신가요? 네, 그 소위 HTML5니 클라우드, 빅데이터, 소셜 게임 따위의, 기획안에 쓰면 사장님 입이 귀에 걸리게 만드는 마법의 단어들이요.이 글도 사실 그 마법의 단어들에 관련된 이야기입니다. 정확히는 클라우드 기술에 관련된 이야기예요.뜬구름 잡는 클라우드대관절 클라우드란 무엇이길래 여러분의 기획안을 통과시키게 하는가 궁금하지 않으셨나요? 알고 계신 분들도 많을 테니 간략하게 설명하고 넘어가겠습니다. 클라우드는 클라우드 컴퓨팅 기술의 약자입니다. 위키피디아에 있는 정의는 다음과 같습니다:인터넷 따위의 네트워크를 통해 실시간으로 많은 컴퓨터들을 관리하는 여러 컴퓨팅 기술과 관련된 개념들을 총칭얼핏 들으면 굉장히 뜬구름 잡는 소리입니다. 아니, 그럼 그 전까지는 그런 걸 안 했다는 건가? 물론 아닙니다. 클라우드 컴퓨팅이란 단어가 버즈워드로써 시장을 강타하기 전에도 소위 클라우드 컴퓨팅을 위한 기술들은 존재했습니다.엄밀히 말하면 클라우드 컴퓨팅은 ‘기술 융합’의 일종이라고 볼 수 있습니다. 기존에 존재하던 개념들과 기술들을 융합하여 새로운 접근법을 탄생시킨 것이죠. 간단히 소개하자면 그 클라우드 컴퓨팅을 이루는 기반에는 다음과 같은 두 개의 거대한 축이 있습니다.가상화(Virtualization) : 하나의 컴퓨팅 자원을 여러 개로 나누어 마치 여러 개의 독립된 컴퓨터처럼 사용하는 기술 혹은 개념그리드 컴퓨팅(Grid computing) : 하나의 작업을 동시에 여러 개의 컴퓨터가 분할하여 처리하는 기술 혹은 개념거기에 중요한 개념 하나만 더 얹고 넘어가겠습니다. 이것도 한 때는 버즈워드로 사람들을 흥분시켰었죠.Application Programming Interface(API) : 복잡한 내부 동작에 대해서는 잘 몰라도 정해진 규약(인터페이스)만 알고 있으면 해당 기능을 사용할 수 있도록 한다는 개념그러니까 어떤 작업을 하기 위해 하나의 컴퓨터를 여러 개로 분리하고(자르고), 또다시 그 분리된 컴퓨터들을 합쳐서(합치는), 어쨌든 정해진 규약대로 사용할 수 있게 만드는 것(편한 거).아, 너무 기네요. 줄여서 “난 잘 모르겠지만 뭔가 좀 편한 거군.” 정도로 해두죠. 그게 클라우드의 궁극적인 목표이자 본질이라고 볼 수 있겠습니다. 그래서 이름도 뜬구름 잡는 소리 같다고 클라우드잖아요?그래도 마냥 뜬구름 잡는 소리만 할 수는 없으니 한번 클라우드 서비스의 종류를 알아봅시다.IaaS(Infrastructure as a Service) - 인프라스트럭쳐, 한마디로 서버를 조립하고 설치하는 방법을 몰라도 쓸 수 있도록 편하게 제공한다고 보면 됩니다. Amazon Web Service 같은 애들이죠.PaaS(Platform as a Service) - 이번엔 IaaS를 잘 몰라도 서비스를 돌릴 수 있게 만들어진 플랫폼을 제공합니다. Heroku가 대표적입니다.SaaS(Software as a Service) - 그렇게 만들어진 플랫폼 위에 돌아가는 서비스들을 제공합니다. icloud.com의 keynote 따위가 있겠군요.생각보다 어렵지 않죠?docker 란 무엇인가사설이 길었네요. 이제부터가 본론입니다. 제가 오늘 소개할 녀석은 클라우드 컴퓨팅에 있어 “자르는” 축을 담당하는 가상화의 떠오르는 아이돌, LXC를 사용한 docker 입니다. LXC가 무엇인지는 여기서 중요하지 않습니다#2. 그냥 업계의 떠오르는 아이돌 정도로 해 둡시다. 그러니까 아이유 같은 존재죠.docker가 등장한 배경을 설명하자면 이렇습니다. Heroku와 함께 PaaS계에서 끗발을 날렸던 dotCloud는 어느 날 갑자기 충격적인 발표를 합니다. 자기네들이 쓰는 가상화 및 애플리케이션 플랫폼을 공개해 ‘오픈 소스로’ 제공하겠다는 것이죠. 아니, 이럴 수가! 이러시면… 이러시면 정말 감사합니다#3!docker의 가장 큰 특징은 다음과 같이 요약할 수 있습니다.image 관리의 간편화와 container 관리 간편화어떤 서비스를 돌리기 위해서는 필요한 서버들이 있습니다. 데이터베이스 서버, 웹 서버, 캐시 서버, 워커 서버 따위의 것들이죠. 이 모든 걸 한 군데로 퉁쳐서 모을 수도 있겠지만 그렇게 되면 데이터베이스, 웹, 캐시, 비동기 업무를 위한 설정과 프로그램들을 한 군데로 모아 관리해야 합니다. 그렇게 되면 설정이 복잡해지거나 애플리케이션이 거대해지거나 필요할 때 횡적인 확장을 하기가 어려워집니다.예를 들어 웹서버에서는 A라는 라이브러리의 1버전을 필요로 하는데 데이터베이스 서버에서는 2버전을 필요로 한다던지, 이벤트 하느라 접속자가 너무 증가했는데 다른 웹서버가 한시간 정도만 필요한 일을 그럴 수 없어서 서버를 통째로 하나 사야 한다던지 하는 일들이죠. docker는 그런 상황에 유연하게 대응하기 위해 서버 설정과 필요한 프로그램들을 따로 관리할 수 있는 환경을 제공합니다.docker는 이렇게 분리된 환경을 image라고 부르며, 이 image를 기반으로 여러 개의 container를 생성할 수 있습니다. 음… 이렇게 이해하시면 편할 것 같습니다. image는 유전자 설계도고, container는 그 유전자 지도에서 만들어진 생물체라고나 할까?즉, 이 설계도를 관리하면 필요할 때 목적에 적합하게 만들어진 생물체를 얼마든지 만들어낼 수 있게 되죠. 필요할 때는 설계도의 설계를 바꿔서 새로운 생물체를 만들어낼 수도 있습니다. 단순하지만 docker의 가장 커다란 컨셉이고 강력하기까지 합니다. 이렇게 단순하고 간편한 환경은 여러 가지 시도를 가능하게 합니다.오토스케일링(웹서버가 필요할 때 웹서버를 막 찍어낸다던가!)유연한 배포 정책(서버를 최신 버전으로 업데이트했는데 버그가 있어서 재빨리 옛날 버전으로 돌아가야 한다던가!)자원의 효율적인 활용(이 쪽 서버가 놀고 있으니까 여긴 웹서버 두개 정도 더 띄운다던지)거기다 수고를 좀 더 들이면, docker의 API를 활용해 Heroku 부럽지 않은 웹 GUI PaaS 서비스를 만들 수 있을지도 모릅니다(만들어 주시면 감사히 쓰겠습니다).한번 docker를 살펴봅시다이야기는 실컷 했으니 한번 설치해보고 실행시켜봅시다. 지면 관계상 모든 플랫폼을 다룰 수는 없기에 우분투 13.10을 기준으로 살펴보도록 하겠습니다. 필요하신 분들은 공식 홈페이지 설치 메뉴얼을 참고하여 진행해주세요.주의 : 이후 내용은 비 개발자 분들에게는 다소 지루한 내용일 수도 있습니다.docker 설치curl http://get.docker.io | sudo sh 참 쉽죠?자 이제 시작이야이제 여러분의 플랫폼에는 docker가 설치됐습니다. 한번 서버에서 기본 이미지를 다운받아 설치해 봅시다.sudo docker pull base 인터넷 환경에 따라 좀 기다리셔야 하실지도 모릅니다. 이미지가 설치되면 아래 명령으로 확인할 수 있습니다.sudo docker images 아래와 비슷한 화면이 나타났다면 성공한 겁니다.REPOSITORY TAG IMAGE ID CREATED SIZE base latest b750fe79269d 8 months ago 24.65 kB (virtual 180.1 MB) base ubuntu-12.10 b750fe79269d 8 months ago 24.65 kB (virtual 180.1 MB) …(생략) 이렇게 내려받은 image에는 다음과 같은 명령어로 접근할 수 있습니다.sudo docker run -i -t base /bin/bash 자세한 명령어 사양은 docker help run을 실행해 알아볼 수 있습니다. 여러분은 이제 base라는 image에 접속했습니다. 지금부터 하는 행동은 image에 영향을 미치게 되며, 이는 전부 로그로 남아 저장됩니다. 한번 이것저것 설치해봅시다.sudo apt-get install python ruby … 이후에 Ctrl+D를 눌러 이미지를 빠져나옵니다. 그리고 아래 명령을 입력하면 방금 전에 수정한 container 목록이 출력됩니다.sudo docker ps -a 아래와 같은 식으로 출력됩니다.CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES eda0060b7af9 base:latest /bin/bash 6 minutes ago Exit 0 lavender_deer 66c849867834 busybox:latest echo Docker has been 8 minutes ago Exit 0 blue_cat 이제 image의 수정사항을 기반으로 새로운 이미지를 만들어 봅시다. 이미지를 만드려면 변경사항을 commit 해야 합니다. VCS나 DVCS를 쓰시는 분이라면 무슨 말인지 감이 오실 겁니다. 네, 바로 버전 관리 시스템의 그것입니다. 기존 base를 기반으로 변경사항을 만들고 commit하여 새로운 이미지를 생성할 수 있습니다. 매우 쉽군요. 한번 생성해봅시다.docker commit [ID] [image name] commit 명령의 구조는 단순합니다. container ID와 그리고 만들 이미지 이름입니다. 이미지 이름은 보통은 만든이/목적 같은 컨벤션으로 만들곤 합니다. 저는 아래와 같이 만들어보겠습니다.sudo docker commit eda0060b7af9 xymz/grocery 확인은 당연히 아래와 같이 할 수 있습니다.sudo docker images repository 에서 여러분이 만든 이미지 이름을 확인할 수 있다면 성공한 겁니다. 여러분의 첫 docker image 생성을 축하합니다!물론 이렇게 약간 거칠어보이는 방법과는 다르게 Dockerfile 이라고, 딱 봐도 버전관리 시스템에 넣을 수 있을 거 같고 정리가 잘 되는 방법도 존재합니다. 아마도 실제로 사용하실 땐 Dockerfile을 사용하게 되실 거고, 그 방법이 훨씬 낫습니다. 다만 본 포스트의 목적은 개발자나 비개발자 분들에게 docker를 한번 소개해보자는 취지라서 Dockerfile의 operation 을 일일히 설명하기엔 얘기가 너무 복잡해질 것 같아 직접 try-out 하기에 쉬운 commandline 쪽을 선택하게 되었습니다.당연히 이게 끝은 아닙니다여기까지 나온 내용으로 서비스를 구성하기에는 무리가 있습니다. 우리는 이제 막 docker image를 생성하고 저장하는 방법을 알았을 뿐이지 그 외에는 아무것도 모릅니다. docker를 제대로 사용하기 위해서는 아래와 같은 방법들을 추가적으로 알아야 합니다.생성된 이미지 관리 : 새로 만든 이미지를 어딘가에 업로드하여 다른 docker 시스템(host)에 배포하기 위한 방법에 대해 알아야 합니다.실제 서비스를 container 에 올리고 관리하는 방법 : 아까 언급한 것처럼 예시를 들자면, 현재 서버에서 웹서버를 를 몇개나 띄울 건지 등을 결정하고 관리하는 방법에 대해 알아야 힙니다.docker host와 guest간의 통신 관리 : docker가 설치된 실제 서버와 그 위에서 돌아가는 container들 간에 오가는 통신에 대한 이해가 필요합니다. 포트 바인딩, 포트포워딩이라고도 하죠.docker API : 이 모든 스택을 관리하기 위한 docker의 API를 알고 있다면 무한한 활용이 가능해집니다.하지만 이 방법들에 대해 여기서 다 열거하고 넘어가기에는 무리가 있으니 좋은 링크를 몇 개 소개토록 하겠습니다.파이썬 웹앱 올려보기docker를 개발환경으로 사용해보기Dockerfile 로 image 관리하기포트 리다이렉션적어놓고 보니 대부분 docker 공식 홈페이지 자료들이네요. 사실 docker는 documentation이 훌륭한 편이라, 그 쪽만 참고해도 많은 도움이 되실 겁니다.Deis?그리고 이 모든걸 쉽게 해주겠다는 Deis라는 녀석이 있습니다. Docker, Chef, Heroku Buildpacks를 이용해 하나의 PaaS스택을 만들고 그 위에 여러분의 서비스를 돌릴 수 있도록 해주겠다는 녀석인데요. 어쩌면 진정한 Open source PaaS 종결자일지도 모르겠습니다. 기회가 된다면 다음에 또 소개할 수 있었으면 좋겠네요.마치기 전에즐거우셨나요? 중간 이후 내용은 다소 비개발자분들에게 지루한 내용이었을지도 모르겠습니다만, 전반적으로 최대한 쉽게 설명하고자 노력했습니다. 다음 번에는 더욱 재밌는 글로 찾아볼 수 있도록 하겠습니다. 그럼 뿅!참고한 링크들docker.ioUsing Docker as a Development EnvironmentDocker: Error starting container: Unable to load the AUFS module주석사실 API는 거창한 기술적 개념이라기보단, 소소한 개발 방법론에 가까운 이야기입니다. 온갖 프로그래밍 언어와 다양한 기술들이 난립하는 와중에 그 모든 걸 알고 전부 뭉쳐서 하나의 덩어리를 만들면 관리/사용하는 비용이 너무 커지니 각 영역을 딱딱 잘라 구분하여 ‘정해진 규약’만 알면 서로 통할 수 있게 만들자. 라는 개념입니다.(약간의 지식이 있는 분들을 위해) LXC(LinuX Containers)는 기존 전가상화full virtualization나 반가상화paravirtualization와는 다르게 OS 위에 가상머신이 따로 돌아가는 게 아니라 OS영역에서 공유 라이브러리를 가지고 유저가 생성하는 프로세스 단위로 성능 분리를 합니다. 덕분에 이름에서 보이듯 특정 플랫폼밖에 지원을 하지 않는다는 단점이 있네요. 그래도 가상화에 따른 자원 손실이 최소화된다는 점에서 많이들 선호하고 있습니다. Heroku에서도 LXC를 통해 가상화를 하고 있죠.보통 이렇게 자신들의 플랫폼을 오픈소스로 공개하는 이유는 단순히 사회에 기여하기 위해서도 있지만, 사내에서 사용되는 기술의 수준을 오픈 소스 커뮤니티의 참여를 통해 향상시키고, 또 좋은 개발자들을 리크루팅 할 수 있게 되는 기회를 만드는 등 선순환을 유도하기 위해서입니다. 그러니까 여러분도 사내에서 사용하는 기술을 공개해 주시면 누이 좋고 매부 좋은 일이라 할 수 있죠.이 글은 __저의 개인 텀블러__에서도 찾아볼 수 있습니다.#스포카 #개발 #개발자 #개발팀 #인사이트 #Docker #클라우드 #꿀팁
조회수 1169

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

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]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 1638

프로세스 모델의 적합도 검사하기

프로세스 모델 도출은 프로세스 마이닝의 출발점이며, 매우 유용합니다. 원본 데이터로부터 프로세스 흐름 모델을 자동으로 구성하여 실제 프로세스를 알 수 있습니다. 이렇게 도출된 프로세스 모델과 이벤트 로그를 비교하는 것이 적합도 검사(Conformance checking)입니다. 적합도는 이전에 말씀드린 정확도(Precision)와는 다른 개념입니다. 정확도(Precision)는 Underfitting을 피하여 데이터를 정확하게 설명할 수 있으나 정확도가 높을수록 프로세스 모델이 대체로 복잡해지게 됩니다. 하지만 적합도가 높다고 하여 프로세스 모델이 복잡해지는 것은 아닙니다.적합도 검사의 기본 아이디어는 프로세스 모델 위에 이벤트 로그를 재생하는 것입니다.아래 예제 모델에 이벤트 로그 a → c → e → g를 재생하여 적합성 검사를 해보겠습니다.[그림 1] 프로세스 모델 예제먼저 a 이벤트를 수행하였습니다.[그림 2] a 이벤트 수행 후다음으로 c 이벤트를 수행했습니다.[그림 3] a, c 이벤트 수행 후이벤트 로그에서는 다음에 e를 수행해야 합니다. [그림 3]을 보면 e를 수행하기 위해서는 d가 먼저 수행되어야 합니다. 하지만 실제 로그에서는 d 수행 없이 e가 수행되었기 때문에 d를 무시하고 e를 수행합니다.마지막으로 g 이벤트 수행하여 프로세스를 마칩니다.이벤트 로그 재생이 완료되면 액티비티 d에 실행되지 못한 토큰이 남아있게 됩니다. [그림 5] 이벤트 로그 재생 후 남아 있는 토큰프로세스 모델 위에 이벤트 로그를 재생하는 동안 얼마나 많은 토큰을 사용하고(이벤트 수행 횟수) 어떤 이벤트를 생략하고 추가했는지 기록합니다. 이를 통해 기록된 이벤트 로그와 모델의 적합도를 비교할 수 있습니다. 적합도가 1이면 모든 로그가 프로세스 모델에 잘 맞는다는 뜻이고, 0에 가까우면 적합도가 매우 낮다는 의미입니다.적합도 검사는 어디에 활용할 수 있을까요? 사람들이 표준 프로세스와 달리 행동하는 이유를 찾을 때 활용 가능합니다. 왜 사람들이 기존 프로세스를 벗어나는지, 벗어나는 부분에 대해서는 잘 보고되었는지 확인할 수 있습니다. 일반적인 감사(Audit and compliance) 절차에도 활용 가능합니다.다른 사례는 도출된 프로세스 모델의 품질을 측정하기 위해 활용할 수 있습니다. 여러 알고리즘을 사용하여 프로세스 모델을 도출했을 경우 어떤 모델이 가장 적합하고 좋은 모델인지 비교해 볼 수 있습니다.마지막으로 프로세스 설명이 제대로 되어 있는지 실제 행동을 기반으로 확인할 수 있습니다. 예를 들어 어떤 서비스를 제공하는 경우 서비스 실행 방법 매뉴얼과 실제로 제공되는 서비스를 비교하여 일치하는지 확인할 수 있습니다.※ 본 블로그에 사용된 그림은 Van der Aalst 교수님 강의자료를 사용하였습니다.#퍼즐데이터 #개발팀 #개발자 #개발후기 #인사이트
조회수 2094

칸반(Kanban) 5개월 사용 후기

사실 개발 방법론이라는 것을 7개월 전만 해도 귓등으로 듣고 그게 왜 필요한지도 알지 못했던 것이 사실입니다. 부끄럽지만 애자일이 수많은 프로그래밍 언어중 하나인줄 알았죠.10개월 전만해도 우리 팀은 저를 포함해서 3명에 불과했고 모든 것은 메신저와 구글 드라이브로 일을 처리했습니다. 기억력이 좋지않지만 머릿속에서 각 팀원들이 언제까지 뭘하고 다음엔 무엇을 언제까지 해야겠다라는 것이 그려질 정도로 적은 숫자였죠. 개발방법론이 필요한 이유가 없으니 무관심한 것은 당연했습니다. 이 글을 읽으시는 분들 중에 아마 7개월 전의 저와 같은 생각을 하신 분이 있을지도 모르겠네요.지금 우리 팀은 11명으로 늘어났고(그중에 소프트웨어 개발팀만 7명) 그들 하나하나를 마이크로 매니징하기에는 저라는 인간이 너무나 머리가 아팠습니다. 그래서 도입한 것이 애자일 개발방법론이었는데 애자일은 비록 실패로 끝났지만 거기서 많은 교훈을 얻고 칸반으로 전환하는 원동력이 되었습니다.우리 팀은 애자일 개발선언 중에서도 "계획을 따르기보단 변화에 대응하기"라는 선언을 굉장히 맘에 들어했는데, 그 이유는 애자일 도입이전 우리의 상황이 그랬기 때문이었습니다. 매일매일 고객의 요구는 들어오고 경영진과의 대화에서 매일매일 우선순위가 바뀌고, 그에 따라 하던 작업이 마무리되지 않으면 브랜치를 새로 파서 다른 작업을 하고 미완성된 코드는 늘어났으며 그에 따라 불평불만도 늘어났습니다.여러 애자일 개발방법론 중에서도 우리가 선택했던 것은 eXtreme Programming(XP)이었는데, 우리에게 스크럼과 같은 1달간의 스프린트는 너무 길다, 2주간의 이터레이션(Iteration)으로 구성된 XP가 좋다라는 것이었습니다.우리는 스크럼 보드를 준비했고 거기에 포스트잇을 붙여가면서 아침마다 스크럼 회의를 했으며, 기록을 남기기위해 레드마인을 사용하였습니다.eXtreme Programming Flow Chart간단하게 왜 실패했는지 이유를 들어볼게요.1. 배포 계획(Release Plan)을 수립하기 힘들다물론 계획자체를 만들기 힘들다는 것이 아닙니다. 배포 계획을 만들어도 그대로 지켜지지 않았습니다. 큰 틀로 배포 계획을 만들고 작은 틀로 반복 계획(Iteration Plan)을 세우는 것이 목표였는데, 수립을 해봤자 절대 지켜지지 않았습니다. 우리와 같은 작은 스타트업의 작은 팀은 시장의 요구사항이라는 급류에 이리저리 쓸려 매일매일 계획과는 다른 일을 하고 있었거든요. 리팩토링할 시간은 커녕 테스트 코드를 짤 시간조차 없었습니다.(핑계일수도 있지만요)거짓말이 아니고 단 한번도 계획대로 되지 않았습니다.2. 팀원들의 시간 예측 능력 부족애자일은 팀원들이 시간 예측을 굉장히 잘한다는 가정하에 잘 돌아가는 방법론입니다. 모두가 함께 한자리에 모여 복잡도를 논의하고 그에 따른 프로젝트의 시간 예측을 하고 함께 번다운 차트(Burn-down chart)를 그리며 하하호호 잘 나아가야 하는데, 우리 팀은 그렇지 않았습니다. 물론 실력부족이라고 탓할 수도 있겠지만 실제로 스크럼 보드에 예측시간 8시간이라고 적어놓고 4시간정도만 지나면 다른 문제가 터지거나 다른 기능을 개발해야하는 둥 제대로 지켜지지 않았을 뿐더러 그런 방해요소가 없다고 하더라고 8시간보다 더 많이 걸리거나 더 적게 걸리기도 했습니다.예측시간을 측정하기 힘든 마이너한 이유중에 하나는, 스파이크 솔루션(Spike solution)를 개발하는데 얼마나 걸리는지 예측하지 못한 탓도 있었는데 이 세상에 없는 솔루션을 개발하는데 있어 이전의 경험만으로는 턱없이 부족했습니다.이런 이유들 때문에 우리는 XP를 버릴 수 밖에 없었습니다. 계획보다는 변화에 적응하자!라는 원대한 목표가 있었지만 애자일 개발방법론은 우리가 닥친 미친듯한 변화를 감당하기에는 벅찼습니다. 우리는 스크럼 보드를 점점 멀리하기 시작했고 다시 구글 드라이브로 돌아갔습니다.저는 구글 문서(Google Docs)에 우리가 해야할 요구사항을 적었습니다. 우선순위가 높은 일일 수록 상단에 두었습니다. 그 오른쪽에는 일을 해야할 사람의 이름을 적었습니다. 그렇게 적고 문서를 공유하면 팀원들은 그 문서를 보고 그 순서대로 일을 진행하였습니다. 일을 진행하다가 생기는 의문점은 급한 일일 경우 구두로 전달하고 급하지 않을 경우에는 메신저 또는 문서의 빈공간을 활용하여 적었습니다.완료된 요구사항은 취소선을 긋고 옅은 글씨로 처리하여 해야 할일과 완벽히 구분되도록 하였으며 한 사람당 해당 시간에 하나의 일만 처리하도록 규칙을 세웠습니다. 보류되는 일은 보류 섹션으로 할일을 옮기고 보류가 되는 이유를 적도록 했습니다. 혼자 해결하기 힘들경우 회의를 통하여 함께 해결할 수 있는 자리를 마련했구요.그런식으로 우리는 배포 시기를 최대한 맞추려고 노력했고 이상하게도 XP를 버리고 구글 문서로 갈아타니 일이 더욱 수월해져서 이제는 생각보다 일이 빨리 끝나는 것이었습니다. 그리고 더욱 놀라운 일은 지금까지 우리가 했던 방식이 칸반과 유사하다는 것이었습니다.저는 바로 칸반 보드를 도입했고 이에따라 애자일에서 배운 규칙/정신과 칸반의 장점을 혼합하여 우리 팀만의 칸반보드를 완성하였습니다. 현재 우리가 쓰고 있는 칸반 보드는 Kanboard의 오픈소스를 그대로 사용하고 있습니다.1. 활발한 커뮤니케이션을 토대로 개발한다. 절대 혼자 일하지 않는다- 지속적으로 팀의 동의(Team agreement)를 구한다.- Knoledge island를 탈출하라(자신이 알고있는 지식이 전부가 아니다).- 코드 병목현상(Code bottleneck)을 탈출하라. Collective ownership을 발동하라.2. 한 번에 한개의 일만 처리하라. 보류하는 일은 최소로 하라칸반의 핵심으로 한 번에 한개의 일만 처리하도록 합니다. 개발자의 뇌는 하나도 손은 두개이고 손가락은 열개이므로 한 번에 하나의 일만 처리해야 합니다. 한 개의 일이 끝나지 않으면 다음 일을 진행하지 않는 것을 규칙으로 합니다.3. 가능하다면 예측시간을 적는 습관을 들인다개발완료시간을 정확히 예측하는 것은 개발자들에게 정말 중요한 능력중에 하나입니다. 신제품을 시장에 빨리 내놓을 수록 피드백을 빨리 받을 수 있으며, 고객으로부터의 소중한 피드백은 개선된 다음 버전을 위한 초석이 되기 때문입니다. 사업적으로 성공하고 싶다면 예측시간을 꼭 적는 습관을 들여 자신이 정해진 시간 동안 얼마만큼의 일을 할 수 있는지 예측하는 일이 큰 도움이 됩니다.4. 더 좋은 방법이 있다면 기존의 방법을 과감히 버린다저의 철학과도 일치하는 이야기인데요, 우리 팀과 회사가 함께 좋아질 수 있는 방법을 발견한다면 과감히 현재의 방법을 버리고 새로운 방법을 시도한다라는 우리 팀만의 맹세입니다. 앞으로 항상 발전하겠다는 의지를 가지고 잠시 손을 놓고 한발짝 물러서서 비판적인 자세로 모든 것을 바라보는 시간을 가지는 것도 혁신의 첫발짝이라고 생각합니다.지금까지 우리 팀이 꾀한 겉으로 보기에 가장 큰 혁신은 기존의 속도가 느리고 사용하기 불편했던 솔루션을 과감히 버리고 새로운 서버와 새로운 언어로 전환하면서 마이그레이션 및 새로운 형태의 최적화된 솔루션을 구축했다는 것입니다.(물론 내부적으로 가장 큰 혁신은 기존의 방법을 버릴 수도 있다라는 생각을 가졌다는 것이지요)현재 저는 팀 매니저로서 User story(요구사항정의서) 관리, Release plan(배포 계획서), 와이어프레임을 포함한 기획서 등 최소한의 문서만 관리하고 있으며, 팀원들 또한 이 시스템에 만족하며 아직까지는 판단하기 이르지만 굉장히 좋은 방법인것 같습니다.5개월간 칸반을 사용하면서 팀원들로부터 받은 피드백은 다음과 같습니다.1. 매일 아침 15분씩 하는 스크럼 회의는 새로운 기능 또는 새로운 프로젝트를 진행할 때는 굉장히 유용하지만, 디버깅 또는 테스팅 기간에는 시간낭비다.이 말을 한 팀원의 말에 따르면, 우리 팀은 데이터베이스를 관리하는 사람, API를 만드는 사람 등등 각자의 역할이 확실히 나누어져 있는데 새로운 기능을 개발할때는 여러사람과 소통해야하는 경우가 많고 개발 스펙이 달라지거나(작게는 함수이름 변경 등) 여러 변수들이 작용할 수 있으므로 짧게 자주만나는 것이 좋다고 말했습니다.2. 회의도 시간낭비다- 회의는 가급적 개최하지 않고 가능하다면 1:1 구두로 해결한다.- 급한일이 아닐경우에는 이메일/메신저를 활용하도록 한다.3. 칸반 보드에 보류 칼럼, 테스팅 칼럼을 나눈다보류 칼럼과 테스팅 칼럼을 나누어 적어 어떤 할일이 보류되었으며 어떤 할일이 테스팅 중인이 확실히 하도록 했습니다. 이는 테스팅을 하는데 오래걸리는 기능들이 있으며 테스팅을 하는 동안 다른 기능을 개발할 수도 있다는 것이 큰 이유였습니다.우선 순위가 바뀌었을 때 할 일을 잠시동안 놓아둘 칼럼이 없다는 것이 보류 칼럼이 존재하는 가장 큰 이유였습니다. 그러나 보류 칼럼에 놓을 수 있는 할 일의 수는 개인당 1개로 제한하여 2개 이상의 보류하는 일이 없도록하여 경각심을 갖도록 하였습니다.앞으로의 계획은 전에 언급했던 와비파커(Warby Parker)의 기술팀이 도입한 와블스(Warbles) 시스템을 적용해보는 것입니다. 우리 팀이 어떻게 바뀔지 정말 기대가 됩니다.#비주얼캠프 #인사이트 #경험공유 #조언 #개발자 #개발팀
조회수 791

컴공생의 AI 스쿨 필기 노트 ④ 교차 검증과 정규화

지금까지 Linear Regression, Logistic Regression 모델을 만들어보았는데요. 우리가 만든 모델이 과연 잘 만들어진 모델이라고 볼 수 있을까요? 이를 알기 위해서 이번 4주차 수업에서는 우리가 만든 모델의 적합성을 보다 객관적으로 평가하기 위한 방법으로 교차 검증(Cross Validation)과 정규화(Regularization)를 배웠어요. 차례대로 하나씩 알아볼까요?1. Cross Validation교차 검증은 새로운 데이터셋에 대해 반응하는 모델의 성능을 추정하는 방법이에요. 학습된 모델이 새로운 데이터를 받아들였을 때 얼마나 예측이나 분류를 잘 수행하는지 그 성능을 알기 위해서는 이에 대한 추정 방식이 필요해요. 먼저 Whole population(모집단)에서 Y와 f를 구하기 위해 Training Set(모집단에서 나온 데이터셋)에서 f와 똑같지 않지만 비슷한 모델 f^를 만들어요. 그리고 이 모델을 모집단에서 나온 또 다른 데이터 셋인 Test Set을 이용하여 확인해요. 하지만 일반적으로 Test Set이 별도로 존재하는 경우가 많지 않기 때문에 Training Set을 2개의 데이터셋으로 나눠요. 이 Training Set에서 Training Set과 Test Set을 어떻게 나누느냐에 따라 모델의 성능이 달라질 수 있어요. 이런 테스트 방법을 교차 검증(Cross validation)이라고 해요.이번 시간에는 교차 검증 방법으로 LOOCV(Leave-One-Out Cross Validation)와 K-Fold Cross Validation을 알아봤어요. LOOCV(Leave-One-Out Cross Validation)LOOCV는 n 개의 데이터 샘플에서 한 개의 데이터 샘플을 test set으로 하고, 1개를 뺀 나머지 n-1 개를 training set으로 두고 모델을 검증하는 방식이에요.K-Fold Cross ValidationK-Fold CV는 n 개의 데이터를 랜덤하게 섞어 균등하게  k개의 그룹으로 나눠요. 한 개의 그룹이 test set이고 나머지 k-1개의 그룹들이 training set이 되어 k번을 반복하게 돼요. LOOCV도 n-fold CV로 볼 수 있어요!코드로 나타내기Step1. 데이터 생성 & train set과 test set  단순 분리# model selection modulefrom sklearn.model_selection import train_test_splitfrom sklearn.discriminant_analysis import LinearDiscriminantAnalysis# read datadf = pd.read_csv('data/data01_iris.csv')data = df.iloc[:,:-1].as_matrix()target = df['Species'].factorize()[0]LOOCV와 K-Fold CV에 사용할 데이터를 구하는 코드에요. data 파일 안의 data01.csv 파일을 읽어서 데이터 프레임 형태로 가져와요.df(데이터 프레임) 안에는 이와 같은 105개의 데이터 셋이 저장되어 있어요.df(데이터 프레임)의 Sepal.Length부터 Petal.Width의 값들을 매트릭스 형태로 data에 할당해요.Species에는 ‘setosa’, ‘versicolor’, ‘virginica’ 값들이 있는데요. factorize() 을 이용하여 setosa는 0, versicolor는 1, virginica는 2로 바꿔줘요.# random splitX_train, X_test, y_train, y_test = train_test_split(            data, target, test_size=0.4, random_state=0)X_train.shape, y_train.shapeX_test.shape, y_test.shape그다음에는 data와 target 데이터를 가지고 training set과 test set으로 6:4로 나눠요.X_train.shape = (90,4),  X_test.shape = (60, 4)가 돼요.# LDA f = LinearDiscriminantAnalysis() f.fit(X_train,y_train) y_train_hat = f.predict(X_train) table_count(y_train,y_train_hat) f.score(X_train,y_train)LDA(Linear discriminant analysis)는 대표적인 확률론적 생성 모형이에요. 즉 y의 클래스 값에 따른 x의 분포에 대한 정보를 먼저 알아낸 후, 베이즈 정리를 사용하여 주어진 x에 대한 y의 확률 분포를 찾아낸다고 해요.Step2. test set 준비(1) LOOCV으로 test set 준비# leave-one-out  from sklearn.model_selection import LeaveOneOutloo = LeaveOneOut()loo.get_n_splits(X_train)scv = []for train_idx, test_idx in loo.split(X_train):    print('Train: ',train_idx,'Test: ',test_idx)    f.fit(X_train[train_idx,:],y_train[train_idx])    s = f.score(X_train[test_idx,:],y_train[test_idx])    scv.append(s) get_n_splits() 함수를 사용하여 (90,4)의 shape을 가지는 X_train을 90개로 나눠요.test set에 0부터 89까지 하나씩 할당되고 할당된 숫자 외의 나머지 숫자들은 training set으로 모델을 검증해요. 위의 결과에서도 볼 수 있듯이 test set에 0이 할당되면 train set에는 1 ~ 89가 할당되어 모델을 검증하게 돼요!(2) K-fold CV로 test set 준비# K-fold CVfrom sklearn.model_selection import KFoldkf = KFold(5)kf.get_n_splits()scv = []for train_idx, test_idx in kf.split(X_train):    print('Train: ',train_idx,'Test: ',test_idx)    f.fit(X_train[train_idx,:],y_train[train_idx])    s = f.score(X_train[test_idx,:],y_train[test_idx])    scv.append(s) KFold(5) : 위에서 배운 k-fold 교차 검증에서 k를 5로 설정하여 우리가 가지고 있는 데이터 셋을 5개의 그룹으로 나눠서 교차 검증을 할 거예요.kf.get_n_splits()를 사용하여 5번 교차 검증할 것을 정해요.위에서 90개의 데이터셋을 5개의 그룹으로 나눴어요. 그리고 각 그룹 한 개씩 test set으로 정하고 나머지 그룹들은 training set으로 할당하고 모델을 검증해요. 예를 들어 그룹 1이 0~17, 그룹 2가 18 ~ 35, 그룹 3이 36~53, 그룹 4가 54~71, 그룹 5가 72~89라고 할 때, test set에 그룹 1을 할당하면 train set에는 그룹 2, 3, 4, 5가 할당되어 모델을 검증하게 돼요.Step3. 교차 검증 시행CV는 단순히 데이터 셋을 나누는 역할을 수행할 뿐이에요. 실제로 모형의 성능(편향 오차 및 분산)을 구하려면 이렇게 나누어진 데이터셋을 사용하여 평가를 반복해야 해요. 이 과정을 자동화하는 명령이 cross_val_score()이에요.# K-fold CVfrom sklearn.model_selection import cross_val_scoref = LinearDiscriminantAnalysis()s = cross_val_score(f,X_train,y_train,cv=3)cross_val_score(f, X_train, y_train, cv=3) : cross validation iterator cv를 이용하여 X_train, y_train을 분할하고 f에 넣어서 scoring metric을 구하는 과정을 반복해요.2. Regularization앞서 말한 우리의 목적은 우리의 데이터셋에 맞는 Y와 f를 구하는 것이었어요. f를 결정하기 위해서는 먼저 결정해야 하는 요소가 있어요. 아래 다섯 가지가 f를 결정하는 요소들이에요.- Model family : linear, neural 등 방법론 결정- Tuning parameter : 모델에 맞는 파라미터 조절 - Feature selection(특징 선택) : 많은 데이터 중 어떤 데이터를 쓸지 고르는 것 - Regularization(정규화)  - Dimension reduction(차원 축소)f를 결정하는 요소 중 Regularization(정규화)에 대해 알아볼게요!정규화 선형회귀 방법은 선형회귀 계수(weight)에 대한 제약 조건을 추가함으로써 모형이 과도하게 최적화되는 현상(과최적화, overfitting)을 막는 방법이에요. 모형이 과도하게 최적화되면 모형 계수의 크기도 과도하게 증가하는 경향이 나타나요. 따라서 정규화 방법에서 추가하는 제약 조건은 일반적으로 계수의 크기를 제한하는 방법이에요. 일반적으로 Ridge Regression, Lasso, Elastic Net 이 세 가지 방법이 사용돼요.Ridge Regression머신 러닝에서는 모델의 오차를 찾기 위해 보통 최소제곱법(Least squares fitting)을 이용하여 β를 최소화시켜요. 위의 RSS는 잔차제곱식으로 예측값과 실제 값 사이의 차이를 구하는 식이에요. 회귀분석의 계수 값을 RSS을 최소화하는 β값을 찾음으로써 구할 수 있어요.Ridge Regression은 최소제곱법에 가중치들의 제곱합을 최소화하는 것을 추가적인 제약 조건으로 갖는 방법이에요. λ는 기존의 제곱합과 추가적 제약 조건의 비중을 조절하기 위한 하이퍼 파라미터에요. λ가 크면 정규화 정도가 커지고 가중치의 값들이 작아져요. λ가 작아지면 정규화 정도가 작아지며 λ가 0이 되면 일반적인 선형 회귀 모형이 돼요.코드로는 아래와 같이 나타낼 수 있어요.from sklearn.linear_model import Ridgef = Ridge(alpha=0.5)f.fit(xtrain,ytrain)f.intercept_,f.coef_f.score(xtrain,ytrain)f.score(xtest,ytest)LassoLasso는 가중치의 절댓값의 합을 최소화하는 것을 추가적인 제약 조건으로 가져요. 아래와 같이 코드로 나타낼 수 있어요.from sklearn.linear_model import Lassof = Lasso(alpha=1.0)f.fit(xtrain,ytrain)f.intercept_,f.coef_f.score(xtrain,ytrain)f.score(xtest,ytest)Elastic NetElastic Net은 가중치의 절댓값의 합과 제곱합을 동시에 제약 조건으로 가지는 모형이에요. 코드로는 아래와 같아요.from sklearn.linear_model import ElasticNetf = ElasticNet(alpha=0.1,l1_ratio=0.5)f.fit(xtrain,ytrain) f.intercept_,f.coef_f.score(xtrain,ytrain)f.score(xtest,ytest)Lasso와 Ridge Regression의 차이점왼쪽 : Lasso, 오른쪽 Ridge Regression위의 두 그림은 Lasso와 Ridge Regression의  차이점을 잘 나타내는 그림이에요. 초록색 부분은 회귀계수(회귀분석에서 독립변수가 한 단위 변화함에 따라 종속변수에 미치는 영향력 크기)가 가질 수 있는 영역이고 빨간색 원은 RSS가 같은 지점을 연결한 것을 보여주는 것으로 가운데로 갈수록 오차가 작아져요.Lasso와 Ridge Regression 모두 RSS를 희생하여 계수를 축소하는 방법이라는 공통점이 있어요.하지만 Ridge Regression과 Lasso의 가장 큰 차이점은 Ridge 회귀는 계수를 축소하되 0에 가까운 수로 축소하는 반면, Lasso는 계수를 완전히 0으로 축소화한다는 점이에요.Cross validation(교차 검증)과 Regularization(정규화)에 대해 알아보았는데요. 간단히 요약해 볼게요.Cross validation(교차 검증)은 머신러닝 모델의 타당성을 검증하는 방법 중의 하나로, 특정 데이터를 training set과 test set으로 분할한 뒤 training set을 활용해 학습하고 test set으로 테스트하여 학습의 타당성을 검증하는 방법이에요. 교차 검증에는 여러 가지 방법이 있는데 그중에서도 우리는 LOOCV와 K-Fold CV를 배웠어요.Regularization(정규화)는 모델의 일반화 오류를 줄여 과적합을 방지하는 방법을 말해요. 일반적으로 Ridge Regression, Lasso, Elastic Net 이 세 가지 방법을 사용해요.이상적인 머신러닝 모델을 만들기 위해 고려해야 할 점들은 정말 많은 것 같아요. 우리가 만든 모델이 적합한 모델인지 이번 수업시간에 배운 교차 검증과 정규화를 통해 잘 살펴봐요!* 이 글은 AI스쿨 - 인공지능 R&D 실무자 양성과정 4주차 수업에 대하여 수강생 최유진님이 작성하신 수업 후기입니다.
조회수 2518

스타트업이 CTO를 찾는 법?

스타트업이 CTO를 찾는 법? 을 알고 계신 분에게 드리는 "질문"입니다. 이 글을 읽으시는 분들에게 부탁드리고 싶은 것은.. 1. 어디에 만나볼 엔지니어(개발자) 분들이 있으니 거기에 포스팅을 해보세요2. 엔지니어 들은 job을 찾을 때, 이런저런 고민을 하니.. 이런 포인트에서 조금 더 고민해보세요. 3. job 포스팅에는 이런저런 구체적인 내용들이 더 필요하니, 구체적으로 XX를 더 작성해보세요4. 이분 한번 만나보시겠어요? (소개 등등) 5. 공유를 해주셔도 좋습니다... 이런 고민을 함께 하시는 분들을 위해~등등의 조언을 댓글로 주셔도 좋고, 메일로 주셔도 좋고.. 아무튼 이 글은 조언을 구하고자 쓰는 글입니다. ^^;개발을 잘 모르는 스타트업 대표가 CTO를 모시는 방법은 어떤 것이 있을까요? ㅜㅜ대부분의 경우 co-founder 중, 엔지니어(engineer) 분이 CTO의 역할을 담당해주시는 것이 일반적인 경우로 보입니다. 하지만 서비스에서 engineer의 비중이 상대적으로 낮은 스타트업의 경우는 회사가 성장해 나감에 따라 function을 더 크게 만들어 나가는 경우도 있겠지요? 파펨도 그러한 회사 중에 하나입니다.지금까지는 할 수 있는 한 효율성을 따져가면서 최소한의 개발을 진행해왔지만, 이제는 조금 더 적극적으로 서비스를 고도화시켜야 할 때! 이기에 이제 좋은 분을 내부에 모셔야 하는데.. 우선 대표 입장에서의 고민을 한번 늘어놔 본다면.. 1) 개발을 거의 모르기 때문에 (새로 모셔야 할) 그분이 실력자 인지 아닌지 알 수가 없다는 불안감2) Ruby on Rails로 개발이 되어 있어, 이 언어에 능한 분을 찾는다는 것이 어렵다는 소문을 이미 많이 들음3) 엔지니어 분들이 선호하는 job 에 대한 구체적인 정보가 없음  반대로 job을 찾고 있는 엔지니어 분의 입장에서 상상력을 발휘해 본다면.. A) 잘 될 회사인지 아닌지 정확히 모르겠음 : 투자 몇 번 받은 것으로 스타트업 평가가 가능?B) 개발팀이 구성되어 있지 않아.. 당분간 나 혼자 full stack으로 일해야 함 : 내가 하나하나 다해야 함? C) 개발이 중심이지 않은 회사에서 일을 하는 게 적합할지? : 나의 커리어 차원에서 도움이 되는가? 위의 내용을 고려한다면, 100년 만의 개기일식이 일어나는 것과 같은 우연이 없다면 정말 만나기 어려운 인연이 아닐까?라는 생각이 듭니다. ㅜㅜ 그래도 어쩌겠습니까... 그런 인연을 찾아 나서야죠. 예전에는 엔지니어 한 분을 만나면, 리쿠르팅과 관계없이 다른 한 분을 소개 요청드리고, 또 그분에게서 다른 분을 소개받아서 계속해서 아는 분들의 영역을 넓혀가고자 노력도 해보았습니다. 그렇다면 파펨 대표가 생각하는 CTO는 어떤 분일까요? 현재의 파펨 구성원들과 아래의 일들을 함께 해나가 주실 분입니다. 1. 자체 커머스로써의 서비스 업그레이드 : 전체 팀과 함께 논의할 일 2. 알고리즘의 upagrade 반영 : 알고리즘 설계자(대표)와 함께 할 일3. 파펨 DB에서 추출할 수 있는 data를 바탕으로 마케팅 insight 발굴 : marketer와 함께 할 일4. 새로운 tool(예, GA보다 amplitude를 한번 사용해보자 등)을 소개하고 도입 이렇게 쓰면 컴퓨터 공학을 전공한 사람에게 저렇게 많은 것을 요청하는 당신은 경영학과 출신이니.. 재무, 회계, HR, 생산관리 모두 잘할 수 있는 사람인가요?라는 질문을 받을 것 같은 느낌이 들지만... ㅜㅜ 아무튼 어려운 리쿠르팅의 길을 떠나기 전에 머릿속에 생각나는 것들을 한번 써보았습니다.파펨에서 engineer를 찾습니다!! 파펨은? a. Ruby on Rails / AWS에서 서비스되고 있고, 나름 github에 히스토리 정리가 잘 되어 있고, 이전에 프리랜서로 개발에 도움을 주신 분이 체계적으로 정리해주셔서 나중에 열어보시면 뜨악하실 정도는 아닙니다. (라고 합니다. ^^;) b. 구체적인 연봉, job title 등은 상황별로 합리적인 논의를 할 준비가 되어 있습니다. C. 퓨쳐플레이와 아모레퍼시픽에서 투자를 유치하였습니다. #파펨 #스타트업 #창업가 #창업자 #마인드셋 #인사이트 #채용 #CTO #팀빌딩 #팀원
조회수 2064

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

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

AWS Lambda + API Gateway로 API 만들어보자

Overview좋은 아침입니다. 오늘은 AWS Lambda와 API Gateway 이용하여 API를 만들어보겠습니다. 서버 구축부터 해야 하지만 이번 글에서 서버는 따로 필요 없습니다. 당황하셨나요? 괜찮습니다. 이 글을 보면 곧 이해가 될 겁니다. 우선 Lambda와 API Gateway부터 알아봅시다. Lambda는 서버를 프로비저닝하거나 관리하지 않고도 코드를 실행할 수 있게 해주는 컴퓨팅 서비스입니다. 브랜디 랩스에는 이미 이것을 활용한 예제가 많은데요. 아마 아래의 포스팅들을 보시면 도움이 될 겁니다.SQS + Lambda: 이상근 팀장님CodeStar + Lambda + SAM으로 테스트 환경 구축하기: 천보성 팀장님API 호출부터 결과 확인까지API Gateway는 규모에 상관없이 API 생성, 유지 관리, 모니터링과 보호를 할 수 있게 해주는 서비스입니다. 이 글에서는 API를 호출해 결과를 확인하는 걸 목표로 진행하겠습니다.최종 API 호출 URL* GET /v1/reviews/{review-no}/comments* POST /v1/reviews/{review-no}/comments AWS(Amazon Web Service) 가입 절차는 생략하겠습니다. 1.AWS 로그인 후 API Gateway 시작!AWS에서도 설명되어 있듯이 API gateway엔 이와 같은 장점이 있습니다.1. API 개발 간소화: 새로운 버전을 신속하게 반복하고, 테스트하고, 출시할 수 있습니다.2. 규모에 따른 성능: 백엔드 시스템에 대한 트래픽 관리하여 유동적으로 API 호출하여 성능을 높이는데 도움이 됩니다.3. SDK 생성: 사용자 지정 SDK를 만들어 애플리케이션에서 신속하게 API를 테스트하고 배포할 수 있습니다. 2.API 생성새 API로 엔드 포인트 유형을 지역으로 선택하여 생성하세요. 엔드 포인트 유형1. 지역 : 현재 리전에 배포2. 최적화된 에지 : CloudFront 네트워크에 배포3. 프라이빗 : VPC에서만 엑세스 가능3.최종 호출 url로 순차적으로 리소스 생성리소스 이름과 리소스 경로를 입력하고 리소스를 생성합니다.리소스는 호출할 수 있는 특정 URL입니다. 생성된 리소스로 /reviews 주소가 만들어졌습니다.다음은 /reviews 주소 뒤에 {review-no}를 생성합니다. 리소스 경로에 {} 가 포함되어 있으면 경로 파라미터를 나타냅니다.마지막 리소스를 생성하게 되면 위의 이미지와 같이 /reviews/{review-no}/comments 리소스가 생성되었습니다. 이제 메서드에 연결할 Lambda 함수를 먼저 생성하겠습니다.4.Lambda 함수 생성GET, POST 메서드에 연결할 각각의 Lambda 함수를 생성합니다.‘Hello from Lambda’ 문자열로 리턴되는 Lambda 함수가 생성되었습니다. 생성된 Lambda 함수를 API Gateway 메서드에 연결해보겠습니다.5.메서드 생성GET, POST 메서드를 생성합니다.메서드의 의미* POST : 새로 생성(Create)* GET : 조회(Read)* PUT : 수정(Update)* DELETE : 삭제(delete)* PATCH : 일부만 수정(Update) 새 메서드의 통합 유형을 Lambda 함수로 선택하고 기존에 생성한 함수명으로 입력한 다음 저장을 누릅니다.메서드 실행 화면입니다. 해당 메서드에 통합 요청할 Lambda 함수가 연결됩니다. 연결된 Lambda 함수를 눌러보겠습니다.왼쪽 목록 트리거 추가하는 부분에 API Gateway가 연결되었습니다. 그럼 이제 정상적으로 호출되는지 테스트해보겠습니다.테스트를 클릭하면 오른쪽에 요청에 대한 결과가 나옵니다. 조금 전에 연결했던 Lambda 함수에 ‘Hello from Lambda’ 값으로 출력됩니다. 이제 리소스로 추가한 경로 파라미터를 매핑하여 출력해보겠습니다.메서드 요청에서는 사용자에게 노출되는 API를 정의할 수 있습니다. 리소스로 경로 파라미터를 추가하게 되면 메서드 요청 -> 경로 요청 부분에 자동으로 추가되어 있습니다.통합 요청에서는 백엔드와의 통신 방식을 지정할 수 있습니다. 메서드 요청에서 보낸 URL 경로 부분을 매핑시켜야 합니다. 명명 규칙은 아래와 같습니다. method.request.{"path" | "querystring" | "header"}.{param_name}매핑 템플릿을 추가하여 수신된 요청을 변환하여 통합 백엔드로 보내야 합니다. 정의된 템플릿이 없기 때문에 매핑 템플릿을 추가한 후 메서드 요청 패스스루로 지정합니다. 그러면 클라이언트가 제공한 요청이 변환없이 통합 백엔드로 전달됩니다.클라이언트가 요청한 경로 파라미터 출력하도록 Lambda 함수를 수정합니다.이제 다시 테스트를 해보겠습니다. 경로에 값을 요청하여 응답 본문에 출력되는 걸 확인할 수 있습니다.6.API 배포스테이지 정보를 입력하고 배포를 클릭합니다.스테이지 상세 정보에 API 호출 주소가 생성됩니다. Postman으로 생성된 API주소를 입력하여 정상적으로 return 값을 확인합니다.Conclusion정말 긴 과정이었습니다. 지금까지 API Gateway를 이용하여 API 생성부터 배포까지 알아봤습니다. API Gateway를 사용하면 서버 없이 높은 확장성을 가진 백엔드 애플리케이션을 구축하고 운영할 수 있게 될 겁니다. 백엔드에 관심이 있는 개발자에게 이 글이 도움이 되길 바랍니다.글곽정섭 과장 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발자 #개발팀 #인사이트 #경험공유
조회수 1908

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

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

기업문화 엿볼 때, 더팀스

로그인

/