스토리 홈

인터뷰

피드

뉴스

조회수 3346

빅데이터 '분석가' '전문가'가 부족한 이유...

업계에서는 대기업이나 공공기관 등의 데이터 분석 수요가 커지면서 빅데이터를 다루거나 데이터 분석가들을 찾는 기업이 늘어난다고 하는 기사나 이야기들이 떠돌아다닌다.한국정보화진흥원에서 발간한 '2015년 빅데이터 시장 현황조사'보고서에 의하면 빅데이터 공급기업과 수요기업 모두 빅데이터 분석가가 필요하다고 내다보고, 많은 데이터 분석가가 필요하다고 이야기했다.분야도 금융을 비롯하여 통신, 커머스 등을 아우르고, IT 관련부서뿐만 아니라, 현업이라고 불리는 마케팅이나 영업도 포함된 관계에서의 데이터 활용을 위해서 빅데이터 '분석가'가 필요하다고 이야기를 한다.죄송하지만.. 한국형 환경에서는 '빅데이터 분석가'나 '전문가'는 그다지 필요 없을 것 같다.1. 변화하지 않는 기업어차피 정해져 있는 프로세서, 내부 R&R과 내부 혁신을 하기 위한 인사이트를 찾고, 데이터 변수를 찾는다고 하더라도 굳이 기업 내부의 변화를 일으키지 않을 것이기 때문에 '진정한 데이터 분석가'는 해당 기업에 무의미할 것이다.정말, 전문가라면 '내부 혁신'에 대한 키워드들을 뽑아줄 텐데... 이런 이야기는 '컨설팅'업체에서도 하지 않고, 내부에서도 '금기'시 해야 할 단어들이 대부분이다.만일, 대기업인 중요 키워드가 '오너'의 키가 문제라고 지적한다면... 아마도, 해당 부서나 관련자들은 움직이지도 못할 것이다.죄송하지만, '내부 혁신'이 불가능하고, '오너'중심의 대기업은 데이터 분석가가 필요하지 않다. 다만, '오너'의 생각을 읽고서 적당하게 마사지된 '데이터'를 보여줄 '외부 데이터 분석'서비스 업체만 필요할 뿐이다.그래서, 국내에서는 데이터 분석 서비스 업체 정도가 적당하다.2. 기업과 조직에 데이터가 없다.프로세스 하단에서 동작하는 수많은 로그들을 추적 감시, 감사하는 시스템이 가동되고 있어야 하며, 고객 서비스를 하는 서비스 집단에서도 하단에서 아이디어가 상단으로 올라가는 환경들이 이미 가동되고 있어야 한다. 데이터의 대부분은 그런 인사이트를 증명하는 근거가 되기 때문이다.이미, 중요한 움직임을 보이고 있을 때에만 '의미 있는 정보'를 추출할 데이터들이 축적되는데... 사실상, 의미 없이 마사지된 '보고서'들만 존재한다.원천적으로 의미 있는 데이터를 추출할 데이터가 있어야 하는데.. 대부분이 왜곡된 정보들이거나, 특정 힘에 의해서 데이터들이 왜곡돼 있다면, 해당 기업과 조직은 데이터가 없다고 봐야 한다.3. 오랜 경험을 축적한 실전 전문가들이 일찍 퇴직한다.빅데이터를 통해서 단지 현황만을 보여주는 것이 아니라, 기업의 미래나 새로운 먹거리를 유도할 수 있는 인사이트를 추출하기 위해서는 해당 도메인이나 해당 마켓에 익숙하고 경험이 풍부한 전문가들이 같이 있어야 한다. 실제, 데이터가 의미하는 방향성이나 수치, 지수가 어떤 것을 의미하는지 읽어 줄 수 있는 것은 데이터 전문가들이 하는 일이 아니다.해당 업무와 해당 도메인의 전문가가 그 '수치'를 읽어 줄 수 있는 것이다.대부분의 기업에서 '실전'이거나 '실제 업무'에 익숙한 전문가나 경험이 축적된 사람들은 하청업체이거나 이미 퇴직한 경험이 풍부한 사람들이다.해당 기업에서는 아무리 데이터가 분석되어도 어떤 의미인지 판독해줄 사람이 없다.4. IT기술 전문가가 필요한 것이 아니다.빅데이터나 머신러닝과 같은 지식화 인사이트는 절대 IT기술이나 주변의 소프트웨어 설루션으로 만들어지는 것이 아니다. 기업 내부에 축적된 '지식'을 기반으로 '사람'을 기준으로 데이터가 만들어진다. 데이터 분석 전문가는 단지, 그것의 가치를 '판정'해줄 수 있는 기준을 마련해줄 뿐이다.대부분의 '한국형'조직들은 데이터 거버넌스 조직도 없으며, 제대로 된 인사시스템이 가동되지 않고 있다. 슬프지만, 빅데이터 전문가들은 내부에서 영입하는 것이 아니라, 내부에서 자생적으로 생성되는 것이다.자생적으로 빅데이터 전문가가 생성되지 않는 조직은 이미, 지식화가 불가능한 형태이기 때문에, 너무 무리하지 말고, 현재 환경에서 연착륙하는 것을 고려하는 것이 최선일 것이다.역시, '한국형'에서는 굳이 '빅데이터 분석가'가 필요한 것이 아니라, '빅데이터 분석가 코스프레'를 하는 사람이 필요한 것 아닌가?오너가 이야기하는 'A'를 'A'처럼 써줄 수 있는 코스프레가 가능한 사람이면 충분한 것 아닌가 한다.
조회수 1138

개발자의 경력관리란?

경력이 아닌 업력이 되는 단계에 이르러야 가능한 것 아닌가 합니다.대부분의 경력은 '어느 회사의 누구'라는 표현에서 만들어진 것이 아닙니다.진정한 경력의 결과는 '자신의 이름'이 곧 브랜드화 되는 것입니다.매우 당연하게,하루 이틀, 한 두해 한다고 해서 얻어지는 것이 아닙니다."10년 경력!"10년 이상 한 분야나 하나의 도메인, 하나의 테크, 하나의 경력, 하나의 경험을 꾸준하게 파고들었을 때에 얻어지고, 그러는 경험속에서 인사이트, 통찰력이 생기게 됩니다.물론. 그래서, 20대에도 명성을 얻을 수 있는 '경력관리'가 가능하다고 이야기합니다.(실제 얻은 사람을 많이 봤습니다. 그들은 10대에 시작했죠. )회사의 테두리 내에서 얻을 수 있는 '경력'은 '경험'일뿐입니다.자신의 이름을 중심으로 기술할 수 있을 때에 '경력'이라고 이야기할 수 있습니다.개발자라면...글을 써서도 얻을 수 있고,강연을 해서도 얻을 수 있고,GitHub에 오픈소스를 공개하면서도 얻을 수 있습니다.현재 30대와 그 이전의 개발자라면...10대와 20대도 똑같습니다.40대, 50대 이후를 준비하세요.반복적인 일, 똑같은 일, 회사의 프로세스의 하나인 일만 하는 '사람'이라면...그냥, 그 회사의 톱니바퀴가 되는 것입니다.대부분 '경력관리'가 잘 안됩니다.앞으로 50대 이후에도 '브랜드'를 얻을 사람이 되려면...자신의 '경력'관리를 잘 해야 얻을 수 있습니다.나중에 닭 튀기거나 치킨 배달할 것이 아니라면...관리를 잘해야 합니다.경력관리가 가능하려면 어떤 회사를 찾아야 할까요.다음을 기억하세요.1. 구루급 개발자가 있는 회사를 찾으세요.2. 자신이 주도적으로 무언가를 만들 수 있는 권한과 책임을 줄 수 있는 회사를 찾으세요.3. 커뮤니티나 외부 강연, 외부 오픈소스 개발 행사에 적극 참여할 수 있는 기회를 주는 회사를 찾으세요.4. 반복적인 업무와 정체된 마켓에서만 반복적으로 서비스를 하는 회사는 회피하세요.5. 우리 도메인은 원래 이래, 이 일은 원래 이래... 이런 식으로 이야기하는 '상급자'가 있는 회사를 피하세요.6. 쉽게 설명할 수 있도록 준비하고, 리뷰를 할 수 있는 기회와 시간이 주어지는 회사를 찾으세요.그리고, 마지막으로...비전은 누가 주거나 만들어 주지 않습니다.결국, 자기 자신이 찾아야 하는데...이것도, 주변에 이야기가 통하는 '구루급 개발자'가 있어야 그나마 방향성을 찾기 좋습니다.혼자 고민하거나,주변에 비슷한 사람들끼리 고민해봐야 답이 안 나옵니다.꼭, 기억하세요!'구루급 개발자'와 상의하세요.그분들은 실패와 성공, 포기와 단념, 선택과 집중에 대해서 알고 있답니다.퇴근시간이라면..구루급 개발자에게 치맥 한잔 하자고 하세요!
조회수 3260

Attention is all you need paper 뽀개기

이번 포스팅에서는 포자랩스에서 핵심적으로 쓰고 있는 모델인 transformer의 논문을 요약하면서 추가적인 기법들도 설명드리겠습니다.Why?Long-term dependency problemsequence data를 처리하기 위해 이전까지 많이 쓰이던 model은 recurrent model이었습니다. recurrent model은 t번째에 대한 output을 만들기 위해, t번째 input과 t-1번째 hidden state를 이용했습니다. 이렇게 한다면 자연스럽게 문장의 순차적인 특성이 유지됩니다. 문장을 쓸 때 뒤의 단어부터 쓰지 않고 처음부터 차례차례 쓰는 것과 마찬가지인것입니다.하지만 recurrent model의 경우 많은 개선점이 있었음에도 long-term dependency에 취약하다는 단점이 있었습니다. 예를 들어, “저는 언어학을 좋아하고, 인공지능중에서도 딥러닝을 배우고 있고 자연어 처리에 관심이 많습니다.”라는 문장을 만드는 게 model의 task라고 해봅시다. 이때 ‘자연어’라는 단어를 만드는데 ‘언어학’이라는 단어는 중요한 단서입니다.그러나, 두 단어 사이의 거리가 가깝지 않으므로 model은 앞의 ‘언어학’이라는 단어를 이용해 자연어’라는 단어를 만들지 못하고, 언어학 보다 가까운 단어인 ‘딥러닝’을 보고 ‘이미지’를 만들 수도 있는 거죠. 이처럼, 어떤 정보와 다른 정보 사이의 거리가 멀 때 해당 정보를 이용하지 못하는 것이 long-term dependency problem입니다.recurrent model은 순차적인 특성이 유지되는 뛰어난 장점이 있었음에도, long-term dependency problem이라는 단점을 가지고 있었습니다.이와 달리 transformer는 recurrence를 사용하지 않고 대신 attention mechanism만을 사용해 input과 output의 dependency를 포착해냈습니다.Parallelizationrecurrent model은 학습 시, t번째 hidden state를 얻기 위해서 t-1번째 hidden state가 필요했습니다. 즉, 순서대로 계산될 필요가 있었습니다. 그래서 병렬 처리를 할 수 없었고 계산 속도가 느렸습니다.하지만 transformer에서는 학습 시 encoder에서는 각각의 position에 대해, 즉 각각의 단어에 대해 attention을 해주기만 하고, decoder에서는 masking 기법을 이용해 병렬 처리가 가능하게 됩니다. (masking이 어떤 것인지는 이후에 설명해 드리겠습니다)Model ArchitectureEncoder and Decoder structureencoder는 input sequence (x1,...,xn)<math>(x1,...,xn)</math>에 대해 다른 representation인 z=(z1,...,zn)<math>z=(z1,...,zn)</math>으로 바꿔줍니다.decoder는 z를 받아, output sequence (y1,...,yn)<math>(y1,...,yn)</math>를 하나씩 만들어냅니다.각각의 step에서 다음 symbol을 만들 때 이전에 만들어진 output(symbol)을 이용합니다. 예를 들어, “저는 사람입니다.”라는 문장에서 ‘사람입니다’를 만들 때, ‘저는’이라는 symbol을 이용하는 거죠. 이런 특성을 auto-regressive 하다고 합니다.Encoder and Decoder stacksEncoderN개의 동일한 layer로 구성돼 있습니다. input $x$가 첫 번째 layer에 들어가게 되고, layer(x)<math>layer(x)</math>가 다시 layer에 들어가는 식입니다.그리고 각각의 layer는 두 개의 sub-layer, multi-head self-attention mechanism과 position-wise fully connected feed-forward network를 가지고 있습니다.이때 두 개의 sub-layer에 residual connection을 이용합니다. residual connection은 input을 output으로 그대로 전달하는 것을 말합니다. 이때 sub-layer의 output dimension을 embedding dimension과 맞춰줍니다. x+Sublayer(x)<math>x+Sublayer(x)</math>를 하기 위해서, 즉 residual connection을 하기 위해서는 두 값의 차원을 맞춰줄 필요가 있습니다. 그 후에 layer normalization을 적용합니다.Decoder역시 N개의 동일한 layer로 이루어져 있습니다.encoder와 달리 encoder의 결과에 multi-head attention을 수행할 sub-layer를 추가합니다.마찬가지로 sub-layer에 residual connection을 사용한 뒤, layer normalization을 해줍니다.decoder에서는 encoder와 달리 순차적으로 결과를 만들어내야 하기 때문에, self-attention을 변형합니다. 바로 masking을 해주는 것이죠. masking을 통해, position i<math>i</math> 보다 이후에 있는 position에 attention을 주지 못하게 합니다. 즉, position i<math>i</math>에 대한 예측은 미리 알고 있는 output들에만 의존을 하는 것입니다.위의 예시를 보면, a를 예측할 때는 a이후에 있는 b,c에는 attention이 주어지지 않는 것입니다. 그리고 b를 예측할 때는 b이전에 있는 a만 attention이 주어질 수 있고 이후에 있는 c는 attention이 주어지지 않는 것이죠.Embeddings and Softmaxembedding 값을 고정시키지 않고, 학습을 하면서 embedding값이 변경되는 learned embedding을 사용했습니다. 이때 input과 output은 같은 embedding layer를 사용합니다.또한 decoder output을 다음 token의 확률로 바꾸기 위해 learned linear transformation과 softmax function을 사용했습니다. learned linear transformation을 사용했다는 것은 decoder output에 weight matrix W<math>W</math>를 곱해주는데, 이때 W<math>W</math>가 학습된다는 것입니다.Attentionattention은 단어의 의미처럼 특정 정보에 좀 더 주의를 기울이는 것입니다.예를 들어 model이 수행해야 하는 task가 번역이라고 해봅시다. source는 영어이고 target은 한국어입니다. “Hi, my name is poza.”라는 문장과 대응되는 “안녕, 내 이름은 포자야.”라는 문장이 있습니다. model이 이름은이라는 token을 decode할 때, source에서 가장 중요한 것은 name입니다.그렇다면, source의 모든 token이 비슷한 중요도를 갖기 보다는 name이 더 큰 중요도를 가지면 되겠죠. 이때, 더 큰 중요도를 갖게 만드는 방법이 바로 attention입니다.Scaled Dot-Product Attention해당 논문의 attention을 Scaled Dot-Product Attention이라고 부릅니다. 수식을 살펴보면 이렇게 부르는 이유를 알 수 있습니다.Attention(Q,K,V)=softmax(QKT√dk)V<math>Attention(Q,K,V)=softmax(QKTdk)V</math>먼저 input은 dk<math>dk</math> dimension의 query와 key들, dv<math>dv</math> dimension의 value들로 이루어져 있습니다.이때 모든 query와 key에 대한 dot-product를 계산하고 각각을 √dk<math>dk</math>로 나누어줍니다. dot-product를 하고 √dk<math>dk</math>로 scaling을 해주기 때문에 Scaled Dot-Product Attention인 것입니다. 그리고 여기에 softmax를 적용해 value들에 대한 weights를 얻어냅니다.key와 value는 attention이 이루어지는 위치에 상관없이 같은 값을 갖게 됩니다. 이때 query와 key에 대한 dot-product를 계산하면 각각의 query와 key 사이의 유사도를 구할 수 있게 됩니다. 흔히 들어본 cosine similarity는 dot-product에서 vector의 magnitude로 나눈 것입니다. √dk<math>dk</math>로 scaling을 해주는 이유는 dot-products의 값이 커질수록 softmax 함수에서 기울기의 변화가 거의 없는 부분으로 가기 때문입니다.softmax를 거친 값을 value에 곱해준다면, query와 유사한 value일수록, 즉 중요한 value일수록 더 높은 값을 가지게 됩니다. 중요한 정보에 더 관심을 둔다는 attention의 원리에 알맞은 것입니다.Multi-Head Attention위의 그림을 수식으로 나타내면 다음과 같습니다.MultiHead(Q,K,V)=Concat(head1,...,headh)WO<math>MultiHead(Q,K,V)=Concat(head1,...,headh)WO</math>where headi=Attention(QWQi,KWKi,VWVi)dmodel<math>dmodel</math> dimension의 key, value, query들로 하나의 attention을 수행하는 대신 key, value, query들에 각각 다른 학습된 linear projection을 h번 수행하는 게 더 좋다고 합니다. 즉, 동일한 Q,K,V<math>Q,K,V</math>에 각각 다른 weight matrix W<math>W</math>를 곱해주는 것이죠. 이때 parameter matrix는 WQi∈Rdmodelxdk,WKi∈Rdmodelxdk,WVi∈Rdmodelxdv,WOi∈Rhdvxdmodel<math>WiQ∈Rdmodelxdk,WiK∈Rdmodelxdk,WiV∈Rdmodelxdv,WiO∈Rhdvxdmodel</math>입니다.순서대로 query, key, value, output에 대한 parameter matrix입니다. projection이라고 하는 이유는 각각의 값들이 parameter matrix와 곱해졌을 때 dk,dv,dmodel<math>dk,dv,dmodel</math>차원으로 project되기 때문입니다. 논문에서는 dk=dv=dmodel/h<math>dk=dv=dmodel/h</math>를 사용했는데 꼭 dk<math>dk</math>와 dv<math>dv</math>가 같을 필요는 없습니다.이렇게 project된 key, value, query들은 병렬적으로 attention function을 거쳐 dv<math>dv</math>dimension output 값으로 나오게 됩니다.그 다음 여러 개의 head<math>head</math>를 concatenate하고 다시 projection을 수행합니다. 그래서 최종적인 dmodel<math>dmodel</math> dimension output 값이 나오게 되는거죠.각각의 과정에서 dimension을 표현하면 아래와 같습니다.*dQ,dK,dV<math>dQ,dK,dV</math>는 각각 query, key, value 개수Self-Attentionencoder self-attention layerkey, value, query들은 모두 encoder의 이전 layer의 output에서 옵니다. 따라서 이전 layer의 모든 position에 attention을 줄 수 있습니다. 만약 첫번째 layer라면 positional encoding이 더해진 input embedding이 됩니다.decoder self-attention layerencoder와 비슷하게 decoder에서도 self-attention을 줄 수 있습니다. 하지만 i<math>i</math>번째 output을 다시 i+1<math>i+1</math>번째 input으로 사용하는 auto-regressive한 특성을 유지하기 위해 , masking out된 scaled dot-product attention을 적용했습니다.masking out이 됐다는 것은 i<math>i</math>번째 position에 대한 attention을 얻을 때, i<math>i</math>번째 이후에 있는 모든 position은 Attention(Q,K,V)=softmax(QKT√dk)V<math>Attention(Q,K,V)=softmax(QKTdk)V</math>에서 softmax의 input 값을 −∞<math>−∞</math>로 설정한 것입니다. 이렇게 한다면, i<math>i</math>번째 이후에 있는 position에 attention을 주는 경우가 없겠죠.Encoder-Decoder Attention Layerquery들은 이전 decoder layer에서 오고 key와 value들은 encoder의 output에서 오게 됩니다. 그래서 decoder의 모든 position에서 input sequence 즉, encoder output의 모든 position에 attention을 줄 수 있게 됩니다.query가 decoder layer의 output인 이유는 query라는 것이 조건에 해당하기 때문입니다. 좀 더 풀어서 설명하면, ‘지금 decoder에서 이런 값이 나왔는데 무엇이 output이 돼야 할까?’가 query인 것이죠.이때 query는 이미 이전 layer에서 masking out됐으므로, i번째 position까지만 attention을 얻게 됩니다.이 같은 과정은 sequence-to-sequence의 전형적인 encoder-decoder mechanisms를 따라한 것입니다.*모든 position에서 attention을 줄 수 있다는 게 이해가 안되면 링크를 참고하시기 바랍니다.Position-wise Feed-Forward Networksencoder와 decoder의 각각의 layer는 아래와 같은 fully connected feed-forward network를 포함하고 있습니다.position 마다, 즉 개별 단어마다 적용되기 때문에 position-wise입니다. network는 두 번의 linear transformation과 activation function ReLU로 이루어져 있습니다.FFN(x)=max(0,xW1+b1)W2+b2x<math>x</math>에 linear transformation을 적용한 뒤, ReLU(max(0,z))<math>ReLU(max(0,z))</math>를 거쳐 다시 한번 linear transformation을 적용합니다.이때 각각의 position마다 같은 parameter W,b<math>W,b</math>를 사용하지만, layer가 달라지면 다른 parameter를 사용합니다.kernel size가 1이고 channel이 layer인 convolution을 두 번 수행한 것으로도 위 과정을 이해할 수 있습니다.Positional Encodingtransfomer는 recurrence도 아니고 convolution도 아니기 때문에, 단어의sequence를 이용하기 위해서는 단어의 position에 대한 정보를 추가해줄 필요가 있었습니다.그래서 encoder와 decoder의 input embedding에 positional encoding을 더해줬습니다.positional encoding은 dmodel<math>dmodel</math>(embedding 차원)과 같은 차원을 갖기 때문에 positional encoding vector와 embedding vector는 더해질 수 있습니다.논문에서는 다른 *frequency를 가지는 sine과 cosine 함수를 이용했습니다.*주어진 구간내에서 완료되는 cycle의 개수PE(pos,2i)=sin(pos/100002i/dmodel)<math>PE(pos,2i)=sin(pos/100002i/dmodel)</math>PE(pos,2i+1)=cos(pos/100002i/dmodel)<math>PE(pos,2i+1)=cos(pos/100002i/dmodel)</math>pos<math>pos</math>는 position ,i<math>i</math>는 dimension 이고 주기가 100002i/dmodel⋅2π<math>100002i/dmodel⋅2π</math>인 삼각 함수입니다. 즉, pos<math>pos</math>는 sequence에서 단어의 위치이고 해당 단어는 i<math>i</math>에 0부터 dmodel2<math>dmodel2</math>까지를 대입해 dmodel<math>dmodel</math>차원의 positional encoding vector를 얻게 됩니다. k=2i+1<math>k=2i+1</math>일 때는 cosine 함수를, k=2i<math>k=2i</math>일 때는 sine 함수를 이용합니다. 이렇게 positional encoding vector를 pos<math>pos</math>마다 구한다면 비록 같은 column이라고 할지라도 pos<math>pos</math>가 다르다면 다른 값을 가지게 됩니다. 즉, pos<math>pos</math>마다 다른 pos<math>pos</math>와 구분되는 positional encoding 값을 얻게 되는 것입니다.PEpos=[cos(pos/1),sin(pos/100002/dmodel),cos(pos/10000)2/dmodel,...,sin(pos/10000)]<math>PEpos=[cos(pos/1),sin(pos/100002/dmodel),cos(pos/10000)2/dmodel,...,sin(pos/10000)]</math>이때 PEpos+k<math>PEpos+k</math>는 PEpos<math>PEpos</math>의 linear function으로 나타낼 수 있습니다. 표기를 간단히 하기 위해 c=100002idmodel<math>c=100002idmodel</math>라고 해봅시다. sin(a+b)=sin(a)cos(b)+cos(a)sin(b)<math>sin(a+b)=sin(a)cos(b)+cos(a)sin(b)</math>이고 cos(a+b)=cos(a)cos(b)−sin(a)sin(b)<math>cos(a+b)=cos(a)cos(b)−sin(a)sin(b)</math> 이므로 다음이 성립합니다.PE(pos,2i)=sin(posc)<math>PE(pos,2i)=sin(posc)</math>PE(pos,2i+1)=cos(posc)<math>PE(pos,2i+1)=cos(posc)</math>PE(pos+k,2i)=sin(pos+kc)=sin(posc)cos(kc)+cos(posc)sin(kc)=PE(pos,2i)cos(kc)+cos(posc)sin(kc)<math>PE(pos+k,2i)=sin(pos+kc)=sin(posc)cos(kc)+cos(posc)sin(kc)=PE(pos,2i)cos(kc)+cos(posc)sin(kc)</math>PE(pos+k,2i+1)=cos(pos+kc)=cos(posc)cos(kc)−sin(posc)sin(kc)=PE(pos,2i+1)cos(kc)−sin(posc)sin(kc)<math>PE(pos+k,2i+1)=cos(pos+kc)=cos(posc)cos(kc)−sin(posc)sin(kc)=PE(pos,2i+1)cos(kc)−sin(posc)sin(kc)</math>이런 성질 때문에 model이 relative position에 의해 attention하는 것을 더 쉽게 배울 수 있습니다.논문에서는 학습된 positional embedding 대신 sinusoidal version을 선택했습니다. 만약 학습된 positional embedding을 사용할 경우 training보다 더 긴 sequence가 inference시에 입력으로 들어온다면 문제가 되지만 sinusoidal의 경우 constant하기 때문에 문제가 되지 않습니다. 그냥 좀 더 많은 값을 계산하기만 하면 되는거죠.Trainingtraining에 사용된 기법들을 알아보겠습니다.Optimizer많이 쓰이는 Adam optimizer를 사용했습니다.특이한 점은 learning rate를 training동안 고정시키지 않고 다음 식에 따라 변화시켰다는 것입니다.lrate=d−0.5model⋅min(step_num−0.5,step_num⋅warmup_steps−1.5)warmup_step<math>warmup_step</math>까지는 linear하게 learning rate를 증가시키다가, warmup_step<math>warmup_step</math> 이후에는 step_num<math>step_num</math>의 inverse square root에 비례하도록 감소시킵니다.이렇게 하는 이유는 처음에는 학습이 잘 되지 않은 상태이므로 learning rate를 빠르게 증가시켜 변화를 크게 주다가, 학습이 꽤 됐을 시점에 learning rate를 천천히 감소시켜 변화를 작게 주기 위해서입니다.RegularizationResidual ConnectionIdentity Mappings in Deep Residual Networks라는 논문에서 제시된 방법이고, 아래의 수식이 residual connection을 나타낸 것입니다.yl=h(xl)+F(xl,Wl)<math>yl=h(xl)+F(xl,Wl)</math>xl+1=f(yl)<math>xl+1=f(yl)</math>이때 h(xl)=xl<math>h(xl)=xl</math>입니다. 논문 제목에서 나온 것처럼 identity mapping을 해주는 것이죠.특정한 위치에서의 xL<math>xL</math>을 다음과 같이 xl<math>xl</math>과 residual 함수의 합으로 표시할 수 있습니다.x2=x1+F(x1,W1)<math>x2=x1+F(x1,W1)</math>x3=x2+F(x2,W2)=x1+F(x1,W1)+F(x2,W2)<math>x3=x2+F(x2,W2)=x1+F(x1,W1)+F(x2,W2)</math>xL=xl+L−1∑i=1F(xi,Wi)<math>xL=xl+∑i=1L−1F(xi,Wi)</math>그리고 미분을 한다면 다음과 같이 됩니다.σϵσxl=σϵσxLσxLσxl=σϵσxL(1+σσxlL−1∑i=1F(xi,Wi))<math>σϵσxl=σϵσxLσxLσxl=σϵσxL(1+σσxl∑i=1L−1F(xi,Wi))</math>이때, σϵσxL<math>σϵσxL</math>은 상위 layer의 gradient 값이 변하지 않고 그대로 하위 layer에 전달되는 것을 보여줍니다. 즉, layer를 거칠수록 gradient가 사라지는 vanishing gradient 문제를 완화해주는 것입니다.또한 forward path나 backward path를 간단하게 표현할 수 있게 됩니다.Layer NormalizationLayer Normalization이라는 논문에서 제시된 방법입니다.μl=1HH∑i=1ali<math>μl=1H∑i=1Hail</math>σl= ⎷1HH∑i=1(ali−μl)2<math>σl=1H∑i=1H(ail−μl)2</math>같은 layer에 있는 모든 hidden unit은 동일한 μ<math>μ</math>와 σ<math>σ</math>를 공유합니다.그리고 현재 input xt<math>xt</math>, 이전의 hidden state ht−1<math>ht−1</math>, at=Whhht−1+Wxhxt<math>at=Whhht−1+Wxhxt</math>, parameter g,b<math>g,b</math>가 있을 때 다음과 같이 normalization을 해줍니다.ht=f[gσt⊙(at−μt)+b]<math>ht=f[gσt⊙(at−μt)+b]</math>이렇게 한다면, gradient가 exploding하거나 vanishing하는 문제를 완화시키고 gradient 값이 안정적인 값을 가짐로 더 빨리 학습을 시킬 수 있습니다.(논문에서 recurrent를 기준으로 설명했으므로 이에 따랐습니다.)DropoutDropout: a simple way to prevent neural networks from overfitting라는 논문에서 제시된 방법입니다.dropout이라는 용어는 neural network에서 unit들을 dropout하는 것을 가리킵니다. 즉, 해당 unit을 network에서 일시적으로 제거하는 것입니다. 그래서 다른 unit과의 모든 connection이 사라지게 됩니다. 어떤 unit을 dropout할지는 random하게 정합니다.dropout은 training data에 overfitting되는 문제를 어느정도 막아줍니다. dropout된 unit들은 training되지 않는 것이니 training data에 값이 조정되지 않기 때문입니다.Label SmoothingRethinking the inception architecture for computer vision라는 논문에서 제시된 방법입니다.training동안 실제 정답인 label의 logit은 다른 logit보다 훨씬 큰 값을 갖게 됩니다. 이렇게 해서 model이 주어진 input x<math>x</math>에 대한 label y<math>y</math>를 맞추는 것이죠.하지만 이렇게 된다면 문제가 발생합니다. overfitting될 수도 있고 가장 큰 logit을 가지는 것과 나머지 사이의 차이를 점점 크게 만들어버립니다. 결국 model이 다른 data에 적응하는 능력을 감소시킵니다.model이 덜 confident하게 만들기 위해, label distribution q(k∣x)=δk,y<math>q(k∣x)=δk,y</math>를 (k가 y일 경우 1, 나머지는 0) 다음과 같이 대체할 수 있습니다.q′(k|x)=(1−ϵ)δk,y+ϵu(k)<math>q′(k|x)=(1−ϵ)δk,y+ϵu(k)</math>각각 label에 대한 분포 u(k)<math>u(k)</math>, smooting parameter ϵ<math>ϵ</math>입니다. 위와 같다면, k=y인 경우에도 model은 p(y∣x)=1<math>p(y∣x)=1</math>이 아니라 p(y∣x)=(1−ϵ)<math>p(y∣x)=(1−ϵ)</math>이 되겠죠. 100%의 확신이 아닌 그보다 덜한 확신을 하게 되는 것입니다.Conclusiontransformer는 recurrence를 이용하지 않고도 빠르고 정확하게 sequential data를 처리할 수 있는 model로 제시되었습니다.여러가지 기법이 사용됐지만, 가장 핵심적인 것은 encoder와 decoder에서 attention을 통해 query와 가장 밀접한 연관성을 가지는 value를 강조할 수 있고 병렬화가 가능해진 것입니다.Referencehttp://www.whydsp.org/280http://mlexplained.com/2017/12/29/attention-is-all-you-need-explained/http://openresearch.ai/t/identity-mappings-in-deep-residual-networks/47https://m.blog.naver.com/PostView.nhn?blogId=laonple&logNo=220793640991&proxyReferer=https://www.google.co.kr/https://www.researchgate.net/figure/Sample-of-a-feed-forward-neural-network_fig1_234055177https://arxiv.org/abs/1603.05027https://arxiv.org/abs/1607.06450http://jmlr.org/papers/volume15/srivastava14a.old/srivastava14a.pdfhttps://arxiv.org/pdf/1512.00567.pdf
조회수 2923

DevOps 팀을 위한 모니터링 팁

다음 중 몇 개나 해당하시나요?1~5명 규모의 작은 개발팀에서 일한다.DevOps 조직이다.우여곡절 끝에 서비스는 런칭했지만, 개발과 동시에 운영을 해야하는 상황이다.서버 인프라 지식이 별로 없다.무중단 서비스 운영 경험이 별로 없다.팀 내에 시스템 엔지니어(SE)와 데이터베이스 전문가(DBA)가 없다.하나라도 해당한다면 이 글이 도움이 될 지도 모릅니다.누구나 쉽고 빠르게 앱을 만들고 서비스를 런칭할 수 있는 시대가 되었지만 문제는 런칭 이후입니다. 런칭 이후에는 고객이 100명이라도 안정적인(High Availability) 서비스를 운영해야 하는 것이 백엔드 개발자의 임무이기 때문입니다.안정적인 서비스를 운영하기 위해서는 체계적인 모니터링 필수라고 하는데 그마저도 쉽지 않습니다. 가장 큰 문제는 (장애가 터지기 전까지) 무엇을 모니터링 해야 하는지조차 모른다는 것이고, 당장 개발해야할 것들이 산더미처럼 쌓여있는데 사람도 부족한 것도 문제입니다.그렇지만 누군가는 해야하는 일입니다. 리디북스 역시 모니터링이 전혀 없던 시절이 있었으나, 크고 작은 실패와 좌절을 겪으며 조금씩 경험을 쌓아가고 있습니다. 이번 글에서는 우리가 모니터링과 관련하여 고민해 온 내용들을 소개해볼까 합니다.어떻게 모니터링할 것인가시스템의 안정성을 높이기 위해 투입해야 하는 노력은 지수적으로 증가합니다. 아래 표에서 보듯이 SLA 를 99.999% 에서 99.9999% 로 높이려고 한다면 1년에 약 5분의 가용시간을 얻을 뿐이지만 이를 위해 수백시간 이상의 노력을 들여야 합니다.가용성연간 장애 시간주간 장애 시간99.995%26.28 분30.24 초99.999%5.26 분6.05 초99.9999%0.525 분0.6048 초완벽함을 추구하면 할 수록 얻을 수 있는 고객 만족은 미미한 것에 비해 이를 위한 개발자의 노력은 기하급수적으로 증가합니다. 따라서, 먼저 대응의 적정선을 찾고 효율적으로 움직이기 위한 계획을 세워야 합니다.리디북스에서는 해야할 일을 4가지로 분류하여 중요한 일부터 처리하는 아이젠하워 매트릭스에서 그 대응 원칙을 차용하였는데, 그 이유는 시사하는 바가 동일하기 때문이었습니다. 즉, 중요한 것은 대부분 긴급하지 않고, 긴급한 것은 대체로 중요하지 않다는 점입니다. 그리고 매트릭스의 두 축은 아래와 같습니다.얼마나 급한가?사무실의 무선 인터넷이 안된다면 서비스에 큰 문제는 아니지만, 당장 해결해야 하는 급한 일입니다. 반대로 백업 스크립트가 며칠째 동작하지 않아서 최근 데이터의 스냅샷이 없다면, 이는 당장 해결할 필요는 없겠지만 매우 중요한 일입니다.그리고 장애란, 단순히 “고장”을 의미하는 것이 아니라 서비스 이용에 지장이 없더라도 어떤 수치나 결과가 예상과 다른 상황을 의미해야 합니다. 예를 들어, 웹서버의 평균 CPU 사용률이 70%가 넘는다거나 네트워크 대역폭을 90% 이상 사용하는 상황은 정상이 아닙니다. 조금만 트래픽이 몰려도 문제가 발생할 가능성이 매우 높기 때문에 잠재적인 장애로 간주해야 합니다.우리는 급한 문제를 우선적으로 처리하는 경향이 있어서, 덜 급하지만 더 중요한 일을 놓치는 경우가 많습니다. 이를 피하려면 장애의 그 심각도에 따라서도 구분해야 합니다.얼마나 심각한가?심각도를 처음부터 너무 상세하게 구분할 필요는 없으며, 크게 서비스 이용에 치명적인 것과 그렇지 않은 것으로 나누어 생각하면 됩니다. “치명적”의 의미는 서비스마다 다를 수 있지만 대개 아래에 해당합니다.사업에 지장을 초래한다.고객을 잃는다.만약 웹페이지의 로딩 속도가 매우 느려서 나쁜 이미지를 준다면 이 역시 치명적일 수 있습니다. 실제로 아마존에서는 로딩 속도가 100ms 지연될 때마다 눈에 띄는 매출 하락이 발생했다는 테스트 결과가 있습니다. 따라서 속도에 대한 매트릭을 모니터링 지표에 추가하는 것은 좋은 선택입니다.이상을 토대로 장애 종류에 따른 대응 원칙을 정리하면 아래와 같습니다. 급함안급함심각함➀ 즉각 대응, 즉각 인지➁ 평소 보완, 항상 경계안심각함➂ 빨리 대응, 최소 대응➃ 대응하지 않기이 중에서 항상 의식하고 놓치지 말아야 하는 것은 안급하지만 잠재적으로 심각한 장애(➁)입니다. 그리고 모니터링은 한 번 시작하게 되면 관리를 위한 비용이 꾸준히 투입되어야 하기 때문에 사소한 문제(➂, ➃)를 굳이 파헤치는 것은 오히려 독이 될 수도 있습니다.모니터링 측면에서 본다면 발생중인 장애는 최대한 빨리 발견하는 것이 중요하며, 잠재적인 장애는 상태의 변화를 최대한 빨리 감지하는 것이 중요합니다. 예를 들어, 디스크의 여유공간은 완전히 바닥나기 전까지 어떠한 경고도 나타나지 않지만 부족한 상황이 발생하면 어떤 부작용이 생길지 예측할 수 없습니다.필수 모니터링 갖추기모니터링을 해야할 대상은 기술 스택과 코드 구현에 따라 달라지겠지만, 빼놓을 수 없는 것들이 몇 가지 있습니다. 리디북스에서는 서버의 프로비저닝과 동시에 아래 내용들을 함께 준비하고 있습니다.1. 리소스 및 시스템 모니터링각종 시스템 리소스 및 하드웨어 상태는 필수 모니터링 대상입니다. 모니터링 툴을 설치해보면 측정해주는 항목들이 너무 많아서 당황스러운 경험을 하게 되는데요. 그 중에서도 우리가 주목하고 있는 항목들은 아래와 같습니다.CPU UsageLoad AverageDisk UsageDisk Utilization (iowait, IOPS)Swap Memory Usage (사용시)Temperature (인프라 직접 구축시)RAID Status (인프라 직접 구축시)S.M.A.R.T Errors (인프라 직접 구축시)이 중 몇가지는 New Relic 에서 무료로 지원하므로 당장 여력이 없다면 이를 이용하는 것도 좋은 방법입니다.클라우드 환경이 아닌 데이터센터에서 인프라를 직접 구축하여 운영하고 있다면 좀 더 많은 노력이 필요합니다. 하드웨어적인 장애를 직접 신경써야 하기 때문입니다. 실제로 팬(fan)이 고장나거나 케이블이 환풍구를 막아서 서버의 온도가 비정상적으로 높아지다가 기기가 오동작하는 어처구니없는 상황도 발생합니다.Disk서버 환경에서 SSD 사용이 점점 대세가 되어가고 있는데, 최근 구글이 공개한 정보에 따르면 SSD에서 배드블럭이 발생하는 일은 매우 흔하며, 시간이 오래될 수록 안정성이 떨어진다고 합니다.따라서 디스크와 관련된 RAID나 S.M.A.R.T 오류는 가능한 빨리 대응해야 합니다. 특히 RAID 장비를 구성할 때에는 같은 공정에서 출하된 같은 벤더의 제품을 일괄적으로 구매해서 사용하기 때문에, 동일한 하드웨어 결함을 지니고 있거나 평균 수명도 비슷하므로 결코 안이하게 대응해서는 안됩니다.리디북스에서는 전자책 원본을 보관하는 스토리지에서 4개의 사본(replica) 중 3개가 연달아 깨지는 끔찍한 사고를 경험한 이후로, 디스크 오류는 1순위로 대응하고 있습니다. 참고로 스토리지 서버를 구축한지 3년째가 되는 해였고, 모두 S사의 제품이었습니다.iowait 은 CPU가 유휴(idle) 상태로 I/O를 대기하는 시간을 나타낸 수치입니다. 이를 통해 현재 시스템이 I/O 병목을 겪고 있는지 판단할 수 있기 때문에 중요합니다. 이 수치가 너무 높다면 블록 디바이스나 네트워크가 너무 느린 상황이거나 포화 상태일 수 있으므로, 더 높은 IOPS 장비로 업그레이드하거나 부하를 분산해야 합니다.단, CPU 성능에 영향을 받는 수치이므로 고성능 CPU를 사용할수록 평균 iowait이 높게 측정됩니다. (따라서 성능을 평가하기 위한 지표로는 IOPS도 함께 분석해야 합니다.)Load AverageLoad Average(평균 부하)는 마치 서버의 종합 성적표 같아서, 이 역시 주목할 필요가 있습니다. Load Average에 변동이 생긴다면 평소와는 다른 처리량(throughput)을 내고 있다는 뜻입니다. 요청량이 증가하여 수치가 올라갔다면 서버 증설과 튜닝에 대비해야 하지만, 그렇지 않다면 어딘가 병목이 발생하여 처리 효율이 낮아졌다는 신호입니다.아직 Load Average를 모니터링하고 있지 않다면 주요 서버군부터 아래 규칙을 참고하여 초기 기준치를 설정하기를 권장합니다. 물론 어디까지나 초기 설정 값이며, 실제 상황에 적합하지 않을 수 있습니다.Warning Level : 0.7 * number of cores Critical Level : 1.0 * number of cores간혹 커널 자체에 문제가 있거나, 커널 모드에서 예외가 발생하는 경우에는 syslogd 데몬이 남기는 로그를 파악해야 합니다. Papertrail, Splunk, Loggly 등의 서비스는 크리티컬 수준 이상의 syslog 에 대해 알림을 설정할 수 있을 뿐 아니라, 텍스트 형태로 남겨지는 모든 로그에 대한 관리를 쉽게 도와줍니다. 비록 유료지만 커널 모니터링 용으로만 사용한다면 비용이 많이 들지 않습니다.2. 응용프로그램 모니터링앱이나 서버에서 발생하는 크래시와 예외를 수집하는 도구 역시 장애 예방에 필수입니다. 해당 기능을 실시간으로 제공하는 다양한 서비스들이 존재하는데 많이 쓰이는 것으로는 Sentry, Rollbar, Airbrake, NewRelic APM 등이 있습니다. 대부분 5분만에 설정이 가능한데다 어느것을 선택하더라도 핵심 기능에는 부족함이 없습니다.단, 현재까지 가성비로는 Sentry가 제일 뛰어납니다. Python의 Flask와 Jinja의 개발자로 유명한 Armin Ronacher가 팀에 합류했기에 발전가능성 측면에서도 많은 기대가 됩니다.Sentry의 실시간 에러 대시보드3. 데이터베이스 모니터링팀에 DBA가 있나요? 모든 서버 개발자들이 인덱스와 스토리지 엔진의 특징에 대해 잘 이해하고, DB를 능숙하게 다루나요? 그것도 아니라면 개발자들이 작성한 모든 스키마와 쿼리에 대한 검증 과정을 거치고 있나요? 만약 그렇지 않다면 슬로우쿼리 모니터링은 필수입니다.우리가 서비스 초기에 겪은 문제의 대부분은 인덱스를 잘 다루지 못하거나 새로 도입한 ORM에 대한 이해도가 낮아서 발생한 문제였습니다. 그 중에서도 특정 쿼리가 너무 많은 I/O를 유발하던 것이 주된 원인이었으며, 작고 가벼운 쿼리가 너무 많이 호출되어 문제가 된 경우는 거의 없었습니다.잘못 설계된 스키마나 쿼리는 평소에는 드러나지 않다가 사용자가 몰리기 시작하면 큰 부하를 발생시켜서 기어이 서비스를 마비시키곤 합니다. 문제가 커지기 전에 그 조짐을 감지할 수는 없을까, 고민 끝에 우리가 시도한 방법은 “2초 이상 수행되는 쿼리에 대해서 로그를 남기고, 초당 3개 이상 로그가 발생할 경우 알림”을 받도록 하는 것이었습니다.MySQL에서는 아래 설정으로 로그를 활성화시킬 수 있습니다.[mysqld] long_query_time=2 # 2초 이상 수행되는 쿼리에 대해서 slow_query_log=1 # 로그를 남겨주세요 쿼리 분석에는 Percona의 pt-query-digest 를 추천합니다. VividCortext 혹은 MONyog 등의 솔루션은 시각적으로 화려하고 실제로도 강력한 기능을 갖추고 있지만, 유료라는 큰 단점이 있습니다.모니터링을 통해 알림을 받게 되면 문제가 더 커지기 전에 해당 기능을 수정하거나 중단시킬 기회가 생깁니다. 특히 새롭게 추가한 기능을 배포할 때 서비스가 불안해 질 수 있는데, 퍼포먼스 문제를 미리 발견하고 롤백을 서두를 수 있다는 것도 장점입니다.물론 가장 이상적인 상황은 n초 이상 수행되는 쿼리를 모두 없애는 것입니다. 하지만 현실은 튜닝을 포기하고 테이블을 풀스캔하도록 두는게 나은 선택일 수 있으며, OLAP/ETL 인프라가 별도로 구축되어 있지 않은 상황에서는 어쩔 수 없이 슬로우쿼리가 발생하게 됩니다. 우리가 초당 로그 갯수로 판단을 하게된 것도 이러한 이유 때문이었습니다.자동으로 슬로우 쿼리를 받아보면 문제해결에 도움이 됩니다.4. 배치 작업(scheduled task) 모니터링매일 백업 스크립트를 돌리고는 있는데, 백업이 정상적으로 완료가 되었는지는 어떻게 판단하면 될까요? 에러는 위에서 설명한 도구들로 확인이 가능하겠지만 스크립트가 수행도중 멈춰버렸거나, 서버의 전원이 꺼졌다면? 게다가 크론 작업(crontab)이 수십개가 넘어가면 이를 수동으로 체크하는 것도 일이므로, 반드시 자동화해야 합니다.이러한 상황에서 활용할 수 있는 유용한 도구가 PushMon 입니다. PushMon은 정해진 시간에 ping을 보내지 않으면 이메일이나 SMS로 알림을 주는 서비스로, 원리는 매우 단순하나 없어서는 안될 기능을 “무료”로 제공합니다.모니터링에 대응하기모니터링을 효율적으로 하기 위한, 즉 서비스 안정성을 높이기 위한 핵심 원칙은 “필요한 인원이 필요한 알림만 받는것”입니다.알림이 너무 많이 와서 음소거(Mute)를 하고 싶은 생각이 든다면 모니터링 체계에 문제가 있다는 신호입니다. 불필요하게 많은 경고는 안전 불감증을 낳을 뿐더러 정작 중요한 경고를 놓칠 확률을 높이기 때문입니다. 치명적인 알림은 모든 채널로 즉각 수신하고, 경고성 알림은 메일로 수신하되 정기 리포트나 메일함 자동분류 기능을 이용하여 중요한 정보를 놓치지 않는 습관이 중요합니다.불필요하게 많은 인원이 알림을 받는 상황도 문제입니다. 알림 수신자를 늘리면 모니터링의 퀄리티가 높아질 것이라고 생각하지만 절대 그렇지 않습니다. 오히려 방관자 효과가 발생하여 아무도 알림에 대응하지 않는 상황이 발생하게 됩니다. 따라서 알림이 발생했을 때에는 1차, 2차 담당자를 사전에 지정하고 운영할 필요가 있습니다.방관자 효과의 적절한 예팀에서 Slack을 사용한다면 기능 연동을 통해 실시간으로 이슈를 파악할 수 있고, 담당자 지정을 보다 쉽고 명확하게 할 수 있습니다. 특히, 별것 아닌 이모티콘(emoji) 만으로도 방관자 효과를 크게 줄일 수 있는데, 예를 들면 아래와 같습니다.👀 - 확인중 ✅ - 확인 완료 😱 - 확인은 하였으나 나는 해결을 못하겠음Sentry를 Slack에 연동한 모습또한, 모니터링 시스템에 대한 모니터링도 중요합니다. SaaS를 이용하는 경우에는 최악의 경우 해당 서비스의 점검기간에 대비할 수 없으며, 심지어는 점검중이라는 사실 조차 인지하지 못할 수 있습니다. 이에 대비하기 위해 리디북스에서는 Server Density로 모니터링을 모니터링하고 있습니다.맺음말장애를 얼마나 꼼꼼하게 예방하는지, 그리고 얼마나 즉각적으로 반응하는지는 팀 구성원의 실력으로 정해지는것이 아니라 팀의 문화와 원칙에 따라 정해집니다. 아직 팀에 뚜렷한 대응 원칙이 없다면 먼저 상황에 맞는 기준과 척도를 결정하고 공유해볼 것을 추천합니다.무엇보다 DevOps를 수행하는 것은 사람임을 잊지 말아야 합니다. 인간은 99.99% 가용성이나 24/7 을 보장하지 못하며, Uptime은 하루도 되지 않습니다. 최근 DevOps가 대세가 되어가지만 Ops에서의 인간적인 측면은 진지하게 고려되지 않고 있습니다. 이러한 환경을 개선하기 위한 HumanOps에 대한 소개와 함께 글을 마칩니다.     HumanOps 계명시스템을 만들고 고치는 것은 인간이다.인간은 지치고 스트레스를 받으며, 행복과 슬픔을 느낀다.시스템은 아직 감정이 없다. 오로지 SLA만 있다.인간은 스위치 온/오프 상태를 반복해야 한다.시스템을 운영하는 인간의 행복이 시스템의 안정성에 영향을 준다.빈번한 알림 == 인간의 피로최대한 자동화하고, 최후의 수단으로 인간에게 이관하라.문서화하고, 훈련하고, 시간을 아껴라.창피 주지 마라.인간의 문제는 시스템의 문제다.인간의 건강은 사업의 건강에 영향을 준다.인간 > 시스템#리디북스 #개발 #DevOPS #모니터링 #인사이트 #서버개발 #운영 #꿀팁
조회수 6049

개발자 채용 시 기술검증 어떻게 할 것인가

eBrain에서 진행하는 "개발자 채용 시 기술검증 어떻게 할 것인가"라는 미니 워크숍을 다녀왔다. 항상 고민하고 있는 주제이기도 하고 개인적으로 팬심(?)을 가지고 있는 김창준님의 강의라 한시간 거리를 극복했다.  이미 창천향로님이 강의 내용을 잘 정리해 주셨다. 하지만 내 자신의 학습을 위해 강의 내용을 재해석 해서 적어 본다. 빠져든다! 1. 현재 기술력 검증의 문제점최근의 개발자 채용에 사용되는 기술력 검증 방식은 다음과 같은 것들이 있다.  온라인 코딩 테스트 (최근에 여러 가지 플랫폼도 있다)손 코딩 테스트기술 인터뷰과제 제출이 중 최근에는 주로 알고리즘에 대한 코딩 테스트가 주가 되는 것 같다. 생각보다 난이도가 있어서 재직자들이 “이런 문제면 저는 못 들어왔을 것 같아요”라고 하는 경우도 있다. 코딩 테스트에 대해 두 가지 사례를 들어 질문을 던져 본다.  삼각형 판별 문제삼각형 판별 문제는 세 좌표가 주어졌을 때 이 삼각형이 어떤 삼각형인지 (정삼각형, 이등변 삼각형, 둔각 삼각형 등)를 맞추는 것이다. 이 프로그램이 잘 동작하는지를 검증하는 것이 QA 동네의 ‘Hello World’ 문제다. 이 문제가 주어지면 초보자들은 그냥 문제를 푼다. 하지만 전문가는 문제를 풀지 않고 “이 프로그램을 누가 쓸 것인가요?”를 물어본다. 콘텍스트에 따라서 완전히 다른 테스트의 설계가 필요하기 때문이다.  코딩 테스트도 이와 비슷하다. 코딩 테스트는 단순화된 문제를 푼다. 즉 맥락이 제거된 상태에서의 문제를 푼다. 실무는 종합적인 환경에서 이뤄진다. 따라서 이 문제를 잘 푼다는 것이 실무를 잘할 수 있는 것을 의미하지 않을 수 있다.  질문) 우리의 코딩 테스트는 과연 실무에서의 실력과 높은 상관관계가 있는가?  전문성 연구개발자는 종종 전문성의 연구 대상이 되곤 한다. 이때 연구비를 이유로 주로 혼자서 빠르게 풀 수 있는 문제로 실험이 이뤄진다. 하지만 이런 식의 실험들에서 “토이 문제”가 아닌 “복잡하고 확장된 문제"를 전달했을 때 전혀 다른 결과가 도출된다는것을 알게 되었다.  복잡한 문제, 즉 실제 문제를 풀 때는 인지적 전략이 많이 바뀐다. 또한 사회적 요소도 필요하다. 이런것들을 “토이 문제”로 검증하기는 쉽지 않다. X를 테스트하면 X를 잘하는 사람을 뽑게 된다.  즉, 알고리즘 코딩 테스트를 하면 알고리즘 코딩 테스트에 능한 사람을 뽑게 된다. 질문) 실무에 최대한 가까운 상황을 제한된 면접 시간 내에 만들어 내려면 어떻게 해야 할까? 2. 개발자 채용은 어떻게 해야 할까?채용이 더 크리티컬 한 곳이 있다. 델타포스, 네이비씰과 같은 특수부대이다. 이곳에서는 사람을 어떻게 뽑을까?  작전 지역을 설정 해 두고, 보급품과 군사장비를 실제 작전 수행 환경과 같이 조성해 놓는다. 그곳에서 직접 작전을 수행하는 것을 시뮬레이션 한다.이를  교관이 직접 따라가며 기록과 채점을 한다.  개발자의 면접 시에도 최대한 실제와 비슷한 환경을 구축하는 것이 좋다. 코딩 문제처럼 맞고 틀림만 보는 것이 아니라 과정에 대한 채점이 이뤄져야 한다. 3. 효과적인 기술력 검증을 위해서는 어떻게 준비해야 하는가?1) 우리가 하는 일을 분석한다.  우리가 하는 일에 코딩만 있는 것이 아니다. 설계도 하고, 버그도 찾고, 장애 해결도 하고, 커뮤니케이션도 한다.  2) 대표 케이스들을 뽑거나 만들어 내야 한다.  예를 들어 새롭게 코드를 작성하는 것보다 기존의 기능을 파악해서 코드를 수정하는 일을 더 많이 한다면 이런 상황을 문제로 만드는 것이 좋다.  3) 대표 케이스들로 파일럿 테스트를 해본다.  우리 회사의 뛰어난 개발자 3명과 평범한 개발자 3명에게 이 문제를 풀게 해보고 이를 기준으로 채점표를 만들어야 한다. 어느 누가 평가해도 비슷하게 나오도록 해야 한다. 뛰어난 개발자의 문제 풀이 방식을 기준으로 채점 기준을 만들 수 있다. 예를 들면 다음과 채점 기준이 나올 수 있다.  질문을 5개 이상 한다.코딩하는 과정에서 반복적인 실행을 한다. 4) 면접 후에는 결과에 대한 논의가 필요하다.  특정 항목에 대해 채점 기준이 다른 경우 이에 대한 논의 과정이 필요하다. 이는 면접관의 훈련에 도움이 된다.   4. 실습실제로 면접 문제 만드는 것을 실습해 보자.1) 수강생의 제안다음과 같은 면접 문제는 어떨까요?첫날 출근을 했는데 회사 웹서비스가 죽었습니다. 어떻게 하면 좋을까요? 2) 코칭좀 더 게임스럽게 만들어 본다. 실제 토이 서버를 죽여 놓고, 쉘을 주면서 실제로 어떻게 해결 하는지 살펴본다.옆에 조언을 줄 수 있는 가상의 3년 차 팀원(NPC처럼)을 제공한다. 제한된 답변을 하도록 한다.면접자가 다음과 같은 경우면 더 높은 점수를 줄 수 있다. 실제 업무를 할 때에는 이런 상황까지 이어진다는 것을 유념하자.  문제의 원인을 밝힌 이후에 이 문제를 근본적으로 해결하기 위한 후속조치를 말한다. 개발팀 내에 이 원인과 해결에 대한 공유를 한다.  5. 질문 답변1) 필터링의 목적으로 코딩 테스트는 의미가 있나요? 간단한 문제를 던져서 못 푸는 사람을 필터링하는 것으로는 의미가 있다. 하지만 그 이상의 목적으로 사용하는 것은 조심해야 한다고 생각한다.코딩 테스트라는 과정은 특히 지원자에게 많은 비용이 드는 과정이기 때문에 조금 더 경제적인 방법들이 있다. 예를 들면 “행동 기반 인터뷰”가 있다. 과거에 있었던 행동에 대한 구체적인 질문을 던지는 것이다.또한 코딩 테스트는 지원자에게 상당히 스트레스를 주는 방법이고, 지능이 높은 사람은 오히려 스트레스에 취약하다는 연구가 있다. 따라서 코딩 테스트를 진행하더라도 스트레스를 덜 주는 방향을 고민해야 한다.  2) 블라인드 테스트(이력서를 보지 않고 면접)의 장단점? 결국 코딩 테스트에 적합한 사람을 뽑게 될 것 같다. 코딩 테스트라는 것이 훈련 과정이 필요하기 때문에 입사에 대한 갈망을 볼 수는 있겠다. 질문 시에는 실무와 관련이 깊은 질문을 하면 좋겠다. 역시나 과거의 행동에 기반한 질문이 편향이 적고 많은 정보를 얻을 수 있다. 예를 들면 “팀장이 한 달 걸릴 일을 일주일 만에 끝내라고 한 적이 있나요? 그때 어떻게 하셨나요?”와 같은 질문이다. 3) 끈기, 성실 여부를 판단할 수 있을까요? 주위에서 끈기, 성실이라는 키워드를 생각하면 떠오르는 사람이 있을 것이다. 그 사람의 구체적인 행동을 기반으로 면접 문제를 만들어내는 것이 좋다. 행동에 대한 질문을 할 때에는 과거에 대한 질문을 하는 것이 좋다. 사람은 미래에 대해서는 거짓을 이야기 하가 쉽지만 과거의 이야기를 할 때에는 과거의 상황을 조작하는 동시에 거짓말을 하기가 쉽지 않다.  4) 채용 여부는 실력에 기반하게 되는데, 결국 연봉은 연차에 따라 주게 된다. 좀 더 세밀하게 측정할 수 있는 방법이 있을까? 임시 월급을 주고, 1달 혹은 3달 뒤에 급여를 적용하는 방법이 있다. 실제 환경에서는 보다 정확하게 퍼포먼스를 측정할 수 있다.  하지만 입사할 때 연봉이 중요한 요소가 되지 않게 하는 것이 더 주요한 방법이다. 내재적 동기를 갖게 하는 것이 더 중요하다. 연봉 인상에 따른 동기는 최대 3 달이면 없어진다. 외재적 동기는 점점 내재적 동기를 감소시킨다. 그 일을 즐기지 않게 되고, 하기 싫어지고, 성과가 없어진다. 연봉 말고 다른 협상 거리를 많이 가지고 있어야 한다. 연봉이 여러 가지 조건 중 하나가 되어야 한다.  5) 현재 잘하는 사람을 기준으로 채점 기준을 만들었다면, 다른 장점이 있는 사람이 탈락되지 않을까? 만일 현재 채점기준에는 적합하지 않지만, 다른 측면에서 장점이 있는 사람이 있다면 그 측면을 반영한 채점 기준을 만들어야 한다.  채용에 대해서 틀린 선입견을 가지고 있는 경우가 많이 있다. 예를 들면 술을 잘 먹는 사람이 협력을 잘한다.라고 생각하는 것이다. 그 반례가 있는지를 생각해 보면 그런 선입견을 깨는데 도움이 된다.  6) 비개발자와 함께 면접을 할 때 합의가 힘든 경우가 있다.  회사 안에서 어떤 사람을 뽑고 싶은지 합의가 필요하다. 우리 회사에서 핵심 인재를 추린 다음에 이 사람들의 공통점을 찾아서 인재상을 만들어야 한다.  7) 전화면접 괜찮을까요? 화상면접이 더 효과적인진 않을까요? 억양이 포함되어 있는 대화는 90%의 정보를 전달할 수 있다고 본다. 그 사람의 생각을 충분히 전달받을 수 있기 때문에 화상면접이 크게 더 효과적이라고 생각하지는 않는다.  우리나라에서는 많이 하지 않지만 면접에 대한 비용이 저렴하기 때문에 전화면접이 효과적인 수단이라고 생각한다. 단, 전화면접을 하기 전에 기준이 명확해야 한다. 느낌만으로 판단을 내리는 것은 의미가 없다. 8) 사내 전문가가 없는 영역에 대한 채용을 해야 한다면? 회사 외부의 전문가 몇 분을 찾아가서 그분들의 경험을 듣는다. 그 경험들에 기반해서 면접 문제를 만든다. 도메인에 관계없는 전문성이 있는지는 검증할 수 있는 방법이 있다. 즉, 전문가의 특징이 있다. 전문가는 공부를 한다. 실력을 향상하기 위한 꾸준한 노력을 한다.전문가는 확정적이지 않고 유연하다. 9) 러닝 커브가 좋은 사람을 찾는 방법은? 소규모 회사일수록 현재는 저평가되어 있지만 성장 가능성이 있는 사람을 채용해야 한다. 사실 능력 좋은 사람이 노력도 많이 한다. 뛰어난 사람은 “의도적 수련”의 양이 많고 질이 좋다.  학습에 관련된 테스트를 할 수도 있다. 예를 들어 “새로운 언어로 작은 프로그램을 작성해 보세요. 그리고 그 과정을 타임 로그로 남겨보세요” 와 같은 문제를 보면 학습 자체에 대한 능력을 테스트할 수 있다.  10) 개발을 잘하는 친구는 리드를 안 하려고 하고, 상대적으로 부족한 친구는 리드를 하려고 합니다.  개발을 잘하는 것에 대해서 생각해 볼 필요가 있다. 보통 개발을 잘한다고 하면 코딩을 잘하는 것만 생각하지만 협력에 대한 것이 포함되어야 한다. 흔히 하는 실수가 코딩 실력만 보고 리더를 삼으려고 하는 것이다.  내가 좋아했던 상사를 생각해 보고 그 사람의 특징을 생각해 보는 것부터 시작해 보는 것이 좋겠다. 개발 트랙, 매니저 트랙으로 나눠서 이야기하는 것은 좋지 않다.   6. 후기좋은 시간이었다. 워크숍에 참여하고 나서 어떻게 실력을 검증할것인가에 대해 구체적인 방향이 잡혔다. 우리가 현재 하고 있는 것들 중에 도움이 되는것과 그렇지 않은것이 구분 되었다. 8퍼센트에 좋은 분을 모실 수 있게 하나씩 시도해 봐야겠다.#8퍼센트 #에잇퍼센트 #개발자 #워크숍 #워크샵 #채용워크숍 #채용워크샵 #후기 #참여후기
조회수 1883

안드로이드 스튜디오

안녕하세요. 크몽 개발팀 입니다.오늘의 포스팅 주제는 "안드로이드 스튜디오" 입니다.안드로이드 스튜디오는 구글이 직접 만든 안드로이드 앱 개발 도구를 말하는데요.안드로이드 스튜디오는 2013년 5월 개발자 컨퍼런스를 통해 프리뷰 버젼을 처음 공개하였습니다.1년 6개월 정도의 기간동안 베타버전이였지만  지난달 8일에  안정화된 정식버전 1.0이 공개되었습니다.안드로이드 개발자 사이트에 가시면 공식적으로 다운받아서 안드로이드 앱을 개발 할 수 있습니다.( Eclipse로 앱을 개발중인데 개발자 사이트에 배포중인 ADT가 내려가서 당황했던 기억이 나네요^^ ) 안드로이드 스튜디오는 IntelliJ 기반으로 만들어 졌는데요.IntelliJ는 워낙 유명한 개발도구인지라 많은 개발자분들이 알고 계실겁니다.Eclipse 와 같이 통합개발툴인데 안정성과 속도면에서 Eclipse보다 뛰어나기때문입니다.하지만 Eclipse가 안드로이드 초기부터 개발자들이 이용해 왔기 때문에 대부분의 개발들에게 익숙하고현재 나온 가이드 or Tip 들이 Eclipse에 기준이 되어있어서 여러부분에서 시행착오를 겪을거 같습니다.그래서 그런지 안드로이드 스튜디오 정식버젼이 나왔지만 아직은 익숙한 Eclipse에 손이가는데요.앞으로 구글에서 공식적으로 ADT에 대한 지원을 끊었으니 조만간 안드로이드 스튜디오로 갈아탈려고 합니다.Android 개발자 사이트 링크 : http://developer.android.com/index.html----------------------------------------------------------------------------------------새롭게 나온 안드로이드 개발도구 "안드로이드 스튜디오"에 대하여  소개하는  포스팅 해보았습니다.다음에는 안드로이드 스튜디오를 직접 사용해보고 각각의 특징들에 대해좀 더 자세히 설명해드리겠습니다. ^_^#크몽 #개발팀 #인턴 #인턴생활 #팀원소개 #업무환경
조회수 1074

주니어 개발자가 외칩니다, "Hello, System Architecture!"

Overview주니어 개발자는 시스템 아키텍처(System Architecture) 또는 시스템 디자인(System Design)이라는 단어에 덜컥 겁부터 먹습니다. 지금 진행하고 있는 개발에만 집중하다 보니 큰 그림을 놓치고 있는 게 아닐까 란 생각이 들었죠. 조금 더 큰 그림을 보고자 공부를 시작했습니다. 문득 같은 생각을 하는 주니어 개발자 분들도 많을 것 같다고 생각했어요. 그래서 이번 글은 시스템 아키텍처에 ㅇ_ㅇ? 뀨? 하는 표정을 짓는 주니어 개발자들을 위해 썼습니다.상상의 나래: 가상의 패션 e커머스상상의 나래를 펼쳐봅시다. 패션 e커머스 서비스를 이용하는 김유저 씨가 구매한 옷이 마음에 들어 상품 리뷰를 남기고 싶어한다고요.김유저 씨는 본인의 착용 사진과 텍스트 리뷰를 작성하고 ‘리뷰 등록하기’ 버튼에 엔터를 탁! 누를 겁니다. 그런데 말이죠. 김유저 씨는 요청하고 싶은 웹서버의 IP 주소를 모르기 때문에 요청을 보낼 수가 없습니다.내 정체를 알려줘: DNS (Domain Name System)그래서, DNS(Domain Name System)에게 물어봅니다. 서버의 도메인 이름으로부터 해당 서버의 IP 주소를 알려주는 것이 바로 DNS입니다. 도메인 이름에 대한 질의를 하고, 만일 해당 도메인 이름이 DNS에 ‘A Record’ 형태로 등록이 되어 있다면 도메인 이름에 해당하는 IP 주소를 응답으로 돌려줍니다.서비스에서 자체 DNS 시스템을 가지고 있을 수 있습니다. 예를 들어 Route 53, Cloud Flare같은 서비스가 있습니다. 그렇다면 또 한 가지 의문이 생깁니다. 왜 서비스는 시스템적 부담을 안고서 자체 DNS 서버를 구축하고 있는 걸까요? 그 이유로 두 가지를 꼽을 수 있습니다.첫 번째로는 신뢰도가 높습니다. 직접 DNS Record를 관리 및 운영하기 때문입니다. 두 번째로는 보안이 우수합니다. 만약 공개하고 싶지 않은 IP 주소, 예를 들어 Database IP 주소 같은 건 공개하지 않습니다. 1)작업장소: Web Server이제 웹서버의 IP 주소를 알았으니 통신을 시도합니다. 웹서버는 웹서비스에서 필요로 하는 다양한 요청과 그에 대한 응답을 제공합니다. 클라이언트가 리뷰에 대한 사진과 텍스트를 등록하고 싶다면 웹서버에게 등록하라는 요청을 보내야 합니다.웹서버에서 요청을 받으면 사용자가 요구한 대로 사진과 텍스트를 등록하고, 그에 대한 결과 정보를 응답으로 보내줄 것입니다. 웹서버 내부에서는 그 과정에 필요한 연산을 수행합니다. 서버 개발자는 이 연산에 대한 코드를 작성하고요.센스가 없는 서버:API (Application Programing Interface)서버는 사람이 아닙니다. 센스나 재치가 없죠. 미리 정의되지 않은 요청은 대응하지 못합니다. (어버버버버 퉤! Error 404!) 그래서 약속한 요청을 보내면 약속한 방식으로 응답해줄게라고 명세를 제공합니다.약속한 요청으로 데이터를 보내면 원하는 요청에서 데이터를 정제해 잘 처리했는지, 또는 처리된 데이터를 약속한 방식(예를 들어, JSON 방식)으로 내보내죠. 웹서버는 정의된 API에 맞춰 요청과 응답을 합니다.그런데 웹서버가 수많은 요청을 받고 응답하면 과부하가 일어날 수도 있습니다. 사용자 수가 어마어마한 규모로 늘어나서 서버가 펑! 하고 터진다면, 김유저 씨는 서비스를 더 이상 이용할 수 없을 겁니다. 이용하고 싶지도 않을 겁니다!따라서, 서버가 감당하는 요청을 나누기 위해 같은 역할을 하는 서버 장비 수를 늘릴 수도 있습니다. 그러면 요청이 각기 다른 웹서버 장비에 분산되어 한 번에 감당할 수 있는 요청 수가 더욱 많아집니다.이 구역의 매니저는 나야: Load Balancer그림처럼 서버가 4대 존재하는 상황이라면, 서버 4대에 일을 적절히 분배해주는 역할이 필요합니다. 그것이 로드 밸런서(Load Balancer)입니다. 로드 밸런서가 서버에게 일을 나누는 방법론은 여러 가지가 있습니다.Random: 랜덤으로 분배하기Least loaded: 가장 적은 양의 작업을 처리하고 있는 서버에게 요청을 할당하기Round Robin: 순서를 정하여 돌아가며 작업 분배하기많이 쓰는 로드 밸런서의 종류는 Layer 4, Layer 7을 꼽을 수 있습니다.Layer 4 Load Balancer: 데이터의 내용을 보지 않고 IP주소 및 TCP/UDP 정보에 따라 단순히 분배를 해줍니다.Layer 7 Load Balancer: 서버가 하는 역할이 분리되어 있는 환경에서 데이터의 내용을 보고 각기 맞는 역할을 하는 서버에게 분배를 해줍니다.로드 밸런서는 클라이언트가 요청을 보내야 할 서버를 골라야 하는 부담을 덜어주며, 로드 밸런서에게 할당된 vIP (가상 IP)로 요청을 보내기만 하면 로드 밸런서에서 알아서 작업을 나눠줍니다. 서버에서는 적절한 로드 밸런서를 사용하면 들어오는 요청이 여러 장비에 분산되어 처리량이 늘어나고 응답 시간이 줄어드는 효과를 기대할 수 있습니다. 컨텐츠 저장소: CDN(Content Delivery Network)이제 웹서버가 클라이언트의 요청에 의해 웹페이지에 대한 응답 결과를 돌려줬습니다. 이때 클라이언트의 화면에 렌더링해야 하는 수많은 이미지가 필요합니다. 이 이미지들을 웹서버가 전부 주려면 데이터의 용량이 너무 크고, 무거워서 서버가 헥헥거리죠. (서버가 죽으면 어떻게 될까요? 클라이언트님이 경쟁사로 환승하겠죠.. 안 돼요..) 따라서 웹서버는 직접 이미지를 주는 대신 CDN(Content Delivery Network)에게 요청하라고 이야기합니다. CDN은 일반적으로 용량이 큰 컨텐츠 데이터(이미지, 비디오, 자바스크립트 라이브러리 등)를 빠른 속도로 제공하기 위해 사용자와 가까운 곳에 분산되어 있는 데이터 저장 서버입니다. 클라이언트는 용량이 큰 컨텐츠 데이터를 가까운 CDN에 요청해 멀리 있는 웹서버에서 직접 받는 것보다 빠르게 받을 수 있습니다. CDN이 동작하는 방식에는 크게 Push CDN, Pull CDN이 있습니다. Push CDN: 서버에서 컨텐츠가 업로드되거나, 변경되었을 때 모두 반영하는 방식 Pull CDN: 클라이언트가 요청할 때마다 컨텐츠가 CDN에 새로 저장되는 방식 두 방식 모두 장단점이 있습니다. Push CDN은 모든 컨텐츠를 갖고 있기에 웹서버에 요청할 일이 없지만 유지하는데 필요한 용량과 비용이 많이 필요하겠죠? Pull CDN은 클라이언트가 요청한 컨텐츠가 있으면 바로 응답하지만 그렇지 않을 땐 데이터를 웹서버로부터 가져와야 하기 때문에 서버에 요청하는 부담이 존재합니다. 컨텐츠명은 그대로인데 내용만 변경되었다면 인지하지 못하고 옛버전의 컨텐츠를 제공하죠. 그래서 Pull CDN에 들어가는 컨텐츠는 TTL(Time To Live)이 적용됩니다. TTL이란 유통기한이라고 생각하면 쉽습니다. 일정시간이 지나면 해당 데이터가 삭제되는 것이죠. 이런 방식이 적용된다면 Pull CDN의 최대 단점을 보완할 수 있습니다. 이렇게 보완이 되면 수정된 데이터에 대해서도 대응이 가능하며 서버의 용량 즉, 비용적 부담이 해소될 겁니다.소중한 내 데이터: Database서비스를 제공하다 보면 클라이언트의 소중한 정보, 이력, 상품 가격, 상품 정보 등 다양한 데이터를 저장하고, 또 제공합니다. 하지만 수많은 데이터를 웹서버에 전부 저장하고 사용하기엔 데이터의 양이 너무 많아 저장 공간도 부족하고, 데이터를 원하는 모양에 맞게 정제하기가 어렵습니다. 그래서 데이터를 저장하는 데이터베이스 서버가 따로 존재합니다.민감한 정보를 다루는 데이터베이스는 ACID라는 성질을 만족해야 하는데요.Atomicity(원자성): 데이터베이스에 적용되는 명령이 중간만 실행되지 않고 완전히 성공하거나 완전히 실패해야 한다는 것을 의미합니다. 반만 적용된 명령이 있다면 헷갈리겠죠.Consistency(일관성): 데이터베이스가 수행한 명령이 일관적으로 반영되어 있어야 한다는 의미입니다. 예를 들어 계좌에 돈을 입금했는데 잔고에 반영되지 않는다면 당황스러울 겁니다.Isolation(고립성): 데이터베이스가 수행하는 명령 도중 다른 명령이 끼어들지 못한다는 것을 의미합니다.Durability(지속성): 성공적으로 수행한 명령은 영원히 그 이후 상태로 남아있어야 한다는 걸 의미합니다. 갑자기 하루 뒤에 명령이 취소되거나 이전 상태로 롤백되면 안 됩니다. Replication (복제 / 이중화)큰 시스템에서는 똑같은 데이터베이스가 여럿 존재한다고 하는데요. 그렇다면 왜 비용적인 부담을 안으면서까지 복제 데이터베이스를 구축해놓는 걸까요? 만약에 데이터베이스가 정상적으로 동작하지 않는다면 클라이언트의 데이터를 변경하지 못하며, 클라이언트가 원하는 정보를 제공하지 못하는 불상사가 일어나게 됩니다. 글로만 써도 벌써 땀이 납니다. 그러므로 복제해놓은 데이터베이스를 얼른 마스터로 등업해 데이터 흐름에 차질이 없도록 대비해야 합니다.만약 하나의 데이터베이스가 어떤 일을 수행할 때 다른 요청들은 계속 기다려야 합니다. 그렇다면 데이터를 변경하는 데이터베이스는 하나, 읽기만 하는 데이터베이스는 여러 대가 존재해도 되지 않을까요? 바로 여기서 Master-Slave의 개념이 탄생합니다.master-slave-replicaMaster-Slave Replica (a.k.a 주인-노예)요청을 분산하기 위해서 데이터베이스를 늘리다 보면 master-slave 토픽이 등장합니다.Mater: CRUD(Create, Read, Update, Delete)가 모두 가능Slave: R(Read)만 가능Master가 데이터를 변경할 동안 읽기에 대한 요청은 Slave에게 보내집니다. 그렇게 하면 읽기 요청은 분산되어 훨씬 더 수월하고 빠른 속도로 데이터 처리가 가능할 것입니다. 만약 Master가 변경된다면 아래 계급인 Slave, Replica 데이터베이스에게도 이 정보를 전해야 합니다. 다시 말해, 자신에게 들어온 요청(Query)을 동일하게 보내 빠른 시간 안에 동기화를 시켜주죠. 하지만 동기화도 시간이 걸리는 작업이므로 무한대로 Slave Replica를 늘려 확장하기는 어렵습니다.Master-Master Replica의문이 하나 생길 겁니다. “여러 대의 Master를 두어서 변경도 가능하고, 읽기도 가능하게 하면 되지 않을까?”앞서 언급했듯이 같은 데이터의 변경 가능한 데이터베이스는 하나여야 할 것입니다. 동시에 같은 데이터를 변경했을 때 갈등을 해소하기 위한 방법론은 존재하지만, 그 방식이 복잡하고 오래 걸립니다. 안정성도 낮아지고, 효율도 떨어집니다. 그래서 Master-Slave 아키텍처를 선호하는 것이죠.Sharding그러면 같은 데이터베이스 테이블을 동시에 변경하는 건 불가능한 걸까요? 그것을 해소하기 위해 샤딩(Sharding)이라는 방법론을 사용합니다. 샤딩된 테이블은 개념적으론 하나의 테이블처럼 보이지만 사실 그 내용물이 쪼개져 있습니다. 쪼개는 방법은 여러 가지 선택할 수 있습니다만, 분명한 건 겹치는 데이터 없이 쪼갠다는 것입니다. 그래서 같은 테이블이어도 쪼개져 있다면 그 테이블에 동시에 접근해 데이터를 변경할 수 있는 것이죠.이외에 서비스별, 기능별로 쪼개어 데이터베이스를 관리하는 Federation 등 많은 데이터베이스 디자인 방법론이 존재합니다.시스템 아키텍처가 가지고 있어야 할 최소본 아키텍처요점: 시스템 아키텍쳐에서 고려해야 할 성질이렇게 간단한 시스템 아키텍처의 면면을 살펴봤습니다. 시스템 개발자라면 시스템을 디자인하면서 반드시 고려해야 할 성질들을 만날 텐데요. 위에서 소개한 내용들 역시 아래의 성질들을 충족하기 위해 탄생했다고 볼 수 있습니다.Scalability (확장성): 10만 명의 요청을 처리할 수 있는 시스템과 1000만 명의 요청을 처리할 수 있는 시스템은 다릅니다. 확장성을 고려한 시스템은 앞으로 클라이언트 수가 늘어났을 때 무리 없이 모든 요청을 처리할 수 있을 겁니다.Performance (성능): 속도와 정확성을 말합니다. 요청한 내용을 정확하고 빠르게 돌려주어야 합니다.Latency (응답 시간): 모든 요청은 클라이언트가 불편해하지 않을 정도로 빠른 시간 안에 돌려주어야 합니다.Throughput (처리량): 같은 시간 안에 더욱 많은 요청을 처리한다면 좋은 시스템입니다.Availability (접근성): 사용자가 언제든지 시스템에 요청을 보내서 응답을 받을 수 있어야 합니다. 비록 서버 장비 한두 대가 문제가 생겨 제 기능을 하지 못하더라도 사용자는 그 사실을 몰라야 합니다.Consistency (일관성): 사용자가 서버에 보낸 요청이 올바르게 반영되어야 하고, 일정한 결과를 돌려주어야 합니다. 요청을 보낼 때마다 불규칙한 결과를 돌려준다면 믿을 수 없는 서비스가 될 것입니다.결론발로 그렸나 싶을 정도의 그림과 기나긴 글을 마무리 지으며주니어 개발자로서 시스템 아키텍처를 공부하면서 느낀 점이 있다면 시스템에 대한 완벽한 대응은 없으며, 모두 장단점이 존재한다는 것입니다. (이것을 보통 trade-off라고 표현합니다.)하지만 설계하는 서비스를 잘 알고 서비스에서 무게를 둬야 할 부분을 파악한다면, 그에 맞는 시스템을 설계하고 디자인할 수 있을 겁니다. 김유저 씨도 만족시킬 수 있을 거고요. 꼬박 이틀을 밤새워서 쓴 글이 아직 시스템 아키텍처를 두려워하는 다른 주니어 개발자분들에게 도움이 되었으면 합니다. 이번에는 시스템에서 아주 기초적인 부분을 공부했으니 다음 글에선 MSA(MicroService Architecture)를 씹어봅시다! 겁이 나고 무서워도 외쳐보세요. “Hello, System Architecture!”이 세상 모든 주니어 개발자분들, 퐈잇팅입니다.참고1) 추가적인 이점에 대하여: 웹서버에서 요청을 보낼 때 database 도메인 네임으로 보낼 경우, 멀리 있는 공인 DNS 서버 (예를 들면 google public DNS server: 8.8.8.8)에 물어오는 것보다 자체 DNS 서버에 물어오는 것이 훨씬 더 빠른 속도로 응답을 받아올 수 있습니다.출처GitHub - donnemartin/system-design-primer: Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards.글오연주 사원 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발자 #개발팀 #인사이트 #경험공유 #주니어개발자
조회수 3020

GitHub 계정으로 Kubernetes 인증하기

초기에는 kube-aws가 만들어준 관리자 인증서를 통해 Kubernetes를 관리했는데 역시나 대내외적으로 여건이 바뀌니 변화가 필요했다. 내부적으로는 개발 인력이 늘고 여러 프로젝트가 동시 진행되니 Staging 환경이 급격히 바뀌는데 계정이 하나이니 누가 무슨 작업을 했는지 확인하기 어렵고 외부적으로는 경쟁사의 보안사고 등에 영향을 받아 보안을 강화할 필요가 생겼다. 하여 보안 관련 작업을 여럿했고 그 중 하나가 바로 GitHub와 Kubernetes를 OAuth로 엮는 일이다.기본적으로는 개발자 각자가 자신의 GitHub 계정으로 인증 토큰을 받고 이를 이용해 Kubernetes API에 접근하는 것이다. 전체적인 흐름은 How I built a Kubernetes cluster so my coworkers could deploy apps faster 등을 참고하면 이해하기 그리 어렵지 않다.1. Admin time should be saved (since they are also our developers)2. New users can generate their own credentials without needing the admin3. User credential is always private for security reasons4. Developers have their own space to experiment5. Project spaces can be accessed and changed by multiple users6. In the future, we may want to enable auditing to track changes다만 저들과 달리 Webhook 토큰 인증 플러그인을 직접 짜지 않고 coreos/dex를 이용했다. Dex를 이용하면 GitHub를 비롯해 다양한 OpenID, OAuth 2.0 인증 서비스와 Kubernetes 클러스터를 엮기 쉽다. 더욱이 kube-aws에 Dex가 통합되어서 설치하기도 쉽다.설치하기구구절절 어떻게 설정하는지 설명할 생각은 없는데 회사와 프로젝트에 따라 세부적인 차이가 꽤나 클 수 있기 때문이다. 그러니 대략적인 작업 순서를 간략히 기술하고 끝내려 한다.우선 kube-aws의 cluster.yaml를 보자.# # Enable dex integration - https://github.com/coreos/dex # # Configure OpenID Connect token authenticator plugin in Kubernetes API server. # # Notice: always use "https" for the "url", otherwise the Kubernetes API server will not start correctly. # # Please set selfSignedCa to false if you plan to expose dex service using a LoadBalancer or Ingress with certificates signed by a trusted CA. # dex: # enabled: true # url: "https://dex.example.com" # clientId: "example-app" # username: "email" # groups: "groups" # selfSignedCa: true # # # Dex connectors configuration. You can add configuration for the desired connectors suported by dex or # # skip this part if you don't plan to use any of them. Here is an example of GitHub connector. # connectors: # - type: github # id: github # name: GitHub # config: # clientId: "your_client_id" # clientSecret: "your_client_secret" # redirectURI: https://dex.example.com/callback # org: your_organization # # Configure static clients and users # staticClients: # - id: 'example-app' # redirectURIs: 'https://127.0.0.1:5555/callback' # name: 'Example App' # secret: 'ZXhhbXBsZS1hcHAtc2VjcmV0' # # staticPasswords: # - email: "[email protected]" # # bcrypt hash of the string "password". You can use bcrypt-tool from CoreOS to generate the passwords. # hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" # username: "admin" # userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"우선 GitHub의 Organization Settings 메뉴로 가서 OAuth Apps에 Dex를 추가한다. 이때 Authorization calllback URL은 https://dex.example.com/callback가 된다.GitHub가 준 Client ID와 Client Secret를 cluster.yaml에 적어넣는다.dex: enabled: true url: "https://dex.example.com" clientId: "example-app" username: "email" groups: "groups" selfSignedCa: false # # # Dex connectors configuration. You can add configuration for the desired connectors suported by dex or # # skip this part if you don't plan to use any of them. Here is an example of GitHub connector. connectors: - type: github id: github name: GitHub config: clientId: "GITHUB_OAUTH_APP_CLIENT_ID" clientSecret: "GITHUB_OAUTH_APP_CLIENT_SECRET" redirectURI: https://dex.example.com/callback org: DailyHotel # # Configure static clients and users staticClients: - id: 'example-app' redirectURIs: 'https://kid.example.com/callback' name: 'Example App' secret: 'ZXhhbXBsZS1hcHAtc2VjcmV0'staticPasswords: - email: "[email protected]" # # bcrypt hash of the string "password". You can use bcrypt-tool from CoreOS to generate the passwords. hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" username: "admin" userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"여기서 dex.example.com은 kube-aws가 띄울 dex Deployment와 연결되는 서비스(ELB)의 도메인주소가 되어야 한다. 그런데 kube-aws는 Dex의 External service를 생성해주지 않으므로 아래와 같이 직접 서비스를 생성해야 한다. GitHub가 이쪽으로 콜백을 보내야 하므로 방화벽을 열어야 하고 회사 도메인 인증서를 붙일 것이므로 `selfSignedCa`값은 `false`로 한다.apiVersion: v1 kind: Service metadata: name: dex namespace: kube-system labels: app: dex component: identity dns: route53 annotations: domainName: dex.example.com service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:blahblah service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https spec: ports: # the ports that this service should serve on - name: https port: 443 targetPort: 5556 protocol: TCP selector: app: dex component: identity type: LoadBalancer loadBalancerSourceRanges: - 0.0.0.0/0staticClients / example-app는 Dex에 포함된 예제 프로그램이다. 이를 이용하면 웹 브라우저를 통해 GitHub에 인증하고 토큰을 내려받을 수 있다. DailyHotel/kid 등의 도커 이미지를 사용하면 쉽게 띄울 수 있다. kube-aws는 이 예제 프로그램을 띄우지 않기 때문에 직접 올려야 한다.apiVersion: v1 kind: Service metadata: name: kid namespace: kube-system labels: app: kid dns: route53 annotations: domainName: "kid.example.com" service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:blahblah service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https spec: ports: - name: https port: 443 targetPort: 5555 protocol: TCP selector: app: kid type: LoadBalancer loadBalancerSourceRanges: - 사무실IP/32 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: kid namespace: kube-system spec: replicas: 1 template: metadata: labels: app: kid spec: containers: - name: kid image: dailyhotel/kid:latest livenessProbe: tcpSocket: port: 5555 timeoutSeconds: 120 ports: - containerPort: 5555 env: - name: CLIENT_ID value: example-app - name: CLIENT_SECRET value: ZXhhbXBsZS1hcHAtc2VjcmV0 - name: ISSUER value: https://dex.example.com - name: LISTEN value: http://0.0.0.0:5555 - name: REDIRECT_URI value: https://kid.example.com/callback이때 example-app의 REDIRECT_URI는 Dex의 REDIRECT_URI와는 다르다는 점에 주목하자. 옵션의 이름이 비슷하기 때문에 헷갈릴 수 있다. 또한 CLIENT_ID와 CLIENT_SECRET은 cluster.yaml 중 GitHub connector 설정이 아닌 staticClients 설정에서 쓴 값이라는 점도 눈여겨볼 필요가 있다.이 정도만 주의하면 dex를 설치하고 설정하는 것은 어렵지 않다. 이제 인증하는 방법을 알아보자.인증하기웹브라우저로 kid에 방문해서 토큰을 받는다. 첫 화면에서 Login 버튼을 누른 후 GitHub 로그인을 하면 토큰이 나온다.GitHub Public profile 메뉴로 가서 Public email 설정을 확인한다. 공개 이메일이 없다면 하나 추가한다. 로그인시 사용자 아이디로 쓰기 위함이다.kubeconfig 파일을 열고 kubeconfig 파일을 열고 MY_PUBLIC_GITHUB_EMAIL에는 GitHub 공개 이메일 주소를 적고 VISIT_KID_EXAMPLE_COM_AND_GET_TOKEN에는 앞서 받은 토큰을 적는다.apiVersion: v1 kind: Config clusters: - cluster: certificate-authority: credentials/ca.pem server: https://MY_KUBE_CLUSTER name: kube-aws-cluster contexts: - context: cluster: kube-aws-cluster namespace: default user: MY_PUBLIC_GITHUB_EMAIL name: kube-aws-context users: - name: MY_PUBLIC_GITHUB_EMAIL user: token: VISIT_KID_EXAMPLE_COM_AND_GET_TOKEN current-context: kube-aws-context인증 파일의 설정이 정확한지 확인하려면 kubectl --kubeconfig=./kubeconfig version을 실행해보자. 아래와 같이 Client/Server의 버전이 둘다 나오면 정상이다.$ kubectl --kubeconfig=./kubeconfig version Client Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.1", GitCommit:"b0b7a323cc5a4a2019b2e9520c21c7830b7f708e", GitTreeState:"clean", BuildDate:"2017-04-03T20:44:38Z", GoVersion:"go1.7.5", Compiler:"gc", Platform:"darwin/amd64"} Server Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.2+coreos.0", GitCommit:"79fee581ce4a35b7791fdd92e0fc97e02ef1d5c0", GitTreeState:"clean", BuildDate:"2017-04-19T23:13:34Z", GoVersion:"go1.7.5", Compiler:"gc", Platform:"linux/amd64"}참고 자료johnw188/dex-exampleKubernetes / Authenticating#데일리 #데일리호텔 #개발 #개발자 #개발팀 #기술스택 #도입후기 #일지 #경험공유 #Kubernetes #Github
조회수 2970

Python 기반의 웹서비스 개발 환경 구축 방법

안녕하세요. 스포카 크리에이터팀의 프로그래밍 인턴을 맡고 있는 __박종규, 정성재, 고아라__입니다. 저희 세 명은 각각 다른 OS 환경에서 웹서비스를 개발하였는데 이번 포스팅에서는 OS별로 개발 환경을 구축하는 과정을 설명하겠습니다.PythonPython(파이썬)은 비영리의 Python 소프트웨어 재단이 관리하는 개방형, 공동체 기반 개발 모델을 가지고 있습니다. Python은 C파이썬 구현이 사실상의 표준이며 동적 타이핑 범용 프로그래밍 언어로, 펄 및 루비와 자주 비교됩니다. Python은 순수한 프로그래밍 언어의 기능 외에도 다른 언어로 쓰인 모듈들을 연결하는 Glue language로써 자주 이용됩니다. 실제 Python은 많은 상용 응용 프로그램에서 스크립트 언어로 채용되고 있습니다. 또한, 유니코드 문자열을 지원하여 다양한 언어의 문자 처리에도 능합니다. 현대의 Python은 여전히 인터프리터 언어처럼 동작하나 사용자가 모르는 사이에 스스로 Python 소스 코드를 컴파일하여 바이트 코드를 만들어 냄으로써 다음에 수행할 때에는 빠른 속도를 보여줍니다.Windows에서 Python 2.x 설치 방법Linux와 Mac OS에서 Python은 기본적으로 설치되어 있기 때문에 터미널 창에서 Python 명령만으로 쉽게 실행할 수 있지만 Windows에서는 Python을 따로 설치해주어야 합니다.Python 공식 사이트해당 사이트에 들어가서 Quick Links (2.7.3) – Windows Installer 선택하여 python-2.7.3.msi 다운로드 후 실행하여 설치합니다. 특별히 원하는 버전이 있을 때에는 DOWNLOAD – Releases에서 버전별로 설치파일을 다운로드 할 수 있습니다. Python의 기본 설치 경로는 C:\Python27 이며 설치 완료 후, 환경변수를 등록해야 합니다. 환경변수를 등록하는 방법은 다음과 같습니다.내컴퓨터(마우스 오른쪽 버튼 클릭) – 속성(시스템 등록 정보) – 고급 – 환경 변수 클릭시스템 변수 – Path 선택 후 편집 클릭변수 값에 맨 뒤에 C:\Python27;C:\Python27\Scripts; 입력 후, 확인 버튼을 눌러 시스템에 적용변수 값은 각각 ; (세미콜론) 으로 구분Python의 패키지 설치 방법시작 메뉴 – 실행 – CMD 로 커맨드 창을 실행 시킨 후에 Python이 설치된 디렉터리로 이동합니다.cd C:\Python27\Scripts 그 다음 easy_install pip 명령으로 pip를 설치해줍니다. pip는 PyPI(Python Package Index)에 등록된 패키지들을 설치하고 관리할 수 있는 패키지 관리 시스템입니다.$ easy_install pip 다음은 pip 명령의 사용법입니다.pip install packageName 명령 : 최신버전으로 설치pip install packageName==0.x.x 명령 : 0.x.x버전으로 설치패키지 설치 경로 : C:\Python27\Lib\site-packagespip uninstall packageName 명령 : package 제거pip freeze 명령 : 현재 환경에 설치된 package 이름과 버전 목록 PostgreSQLPostgreSQL는 California 대학 Berkeley computer science 학부에서 개발한 POSTGRES, Version 4.2 를 기반으로 한 오브젝트 RDB 관리 시스템(ORDBMS)입니다. 또한, PostgreSQL은 BSD 허가권으로 배포되며 오픈소스 개발자 및 관련 회사들이 개발에 참여하고 있습니다. 따라서 누구라도 사용, 수정, 배포할 수 있으며 목적과 관계없이 무료로 이용할 수 있습니다.각 운영체제 별 PostgreSQL 설치 방법WindowsWindows에서 PostgreSQL을 설치하기 위해 해당 사이트로 접속합니다.PostgreSQL 공식 사이트해당 사이트의 Download탭을 선택하여 Downloads 페이지로 이동합니다. 그 다음 Binary packages 에서 Windows를 선택하여 Windows installers 페이지로 이동합니다. One click installer 에서 Download 선택 후 이 페이지 로 이동하여 해당하는 OS 버전으로 선택하여 다운로드 후 설치합니다.Linux (ubuntu 12.04 LTS)Linux(리눅스)에서 PostgreSQL은 여러 가지 방법으로 설치 할 수 있습니다. 패키지로 설치하는 방법은 터미널 창에서 아래와 같은 명령어를 입력하시면 됩니다.$ sudo apt-get install postgresql-X.X(버전명) 다른방법으로 설치하는 방법은 아래 주소로 들어가시면 자세히 나와 있습니다.PostgreSQL 공식 사이트추가로 PostgreSQL을 편하게 이용하고 싶은 사용자는 pgadmin3이라는 PostgreSQL의 GUI 프로그램을 설치하시면 됩니다. 설치 방법은 터미널 창에서 아래와 같은 명령어를 입력하시면 됩니다.$ sudo apt-get install pgadmin3 Max OS ( MAC OS X Mountain Lion 10.8.2)Mac OS에서 PostgreSQL은 Homebrew를 이용하여 설치하도록 하겠습니다. Homebrew는 Mac OS의 패키지 관리자 프로그램입니다. PostgreSQL은 Homebrew 이외에도 PostgreSQL 다운로드 사이트, Homebrew와 비슷한 기능을 하는 MacPorts 등을 이용해서도 다운받을 수 있습니다. 하지만 PostgreSQL 이외의 다른 프로그램을 설치하기에도 패키지 관리자가 유용하기 때문에 저는 Homebrew를 이용하도록 하겠습니다. 일단, Homebrew 패키지를 컴파일 하기 위해서 xCode가 설치되어 있어야 합니다. 앱스토어에서 xCode를 검색하여 받도록 합니다.xCode 설치를 완료했으면 메뉴에서 __xCode탭 - Preferences - Downloads - Command Line Tools__를 다운받습니다. 그럼 이제 Homebrew 패키지를 컴파일할 수 있게 되었습니다.이제 터미널을 실행시키고 다음 명령을 입력하면 Homebrew가 설치됩니다.$ ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)" Homebrew가 이미 설치되어있다면 업데이트할 사항이 없는지 확인해보고 업데이트를 시켜주세요.$ brew update이제 Homebrew 설치를 완료했으니 간단하게 PostgreSQL을 설치할 수 있습니다.$ brew install postgresql 설치된 버전을 $ psql –ver 명령으로 확인해 보니 PostgreSQL 9.1.4 버전이 설치되어 있었습니다.psycopg2psycopg2는 Python library의 한 종류로써 하는 역할은 Python에서 PostgreSQL를 활용하게 해주는 library입니다. 설치 방법은 터미널 창에서 아래와 같은 명령어를 입력하시면 됩니다.$ pip install psycopg2 * 참고 ubuntu/debian 사용하시는 분 중에서 설치가 안 되시는 분들은 psycopg2를 설치하시기전에 python-dev라는 패키지 파일을 설치하시면 psycopg2를 설치 하실 수 있습니다. 설치 방법은 터미널 창에서 아래와 같은 명령어를 입력하시면 됩니다.$ sudo apt-get install python-dev FlaskFlask는 Python용 Micro Framework이며, Jinja2 template engine과 Werkzerg WSGI toolkit에 의존합니다. Flask에는 기본적으로 많은 환경설정 값들이 존재합니다. 규칙에 따라 템플릿 파일과 CSS, JavaScript, Images 등의 파일들은 templates과 static 이라는 하위 디렉터리에 각각 보관해야 합니다. 기본적으로 Flask는 다른 라이브러리에는 존재하는 Database abstraction layer, Form validation 등의 기능을 포함하지 않지만, 기능을 추가할 수 있는 확장성을 제공합니다. 이미 수많은 라이브러리들이 Database integration, Form validation, Upload handling, Various open authentication technologies등을 제공합니다. SQLAlchemy나 다른 DB Tool을 이용하여 고급 패턴을 구현할 수 있으며, Flask를 이용해보면 다양한 기능을 확장할 수 있다는 것을 알 수 있을 것입니다.Flask 설치 방법Flask를 설치하려면 터미널 창에서 다음 명령어를 입력하면 됩니다. (windows의 경우 cmd창) $ sudo pip install flask SQLAlchemySQLAlchemy는 Oracle, DB2, MySQL, PostgreSQL, SQLite와 같은 관계형 데이터베이스에 강력하고 유연한 인터페이스를 제공하는 Python SQL Toolkit이자 Object Relation Mapper(ORM)입니다. 여기서 ORM은 객체를 관계형 DB 테이블에 매핑해주는 역할을 하는데 SQLAlchemy는 객체를 매핑하기 위해 특정 클래스를 상속받지 않아도 되기 때문에 높은 수준의 라이브러리라고 할 수 있습니다.앞선 단계까지의 설치가 완료되었다면 SQLAlchemy의 설치법 역시 pip 명령을 쓰면 되므로 간단합니다. 윈도우는 커맨드창을 실행시키고, 리눅스와 MAC OS의 경우 터미널을 실행시켜 다음 명령을 입력합니다. 저는 SQLAlchemy 0.7.2 버전을 설치했습니다.$ pip install SQLAlcheymy==0.7.2Permission denied라는 문구가 뜨면 권한이 없는 것이므로 관리자 계정으로 설치를 해주어야 합니다. 관리자 계정은 $ sudo su로 로그인하거나 명령 앞에 sudo를 붙이면 됩니다.$ sudo pip install SQLAlchemy==0.7.2 여기에서 ==0.7.2는 설치할 SQLAlchemy의 버전을 뜻하며 버전을 명시하지 않으면 최신 버전으로 깔립니다.Flask-SQLAlchemyFlask-SQLAlchemy는 SQLAlchemy를 더욱 뒷받침할 수 있는 Flask의 확장으로 SQLAlchemy 0.6 이상의 버전을 필요로합니다. Flask-SQLAlchemy 역시 pip 명령으로 설치할 수 있으며 저는 Flask-SQLAlchemy 0.15 버전을 설치했습니다.$ sudo pip install Flask-SQLAlchemy==0.15 마치며지금까지 Windows, Linux, Mac OS에서 Python, PostgreSQL을 이용한 웹서비스 개발 환경 구축 방법에 대해 알아보았습니다. 이 글을 통해서 처음 접한 사람들도 특정 OS에 구애받지 않고 쉽게 웹서비스 개발 환경을 구축하는 데에 도움이 되었으면 좋겠습니다.출처Python 위키백과pip 위키백과 Flask 홈페이지PostgreSQL plusPostgreSQL 위키백과 Essential SQLAlchemy, O REILLY, 2008Flask-SQLAlchemy 홈페이지#스포카 #개발 #개발팀 #개발자 #파이썬 #Python #개발환경 #업무환경 #꿀팁 #조언 #인사이트
조회수 4744

Elasticsearch X-Pack Alerting 체험기

Logstash로 로그를 수집한 후 Elasticsearch와 Kibana로 분석하는 방법을 다룬 글은 많다. 그런데 이상하더라 이 말이지. 로그를 분석하고 경향을 파악하는 정도라면야 괜찮은데 심각한 오류 로그를 발견했을 때 Slack이나 이메일 등으로 알람 받을 수단이 마땅치 않더라. 사람이 키바나 대시보드를 5분마다 확인할 수도 없는 노릇이다. (이건 새로운 차원의 고문?)이런 생각을 먼저 한 사람이 있기 마련이라 Yelp의 elastalert라던가 Elasticsearch의 X-Pack을 활용하면 이런 문제를 해소할 수 있다. 오늘은 그 중에서 후자를 살펴볼 예정이다.경고! X-Pack은 Elasticsearch가 유료 서비스 시장을 열려고 야심차게 미는 모양인데 “자기네가 직접 만들었으니 쿨하겠지?”라고 쉽게 생각하면 하루 안에 절벽 아래로 떨어지는 끔찍한 기분을 맞이할 수도 있다.X-Pack은 가격이 상당한데 Alert 등을 설정하려면 전적으로 RESTful API에 의존해야 한다. 적어도 아직까지는! 이 사실을 깨닫자마자 당황할 수 있는데 침착하자. 이것은 시작일 뿐이다. 여러분이 검색엔진의 초보라면 그 다음 난관은 검색 쿼리를 작성하는 것이다. “나는 그냥 OutOfMemoryError 로그를 발견하면 알람을 보내줬으면 좋겠어"라고 쉽게 생각했겠지만 그 간단한 결과를 얻으려면 험난한 여정을 거쳐야 한다."search" : { "request" : { "indices" : [ "", ], "body" : { "query" : { "bool" : { "must" : { "multi_match": { "query": "OutOfMemoryError", "fields": ["message", "log"] } }, "filter" : { "range": { "@timestamp": { "from": "{{ctx.trigger.scheduled_time}}||-5m", "to": "{{ctx.trigger.triggered_time}}" } } } } } } } }음… 좋다. 일단 이렇게 작성한 쿼리가 제대로 된 것인지 테스트하려면 어떻게 해야 하는가? 검색 API로 대충 테스트해볼 수는 있다.GET logstash-2017.02.2*/_search { "query" : { "bool" : { "must" : { "multi_match": { "query": "OutOfMemoryError", "fields": ["message", "log"] } } } } }어찌어찌 잘 나온다. 그래서 잘 돌 줄 알았지? 그럴 줄 알고 있다가 이런 메시지를 만난다.Trying to query 1157 shards, which is over the limit of 1000. This limit exists because querying many shards at the same time can make the job of the coordinating node very CPU and/or memory intensive. It is usually a better idea to have a smaller number of larger shards. Update [action.search.shard_count.limit] to a greater value if you really want to query that many shards at the same time.음… logstash 인덱스를 매시간마다 분할했더니 샤드가 꽤 많아진 모양이다. 그래서 최근 두 개의 인덱스로 검색 대상을 제한하려고 한다. Date math support in index names라는 문서에 인덱스 이름을 동적으로 바꾸는 법이 나와 있긴 하다. 그런데 막상 내가 짠 게 어떤 값이 나오는지 확인하는 방법은 제대로 안 나온다. 예를 들어 가 logstash-2017.02.22t01로 해석되는지 어떻게 아는가? 많은 삽질 끝에 방법을 찾았다.를 URL 인코딩한다.그렇게 얻은 값 을 가지고 인덱스 조회 API를 호출한다. GET /3Clogstash-{now-1h/d}t{now-1h{HH}}>그러면 다음과 같이 결과가 나와서 인덱스 이름이 어떻게 해석됐는지 확인할 수 있다.{ "logstash-2017.02.23t01": { "aliases": {}, "mappings": { /* 중략 */ } }여기까지는 전적으로 검색 쿼리 작성 경험이 부족해서 발생한 삽질이다. 하지만 애플리케이션 로그 분석을 패턴화하지 않고 이렇게 검색 쿼리를 복잡하게 짜야 한다니 아직 갈 길이 멀다는 생각이 든다. DataDog 또는 NewRelic 같은 상용 서비스를 참고해서 개선하면 좋겠다.이제 결과를 알람으로 보내면 된다. 이래저래 고생하다 대충 아래와 같은 형태로 완성했다.PUT _xpack/watcher/watch/outofmemoryerror { "trigger" : { "schedule" : { "cron" : "0 0/4 * * * ?" } }, "input" : { "search" : { "request" : { "indices" : [ "", "" ], "body" : { "query" : { "bool" : { "must" : { "multi_match": { "query": "OutOfMemoryError", "fields": ["message", "log"] } }, "filter" : { "range": { "@timestamp": { "from": "{{ctx.trigger.scheduled_time}}||-5m", "to": "{{ctx.trigger.triggered_time}}" } } } } }, "sort" : [ { "@timestamp" : {"order" : "desc"}}, "_score" ] } } } }, "condition" : { "compare" : { "ctx.payload.hits.total" : { "gt" : 0 }} }, "actions" : { "notify-slack" : { "throttle_period" : "5m", "slack" : { "message" : { "to" : [ "#ops", "@dev" ], "text" : "로그 모니터링 알람", "attachments" : [ { "title" : "OutOfMemoryError", "text" : "지난 5분 동안 해당 오류가 {{ctx.payload.hits.total}}회 발생했습니다. 가장 최근의 오류는 다음과 같습니다.", "color" : "warning" }, { "fields": [ { "title": "환경", "value": "Prod", "short": true }, { "title": "발생시각", "value": "{{ctx.payload.hits.hits.0._source.@timestamp}}", "short": true }, { "title": "메시지", "value": "{{ctx.payload.hits.hits.0._source.message}}", "short": false }, { "title": "확인명령어", "value": "`GET /{{ctx.payload.hits.hits.0._index}}/{{ctx.payload.hits.hits.0._type}}/{{ctx.payload.hits.hits.0._id}}`", "short": false } ], "color" : "warning" } ] } } } } }4분마다 검색 쿼리를 실행해서 최근 5분 간의 레코드를 감시하기 때문에 동일한 오류에 대해 2회 연속으로 알람을 받을 가능성이 있다. X-Pack은 이를 우회할 방법을 제공하지 않는 것 같다. 그래서 쿼리가 발견한 레코드의 인덱스 ID를 Slack 메시지 중 확인명령어 필드에 넣었다. 알람이 두 번 왔지만 인덱스 아이디가 동일하다면 오류가 한번 발생한 것으로 간주하면 된다.참고 문서위의 Alert를 작성하며 도움을 받은 문서는 다음과 같다.Multi Search Template은 검색 쿼리를 짤 때 도움이 됐다.Search Input 문서는 검색 쿼리 또는 검색 결과를 작성할 때 어떤 변수를 사용할 수 있는지 설명한다. 예) {{ctx.payload.hits.hits.0._source.message}}Watcher APIsSlack ActionDate math support in index names 문서는 인덱스 이름을 동적으로 바꾸는 법을 설명한다.기타Elasticsearch Cloud는 기본적으로 이메일 발송을 지원하기 때문에elasticsearch.yml 설정에 xpack.notification.email를 추가하지 않아도 된다. 아니, 추가하면 잘못된 설정이라며 거부한다. Illegal이라고만 하지 이유를 자세히 알려주지 않기 때문에 삽질하기 쉽니다. Invalid addresses라고 오류 로그가 찍히면 이것은 설정 문제가 아니다. 이메일 설정 메뉴로 가서 Watcher Whitelist에 수신 이메일 주소를 등록하면 문제가 해결된다.테스트용 로그 메시지를 Fluentd로 보내고 싶다면 fluent-cat 명령을 이용한다.echo '{"message":"Dummy OutOfMemoryError"}' | fluent-cat kubernetes.logOriginally published at Andromeda Rabbit.#데일리 #데일리호텔 #개발 #개발자 #개발팀 #인사이트
조회수 8284

Node.js로 Amazon DynamoDB 사용하기

DynamoDB 로컬 설정 (다운로드 버전)실제 DynamoDB 웹 서비스에 액세스하지 않고 로컬에서 애플리케이션 작성 및 테스트를 할 수 있음1. 다운로드 링크에서 DynamoDB 무료 다운로드2. 압축 해제 후 해당 디렉터리에서 아래의 명령어로 실행java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb* Ctrl+C로 중지할 수 있고 중지하기 전까지 수신 요청을 처리함* 기본적으로 8000번 포트를 사용Node.js 용 AWS SDK 설치1. 설치npm install aws-sdk2. 실행// app.jsvar AWS = require("aws-sdk");var s3 = new AWS.S3();// 버킷 이름은 모든 S3 사용자에게 고유한 것이어야 합니다.var myBucket = "dynamodb.sample.wonny";var myKey = "myBucketKey";s3.createBucket({ Bucket: myBucket }, function(err, data) {  if (err) {    console.log(err);  } else {    params = { Bucket: myBucket, Key: myKey, Body: "Hello!" };    s3.putObject(params, function(err, data) {      if (err) {        console.log(err);      } else {        console.log("Successfully uploaded data to myBucket/myKey");      }    });  }});node app.js테이블 생성// CreateTable.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var dynamodb = new AWS.DynamoDB();var params = {  TableName: "Movies",  KeySchema: [    { AttributeName: "year", KeyType: "HASH" }, // Partition key    { AttributeName: "title", KeyType: "RANGE" } // Sort key  ],  AttributeDefinitions: [    { AttributeName: "year", AttributeType: "N" },    { AttributeName: "title", AttributeType: "S" }  ],  // 다운로드 버전인 경우 아래 코드 무시  ProvisionedThroughput: {    ReadCapacityUnits: 10,    WriteCapacityUnits: 10  }};dynamodb.createTable(params, function(err, data) {  if (err) {    console.log(      "Unable to create table. Error JSON: ",      JSON.stringify(err, null, 2)    );  } else {    console.log(      "Created table. Table description JSON: ",      JSON.stringify(data, null, 2)    );  }});node CreateTable.js샘플 데이터 로드1. 이곳에서 샘플 데이터 파일 다운로드데이터 형태는 아래와 같음[    {        "year": 2013,        "title": "Rush",        "info": {            "directors": ["Ron Howard"],            "release_date": "2013-09-02T00:00:00Z",            "rating": 8.3,            "genres": [                "Action",                "Biography",                "Drama",                "Sport"            ],            "image_url": "http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg",            "plot": "A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda.",            "rank": 2,            "running_time_secs": 7380,            "actors": [                "Daniel Bruhl",                "Chris Hemsworth",                "Olivia Wilde"            ]        }    },    ...]- year 및 title을 Movies 테이블을 위한 기본 키 속성 값으로 사용- info의 나머지 값들은 info라는 단일 속성에 저장- JSON을 DynamoDB 속성에 저장2. 샘플 데이터 Movies 테이블에 로드// LoadData.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();console.log("Importing movies info DynamoDB. Please wait.");var allMovies = JSON.parse(fs.readFileSync("moviedata.json", "utf8"));allMovies.forEach(function(movie) {  var params = {    TableName: "Moves",    Item: {      year: movie.year,      title: movie.title,      info: movie.info    }  };  docClient.put(params, function(err, data) {    if (err) {      console.error(        "Unable to add movie",        movie.title,        ". Error JSON:",        JSON.stringify(err, null, 2)      );    } else {      console.log("PutItem succeeded:", movie.title);    }  });});node LoadData.js테이블에 항목 추가// PutItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";var params = {  TableName: table,  Item: {    year: year,    title: title,    info: {      plot: "Nothing happens at all.",      rating: 0    }  }};console.log("Adding a new item...");docClient.put(params, function(err, data) {  if (err) {    console.error(      "Unable to add item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("Added item:", JSON.stringify(data, null, 2));  }});node PutItem.js- 기본 키가 필요하므로 기본 키 (year, title) 및 info 속성 추가항목 읽기// GetItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";var params = {  TableName: table,  Key: {    year: year,    title: title  }};docClient.get(params, function(err, data) {  if (err) {    console.error(      "Unable to read item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("GetItem succeeded:", JSON.stringify(data, null, 2));  }});node GetItem.js항목 업데이트// UpdateItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";var params = {  TableName: table,  Key: {    year: year,    title: title  },  UpdateExpression: "set info.rating = :r, info.plot=:p, info.actors=:a",  ExpressionAttributeValues: {    ":r": 5.5,    ":p": "Everything happens all at once.",    ":a": ["Larry", "Moe", "Curly"]  },  ReturnValues: "UPDATED_NEW"};console.log("Updating the item...");docClient.update(params, function(err, data) {  if (err) {    console.error(      "Unable to update item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));  }});node UpdateItem.js- 지정된 항목에 대해 수행하고자 하는 모든 업데이트를 설명하기 위해 UpdateExpression을 사용- ReturnValues 파라미터는 DynamoDB에게 업데이트된 속성("UPDATED_NEW")만 반환하도록 지시원자성 카운터 증가시키기update 메서드를 사용하여 다른 쓰기 요청을 방해하지 않으면서 기존 속성의 값을 증가시키거나 감소시킬 수 있음 (모든 쓰기 요청은 수신된 순서대로 적용)실행 시 rating 속성이 1씩 증가하는 프로그램// Increment.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";// Increment an atomic countervar params = {  TableName: table,  Key: {    year: year,    title: title  },  UpdateExpression: "set info.rating = info.rating + :val",  ExpressionAttributeValues: {    ":val": 1  },  ReturnValues: "UPDATED_NEW"};console.log("Updating the item...");docClient.update(params, function(err, data) {  if (err) {    console.error(      "Unable to update item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));  }});node Increment.js항목 업데이트(조건부)UpdateItem을 조건과 함께 사용하는 방법조건이 true로 평가되면 업데이트가 성공하지만 그렇지 않으면 수행되지 않음// ConditionalUpdateItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";// Increment an atomic countervar params = {  TableName: table,  Key: {    year: year,    title: title  },   UpdateExpression: "remove info.actors[0]",  ConditionExpression: "size(info.actors) > :num",  ExpressionAttributeValues: {    ":num": 3  },  ReturnValues: "UPDATED_NEW"};console.log("Attempting a conditional update...");docClient.update(params, function(err, data) {  if (err) {    console.error(      "Unable to update item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));  }});node ConditionalUpdateItem.js다음과 같이 작성하면 아래와 같은 에러 메시지가 표시 됨The conditional request failed"영화에는 3명의 배우가 있는데 배우가 3명보다 많은지를 확인하고 있어 에러가 발생다음과 같이 수정하면 정상적으로 항목이 업데이트 됨ConditionExpression: "size(info.actors) >= :num",항목 삭제// DeleteItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";var params = {  TableName: table,  Key: {    year: year,    title: title  },  ConditionExpression: "info.rating <= :val",  ExpressionAttributeValues: {    ":val": 5.0  }};console.log("Attempting a conditional delete...");docClient.delete(params, function(err, data) {  if (err) {    console.error(      "Unable to update item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("DeleteItem succeeded:", JSON.stringify(data, null, 2));  }});node DeleteItem.js다음과 같이 작성하면 아래와 같은 에러 메시지가 표시 됨The conditional request failed특정 영화에 대한 평점이 5보다 크기 때문에 에러가 발생다음과 같이 수정하면 정상적으로 항목이 삭제 됨var params = {  TableName: table,  Key: {    year: year,    title: title  }};데이터 쿼리- 파티션 키 값을 지정해야 하며, 정렬 키는 선택 사항- 1년 동안 개봉한 모든 영화를 찾으려면 year만 지정, title을 입력하면 2014년 개봉된 "A"로 시작하는 영화를 검색하는 것과 같이 정렬 키에 대한 어떤 조건을 바탕으로 일부 영화를 검색할 수도 있음한 해 동안 개봉한 모든 영화// QueryYear.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var params = {  TableName: "Movies",  KeyConditionExpression: "#yr = :yyyy",  ExpressionAttributeNames: {    "#yr": "year"  },  ExpressionAttributeValues: {    ":yyyy": 1985  }};docClient.query(params, function(err, data) {  if (err) {    console.error("Unable to query. Error JSON:", JSON.stringify(err, null, 2));  } else {    console.log("Query succeeded.");    data.Items.forEach(function(item) {      console.log(" -", item.year + ": " + item.title);    });  }});node QueryYear.jsExpressionAttributeNames는 이름을 교체함. 이를 사용하는 이유는 year가 DynamoDB에서 예약어이기 때문. KeyConditionExpression을 포함해 어떤 표현식에서도 사용할 수 없으므로 표현식 속성 이름인 #yr을 사용하여 이를 지칭ExpressionAttributeValues는 값을 교체함. 이를 사용하는 이유는 KeyConditionExpresssion을 포함해 어떤 표현식에서도 리터럴을 사용할 수 없기 때문. 표현식 속성 값인 :yyyy를 사용해 지칭* 위의 프로그램은 기본 키 속성으로 테이블을 쿼리하는 방법. DynamoDB에서 1개 이상의 보조 인덱스를 테이블에 생성하여 그 인덱스로 테이블을 쿼리하는 것과 동일한 방식으로 쿼리 작업 가능. 보조 인덱스는 키가 아닌 속성에 대한 쿼리를 허용하여 애플리케이션에 더 많은 유연성을 부여함한 해 동안 개봉한 모든ㄴ 영화 중에 특정 제목을 지닌 영화year 1992에 개봉한 영화 중에 title이 "A"부터 "L"까지의 알파벳으로 시작하는 영화를 모두 조회합니다.// QueryTitle.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();console.log(  "Querying for movies from 1992 - titles A-L, with genres and lead actor");var params = {  TableName: "Movies",  ProjectionExpression: "#yr, title, info.genres, info.actors[0]",  KeyConditionExpression: "#yr = :yyyy and title between :letter1 and :letter2",  ExpressionAttributeNames: {    "#yr": "year"  },  ExpressionAttributeValues: {    ":yyyy": 1992,    ":letter1": "A",    ":letter2": "L"  }};docClient.query(params, function(err, data) {  if (err) {    console.error("Unable to query. Error JSON:", JSON.stringify(err, null, 2));  } else {    console.log("Query succeeded.");    data.Items.forEach(function(item) {      console.log(        " -",        item.year + ": " + item.title + " ... " + item.info.genres + " ... ",        item.info.actors[0]      );    });  }});node QueryTtiel.js스캔테이블의 모든 항목을 읽고 테이블의 모든 데이터를 반환선택 사항인 filter_expression을 제공할 수 있으며 그 결과 기준이 일치하는 항목만 반환하지만 필터는 테이블 전체를 스캔한 후에만 적용됨// Scan.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var params = {  TableName: "Movies",  ProjectionExpression: "#yr, title, info.rating",  FilterExpression: "#yr between :start_yr and :end_yr",  ExpressionAttributeNames: {    "#yr": "year"  },  ExpressionAttributeValues: {    ":start_yr": 1950,    ":end_yr": 1959  }};console.log("Scanning Movies table.");docClient.scan(params, onScan);function onScan(err, data) {  if (err) {    console.error(      "Unable to scan the table. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    // print all the movies    console.log("Scan succeeded.");    data.Items.forEach(function(movie) {      console.log(        movie.year + ": ",        movie.title,        "- rating:",        movie.info.rating      );    });    // continue scanning if we have more movies, because    // scan can retrieve a maximum of 1MB of data    if (typeof data.LastEvaluatedKey != "undefined") {      console.log("Scanning for more...");      params.ExclusiveStartKey = data.LastEvaluatedKey;      docClient.scan(params, onScan);    }  }}node Scan.jsProjectionExpression은 스캔 결과에서 원하는 속성만 지정FilterExpression은 조건을 만족하는 항목만 반환하도록 조건을 지정. 다른 항목들은 모두 무시됨테이블 삭제// DeleteTable.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var dynamodb = new AWS.DynamoDB();var params = {  TableName: "Movies"};dynamodb.deleteTable(params, function(err, data) {  if (err) {    console.error(      "Unable to delete table. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log(      "Deleted table. Table description JSON:",      JSON.stringify(data, null, 2)    );  }});node DeleteTable.js#트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유 #데이터베이스 #DB #개발 #AWS #아마존 #NoSQL 
조회수 1494

원하는 정보를 5초 안에 인지할 수 있게 하자

우리나라에서 웹 서비스가 아이디어에서 출발해 출시되기까지 여러 단계를 거치게 되는데 크게는 기획, 디자인, 개발의 3단계를 거치게 된다고 볼 수 있다. 각 단계별로 세분화된 역할들이 있어도 결국은 각각 기획자, 디자이너, 개발자로 분류된다. 어니스트펀드에서는 그들이 제품개발팀을 이루고 있다.어니스트펀드 제품개발팀나는 그중 개발자로 속하고 퍼블리싱 & 프론트 개발을 하고 있다. 퍼블리싱은 디자이너가 그린 디자인된 화면을 웹페이지용 프로그래밍 언어라고 할 수 있는 HTML과 CSS로 웹 문서화하는 것이고, 프론트 개발은 HTML과 CSS로 만들어진 웹문서를 사용자의 의도/목적에 따라 기능이 동작하도록(주로 데이터 입출력, 예를 들자면 네이버 검색창의 자동 완성이나, 네이버 메인의 다음 뉴스 보기 등) 기능을 개발하는 것이다.어니스트펀드에서는 팀원들이 자신의 지식/경험을 공유하는 브런치 글을 돌아가면서 쓰고 있고 나도 함께하기로 결정하였다. 내가 가치 있게 공유할 수 있는 내용이 무엇인지를 고민하면서 나의 과거 경험들을 생각해보았다.나는 2002년 웹 디자인을 시작으로 퍼블리싱 업무를 겸하다 2004년부터 퍼블리싱 업무를 본격적으로 했고 2011년부터 스타트업에 합류하면서 기획 및 프론트 개발까지 제품 개발에 있어서 서버 개발을 제외한 사용자와 접하는 모든 업무를 두루 경험하였다. 보통 디자인 전공자들은 기획파트로 전업하는 경우가 많지만 나는 프로그래밍 언어로 코드를 작성하는 것이 재미있어 기회가 닿을 때마다 업무 영역을 넓혀왔다.따라서 기획과 디자인, 퍼블리싱, 프론트 개발에 이르는 사용자와 접점이 많은 다양한 업무를 해오면서 경험한 것을 바탕으로, 서비스를 구성하고 화면을 개발하는 데 있어 도움이 되는 유용한 내용을 공유하고자 한다.1. 많을 땐 나눠서 해결하자정보가 많다는 것은 정리 정돈할 물건이 많다는 것과 비슷하게 생각할 수 있다. 물건이 목적에 맞게 정리되지 않으면 찾기 어렵고 정리해놓더라도 쉽게 어질러질 수 있다. 정보도 마찬가지로 목적에 맞게 정리가 안되어 있을 때 이해가 어렵게 되고, 이해가 어려워서 이해를 돕기 위한 불필요한 설명이 덧붙여지다보면 더욱 이해하기 어려운 결과를 낳게 된다. 그렇게 되면 결국 설명하는 말만 늘어나고 고객의 이해는 저편에 남게 된다.웹페이지가 뜨는데 1초, 훑어보는데 3초, 원하는 정보를 캐치하는데 5초로 충분해야 한다. 사용자가 원하는 정보를 5초 안에 캐치하지 못할 정보의 양이라면 정보를 나누는 것이 좋다. 2. 제목을 생략하지 말자목적으로 나누어진 정보를 사용자가 빠르게 캐치할 수 있도록 돕는 가장 중요한 요소는 바로 제목이다. 제목은 본문을 다 읽지 않아도 내용을 어느 정도 짐작할 수 있게 한다. 따라서 훒어보는데 3초라는 의미는 한 페이지의 메뉴와 제목을 훑어보는데 필요한 시간이다. 이런 제목의 중요성 때문에 제목은 직관적이어야 하고 되도록 생략하지 말아야 한다. 생략을 할 때는 제목이 없어도 이해가 가능하며, 생략된 제목을 누구나 유추할 수 있을 경우가 아니면 제목의 생략을 피하도록 한다. 위 캡쳐화면은 네이버 메인 콘텐츠의 일부를 캡처한 이미지다. 네이버 메인 중 제목이 생략된 예는 왼쪽 하단 영역인 '주제형 캐스트'뿐이다. 다른 영역들은 '뉴스스탠드', '쇼핑' 등 제목을 생략하지 않고 노출시키고 있다. 메인 페이지처럼 목적이 다양한 페이지일수록 콘텐츠의 성격을 분명히 알 수 있게 하는 제목은 짧은 시간 안에 원하는 정보를 찾는데 도움을 준다.3. 한눈에 중요 정보를 읽을 수 있게 하자그다음으로는 정보의 배치이다. 해당 정보가 발생한 원인, 결과 등 고객이 인지하는 과정에 기반한 그룹으로 나누는 것이 좋다. 정보를 배치할 때는 개별 정보의 중요도 순서와 왼쪽에서 오른쪽, 위에서 아래로 흘러가는 흐름대로 배치고 중간에 역행하는 구성이 없는 것이 좋다. 국내 대형 인터넷 쇼핑몰의 상품 목록을 보면서 위 설명을 이해할 수 있다.정보 배치에 정답이 있는 것은 아니지만 마치 정답이 있는 것처럼 상품, 제목, 할인율, 가격, 현재 판매현황에 이르는 순서대로 나열하고 있다. 이는 선두업체를 따라 흉내 낸 것이 아니라 이와 같은 구성이 인지하기에 용이하기 때문에 모두 이와 같이 구성했다고 생각한다.   4. 어렵지 않게 보이도록 하자서비스에 대한 정보를 전달하고 나서 우리가 기대하는 바는 고객이 서비스를 이해하고 우리 서비스를 이용하게 하는 것이다. 쇼핑몰에서는 주문을 받는 것일 것이고, 어니스트펀드의 경우는 대출이나 투자를 신청하는 경우이다. 서비스를 이용하게 하려면 고객의 정보를 필수적으로 입력을 받아야 한다. 어니스트펀드의 경우는 대출 및 투자에 대한 금융서비스이기 때문에 더욱 많은 정보를 고객에게 요청한다. 고객의 정보를 웹 상에서 입력을 받을 때는 "폼"이라는 일종의 정형화된 웹페이지 구성항목을 이용하게 되는데 이것은 정형화되어있기 때문에 남들과는 다른 개성적인 방식을 이용하기는 어렵다. 금융서비스의 입력 폼이 아주 쉽지는 않다는 것을 고객들은 여러 다른 서비스를 이용하면서 어느 정도 알고 있다. 그러나 고객이 중간에 포기하지 않고 제대로 서비스 이용을 완료할 수 있도록 어렵지 않게 만들어야 하고, 언제나 경쟁사의 서비스를 확인하고 경쟁사보다는 어려워 보이지 않도록 만들어야 한다.5. 순서는 반드시 지키자순서는 여러 가지가 있다. 입력해야 할 항목이 무엇인지를 알려주는 입력항목 및 입력하는 창(=입력 필드), 입력하는데 필요한 도움말, 입력해야 할 항목들을 나열하고 전송/입력완료 버튼까지의 순서가 곧 정보의 순서이다. 이 중 쉽게 놓치는 부분은 첫 입력 필드에서 입력완료 버튼까지의 여정 중에 연관이 없는 링크나 버튼을 추가하는 경우이다. 이 순서는 디자인상으로는 잘 구분되지 않을 수 있지만, 웹코드 상으로는 100% 지켜져야 하는 순서이고 디자인과 웹코드의 순서가 일치하면 가장 좋은 결과이다.'다음'과 '네이버'의 로그인 영역을 비교해보자면 두 포탈 서비스 모두 메인 검색창에서 탭키로 아이디 입력 칸까지 이동할 수 있지만, 아이디 입력 후 비밀번호를 입력하고 로그인 버튼을 누르기까지의 탭키 이동 경로가 다르다. 다음 로그인 화면네이버 로그인 화면다   음 : 아이디 입력 -> 비밀번호 입력 -> 로그인 버튼 -> 로그인 상태 유지 순서로 이동한다.네이버 : 아이디 입력 -> 비밀번호 입력 -> 로그인 상태 유지 -> IP보안 선택여부 -> 로그인이다.탭키로 입력필드를 이동하는 경우가 곧 웹코드상에서의 각 입력 필드의 순서가 되는데, '다음'과 같은 경우는 아이디/비밀번호 입력 후 로그인에 대한 옵션을 키보드로 선택하기 위해서는 로그인 버튼을 지나쳐야 선택할 수 있다. 로그인에 대한 옵션은 로그인 버튼을 선택하기 전에 나오는 것이 더 자연스럽지 않을까? 눈에 보이는 순서도 중요하지만 각 입력필드의 논리적 우선순위를 지키는 것 또한 중요하다.6. 틀린 부분을 즉시 명확하게 알려주자고객이 언제나 우리가 기대한 값을 입력해주지는 않는다. 이 경우 너무너무 명확하게도 오류가 발생한 시점에 오류가 발생한 지점을 알려주는 것이 필요하다. 10개의 입력필드가 있는데 입력완료 버튼을 누르자마자 10개 항목 구구절절이 맞고 틀리고를 알려주는 것보다는, 오류가 발생한 시점에 알려주는 것이 훨씬 인지가 빠르다. 따라서 오류 항목을 보여주어야 하는 곳은 해당 입력필드의 다음이고 전송 버튼이나 후속 작업 이전이 되는 것이다. 위 캡쳐화면은 어니스트펀드에서 대출을 받고자 할 때 이름과 생년월일을 입력하는 부분이다. 필자는 생년월일 부분에 5월 32일이라고 없는 날짜 정보를 넣었고, 이와 같은 입력 실수는 사용자가 실수를 했다는 것을 시스템이 "정확한 정보를 입력해 주세요"라고 즉시 알려주고 있어 사용자가 입력을 실수하지 않도록 돕고 있다. 웹 페이지를 보는 고객들은 아무런 도움 없이 해당 서비스를 이해하고 이용할 수 있어야 한다. 똑같은 정보라고 하더라도 어떤 순서로 어떻게 보여주느냐에 따라서 인지와 인식은 크게 개선될 수 있다. 하물며 정보까지 가공을 하게 되면 더욱 큰 개선을 이끌어 낼 수 있다. 각자가 맡고 있는 서비스에서 5초 안에 고객이 원하는 정보를 웹 페이지 내에서 바로 인지할 수 있는지를 생각해보고 아니다면 테스트해보고 개선해보자.#어니스트펀드 #개발자 #개발팀 #UX개발 #철학 #인사이트

기업문화 엿볼 때, 더팀스

로그인

/