스토리 홈

인터뷰

피드

뉴스

조회수 939

[Buzzvil People] Ben Yoo, Software Developer

 Buzzvil People에서는 다양한 배경과 성격 그리고 생각을 지닌 버즈빌리언들을 한 분 한 분 소개하는 시간을 갖습니다. 어떻게 버즈빌에 최고의 동료들이 모여 최고의 팀을 만들어가고 있는 지 궁금하시다면, 색색깔 다양한 버즈빌리언들 한분 한분의 이야기가 궁금하시다면, Buzzvil People을 주목해주세요.1. 간단한 자기 소개 부탁드립니다.  안녕하세요 저는 버즈빌에서 Server engineering 을 맡고 있는 유병우입니다. 회사에서는 Ben 이라는 닉네임을 쓰고 있고 저와 아내 사이에 아기가 하나 있는데 회사에서는 벤, 벤처, 미니벤이렇게 부르고 있습니다. 성격은 매우 Active 해서 웬만한 스포츠는 다 좋아하고 회사에서는 Rock band도 하고 있습니다. 프로그래머! 어린 시절 Basic 이라는 언어로 시작한 프로그래밍이 너무 재밌기도 했고 가능한 많은 사람들에게 유익을 끼치고 싶다는 생각에 Software Engineer 가 되었습니다. 10년 전 병역특례 시절 카카오톡 이전에 존재했던 m&Talk 이라는 무료 메신저 개발을 시작으로 삼성의 Chat@n, 그리고 Line, Naver 외 여러 앱에 들어가는 push notification platform 을 개발한 경험이 있습니다. 전 세계에서 억 단위가 넘는 유저들에게 서비스하고 그 유저들에게 좋은 경험을 선사하는 것이 저에게 더욱 Software 의 매력에 빠지게 만들었던 것 같습니다. 새로운 기능이나 개선사항을 배포하고 나면 유저들의 Feedback 을 보는 것이 아침에 눈을 뜨면 가장 먼저 하는 일이었습니다. (늘 즐겁기만 한 건 아니었습니다. 특히 버그를 배포한 다음 날엔.. -_-a)  2. 어떻게 버즈빌에 오시게 되셨나요?  Infobank 에서의 인연 Infobank 에서의 병역특례를 하면서 m&Talk이라는 메신저를 개발할 때 Product Team의 Jay 는 iPhone 쪽 개발을 주도하고 있었고 저는 Android 쪽 개발을 주도하고 있었습니다. 함께 하나의 Product 을 만들면서 여러 가지 의견을 주고받기도 했고 서로 부족한 부분을 잘 보완해주는 친구이자 동료라는 생각을 많이 했습니다.  창업을 결심 나중에 Jay가 미국에서 함께 잠금화면 서비스를 만들어보자고 절 찾아왔고 그렇게 해서 Slidejoy 라는 회사를 함께 공동창업하게 되었습니다. 당시 좋은 회사에서 만족하며 생활하고 있었고 한 가정의 가장으로서 불안정한 길을 선택하는 것에 대한 두려움이 있었지만 좋은 사람들과 함께 창업이라는 기회는 자주 오지 않는다는 것과 다음의 단순한 생각이 창업의 길로 저를 이끌었습니다.  “뭐, 굶어 죽지는 않겠지.” 버즈빌로 합병 많은 위기들을 헤쳐나가며 Slidejoy 는 계속 성장했고 좋은 기회에 한국에서 비슷한 서비스를 하고 있던 저희보다 규모가 큰 회사인 버즈빌로 합병을 하게 되었습니다.  3. 버즈빌에서 어떤 업무를 담당하고 계신가요?  신기술 & Refactoring  제가 Software 를 개발하면서 가장 중요하게 생각하는 것은 효율 / 훌륭한 Design 을 가지고 있는 프로젝트 설계인데요, 효율을 올리기 위해 Go 와 Kubernetes 등의 기술을 회사에 도입했고 MVP, MVC 와 같은 Design pattern 들을 도입해서 코드를 읽기 쉽고 서로 분리하고 재사용 가능한 구조로 만드는 것에 노력 중입니다.    Go server engineering 실제 업무는 BuzzScreen / HoneyScreen 에서 광고 및 콘텐츠 할당과 Slidejoy 라는 서비스의 API 서버 개발을 맡고 있으며 Slidejoy 클라이언트를 개발했어서 클라이언트 쪽도 조금씩 참여하고 있습니다. 새로운 기술에 관심이 많다 보니 BuzzScreen 과 HoneyScreen 할당 로직을 전부 Go 언어로 포팅했고 비약적인 성능 향상이 있었습니다. (Go 서버 개발하기)  4. 스타트업에서 혹은 광고업계에서 일하는 느낌이 어떠세요?  사람 > 회사 대기업에서의 경험과 다르게 스타트업에서는 한 사람 한 사람이 일당백인 경우가 많은 것 같습니다. 그리고 그런 한 사람에 의해서 회사가 좌지우지 할 수 있는 곳이 스타트업입니다. 회사가 겪는 크고 작은 성장과 위기 모두 그대로 직원들에게 전달 되다 보니 그만큼 Buzzvil 식구들 모두 함께 만들어가는 서비스의 성공에 초점을 맞출 수 있습니다.  모바일 광고 저는 사실 미디어에 큰 흥미가 없고 광고는 더더욱 관심이 없었습니다. 하지만 Mobile 이라는 Big wave 안에서 0에서 출발해서 수억 명이 사용하게 된 급속도로 성장하는 Messenger 를 개발을 몸으로 체험할 수 있었고 모바일 광고 역시 Buzzvil 을 성장시킨 Big wave 였다고 생각합니다. 이렇게 급속도로 변하고 성장하는 시장에서 스타트업에 분명히 가치를 계산할 수 없는 엄청난 기회가 있다고 생각합니다.  5. 이것만큼은 버즈빌이 참 좋다! 어떤 게 있으실까요?  밝고 명랑한 문화 회사 회식 중에서 저는 “친해지길 바래” 라는 테마를 정말 좋아하는데요. 그야말로 정해진 예산 안에서 소수의 사람들끼리 마음껏 놀 수 있습니다. 지난번 친해지길 바래 때는 간단히 막국수 먹고 그 외의 모든 예산을 사격 및 방탈출 등의 액티비티에 쏟아부었습니다. 회식 날 밤에 배가 고픈 건 태어나서 처음이었던 것 같아요. 올해 초에 다녀왔던 전 직원들과 함께 다녀온 Bali 에서의 워크숍도 빠질 수 없습니다. 워낙 서로 친하게 지내다 보니 밤잠을 아껴가며 놀았던 기억이 납니다. 휴양지를 다녀왔는데 한국 돌아와서 1~2주 체력적으로 정말 힘들었던 기억이 나네요. 어느 Slack 채널에서나 난무하는 아재개그와 어처구니없는 3행시, 직원들의 표정이 담긴 얼굴로 만든 이모티콘 등 직원들 사이에서 주고받는 대화에는 늘 위트가 넘칩니다. 다크할거야! 라고 생각할 틈을 주지 않습니다. 비록 웃기지 않더라도 응원해줍니다. 노력은 언젠가 결실을 맺을 것이라 기대하기 때문이죠. 같이 놀고 같이 공부하는 회사 마음껏 교육이나 운동을 할 수 있도록 지원해주는 프로그램이나 무제한 도서구매를 지원하고 다양한 주제의 동아리나 스터디 모임 등이 있고 이걸 회사 차원에서 장려하는 것이 빼놓을 수 없는 Buzzvil 의 특징인 것 같습니다. 머신러닝, 영어스터디, 통기타 등의 스터디 모임과 밴드, 축구, 배드민턴, 테니스, 필라테스 등의 동아리 모임 등 대부분 직원들이 하나 이상의 프로그램에 참여하고 있습니다.  6. 개인적인 목표나 꿈이 있으신가요? 있다면, 버즈빌에서의 경험이 어떻게 도움이 된다고 생각하시나요?  많은 사람들에게 편리함을 제공 잠금화면이라는 대부분 사람들이 기존에 크게 활용되지 않고 있던 공간에 Value 를 만드는 것이 버즈빌에서 더 열심히 프로그램을 개발하게 만드는 원동력입니다. 위에도 기술 했지만 저는 가능한 많은 사람들에게 유익을 끼치고 싶어서 Programming 을 하게 되었고 대부분의 다른 산업과 달리 제가 하는 개발 작업은 하나의 복제품을 생성하는데 Ctrl+C / Ctrl+V 만으로 충분하니까 좋은 제품을 만들면 더욱 발전돼서 긍정적인 영향을 더 널리 끼칠 수 있을 것 같습니다.  다른 개발자들이 읽기 쉬운 코드 실제 제가 일을 하면 할수록 기존의 코드를 구조화하고 모듈화하고 사용하지 않는 코드를 지우는 일에 열심을 가지고 있다는 사실을 알게 되었어요. 확장이나 활용이 가능한 Core 나 Library 쪽 개발을 주로 하면서 어떻게 짜면 제 코드를 사용하는 사람이 덜 혼란스럽고 잘 활용할 수 있는지와 어느 곳에 어떤 설계가 어울리는지도 많이 고민해왔던 것 같습니다 버즈빌에서 버즈스크린이라는 상품을 통해서 저의 이런 성향을 마음껏 발휘하고 있습니다. 여러 Publisher 가 쉽게 사용할 수 있어야 하고 SDK 등을 사용할 때 쉽게 Integration 되어야 하기 때문이죠. ‘내가 짠 코드를 인수인계 받을 사람이 연쇄살인범이고 그 사람은 너의 주소를 알고 있다고 생각하고 코딩하라.’ 라는 말이 있는데요. 누구에게도 부끄럽지 않은 코드를 짜려고 항상 노력합니다. 갈 길이 아직 멀지만 연쇄살인범이라도, 어떻게 이렇게 코드를 (잘?) 설계했는지 의논하러 오게 만드는 것이 저의 꿈입니다.     *고성장 스타트업 버즈빌의 채용공고(전문연구요원 포함)를 확인하고 싶으면 아래 버튼을 눌러주세요!
조회수 3185

개발자 커리어 전환기 2 | 3시간 만의 퇴사 결정, 비전공자로 개발에 뛰어들다.

Q) 안녕하세요 Juan Carlos(환 까를로스)님 자기소개 부탁드려요.네 안녕하세요. 지금 immersive 6기에서 개발자가 되기 위해 열심히 공부하고 있는 환 까를로스라고 합니다. 어쩌다보니 immersive 6기에서 전문 네비게이터로 생활하고 있어요.(웃음) 네비게이터는 페어프로그래밍을 할 때 드라이버가 코딩을 할 수 있도록 큰 그림을 그려주는 거라고 생각하시면 되요. 페어와 같이 코딩을 하면서 Immersive를 헤쳐나가고 있습니다.Q) 코드스테이츠 오시기 전에는 어떤 일을 하셨었나요?해외영업을 했습니다. 이 일을 선택한 이유는 조금 특별해요. 제가 취준생이었을 때 회사를 여러 곳을 지원을 했었습니다. 지원한 기업에서 합격 통보를 받았죠. 근데 막상 그 기업에 입사하려고 보니까 지방에서 근무를 해야 하는 거예요. 그전까지는 이런 것들을 생각도 안 하고 있다가, 막상 닥치니까 곰곰이 생각하게 되었어요.'내가 서울을 떠나서 잘 살 수 있을까?' 지방에서 산다는 거에 대해서 크게 생각하고 있지 않았었는데, 막상 닥치니까 고민이 많이 되더라구요. 제가 서울 토박이인데, 고향을 떠나서 사는 거는 제가 너무 힘들 것 같아서 포기하고 지금 현 직장(지금은 퇴사를 했죠)에 다니게 된 거예요. 그리고 제가 공대 출신인데 공대 출신이 서울에서 직장을 잡으려면 영업 밖에 없더라구요. 그래서 영업직을 선택했었습니다.Q) 그럼 직장을 나오게 된 계기가 있으신가요?새로운 것을 수용할 생각이 없는 경직된 조직문화가 너무 안 맞았어요. 저는 신입을 뽑는 이유는 조직이 시장의 흐름이나 세대의 변화에 맞춰 변하기 위해서라고 생각해요. 근데, 전에 팀은 변할 생각을 안 하더라고요. 야근까지 해가면서 업무개선을 해도 기존 방식을 고수하자는 피드백이 계속되니 열정이 사라지는 것을 느꼈죠. 제가 4년 정도 다녔는데, 퇴사를 고민하고 3시간 만에 결정하고 사표를 내고 나왔어요.저는 뭔가 다양한 경험을 하고 제 스스로가 발전하는 걸 좋아하는데, 발전한다는 느낌이 없었죠. 부서를 여러 곳으로 옮긴 이유도 제가 정확히 뭘 좋아하는지 모르니까 이것저것 해보면 알지 않을까 생각했어요. 영업 파트에서 일하면서도 기획부터 경영지원까지 다양한 일을 맡았었죠.Q) 3시간이면 정말 짧네요! 보통은 여러 번 고민하기 마련인데요. 그럼 퇴사하시고 나서는 무엇을 하셨나요?음... 사실 퇴사하고 나서 제가 맡았던 고객들이 경쟁사로 이직할 수 있게 도와주겠다고 하셔서 고민을 많이 했어요.  근데, 이왕 퇴사했는데 새로운 걸 해보고 싶었어요. 한 군데 계속 있으면 뭐랄까.. 나태해지는 것 같아서요.- 다른 분야의 직장을 잡으신 건가요?일단은 여행 가야지라고 생각해서, 스페인으로 떠났어요.  첫 번째로는 스페인의 순례길을 가기로 했죠. 1000km 정도 되는 길을 걸었던 것 같아요. 순례길을 걸으면서 다양한 사람들을 만나고 생각도 정리도 좀 하고 그랬어요. 거기에는 전 세계 퇴사한 사람이 다 모이는 것 같아요. 숙소에서 만난 친구들에게 물어보면 죄다 회사를 퇴사하고 왔다고 하더라구요(웃음) 그리고 그곳에서 개발자가 돼야겠다는 마음을 먹었습니다.Q) 어떤 경험을 하셨길래 그곳에서 개발자가 돼야겠단 마음을 먹으셨나요?먼저 이 얘기를 해야 하겠네요. 사실 제가 여행경비가 이렇게 많이 들지 몰랐어요. 순례길을 여행하다가 돈도 떨어져 가는데 직업이 있는 채로 순례길을 도는 사람들을 만나게 된 거예요. 세 명을 만났는데, 세 명 다 소프트웨어 엔지니어였습니다. 처음에는 브라질 개발자를 만났어요. 그때까지만 해도 별생각이 없었죠. 다음으로는 러시아 개발자를 만났습니다. 러시아 개발자 친구를 보면서 아 이런 게 디지털 노마드구나라는 생각을 갖게 되었죠. 그리고 마지막으로 스페인 개발자 친구를 만나니까 정말 개발자라는 직업이 부럽게 느껴지더라구요. Q) 디지털 노마드를 보고 개발자가 돼야겠단 결정을 하신 거네요! 그럼 코드스테이츠를 선택해주신 이유가 있으신가요? 아까 제가 생각보다 여행 경비가 많이 드는지 몰랐다고 했잖아요. 순례길만 여행하는데도 여행 경비가 다 떨어진거에요(웃음) 그래서 어쩔 수 없이 세계 여행의 꿈을 접고 한국으로 오게 되었죠. 그리고 한국으로 돌아오는 비행기 안에서 개발자가 되기로 결심을 했습니다. 내가 여행을 다니고 하고 싶은 것을 하면서도 일도 하고 그게 너무 좋아 보이는 거에요. 물론 한국의 현실은 많이 다르겠지만 그래도 개발자라면 가능하지 않을까라고 생각을 했습니다. 그리고 그 비행기에서 핸드폰으로 코딩 관련해서 검색을 하다가 코드스테이츠를 알게 되었어요. 알아보니까 교육철학도 좋고 저에게도 괜찮은 방식을 것 같아서 그 비행기 안에서 바로 결정을 하게 되었습니다. 퇴사할 때와 마찬가지로 일사천리로 결정을 했습니다.- 비행기 안에서 모든 결정이 이루어졌네요! 3시간 만에 퇴사를 결정하신 것 같이요!뭐 망설일 이유가 있나요. 자신감과 결단력 그게 제 장점이니까요(웃음)Q) 그럼 이제 Immersive 얘기를 해볼게요. Immersive에서의 생활은 어떠세요?생각했던 것보다 여유가 있어서 좋아요. 그전에는 되게 불안하고 빡빡하고 그럴 것 같은데 막상 해보니까 할만하더라고요. 그리고 일단 사람들이 너무 좋아요. 같이 지내는 사람들이 좋으니까 Immersive도 할만한 것 같아요.Q) 그러면 지금 Immersive에서는 어떤 것을 배우고 있나요?서버를 배우고 있어요. 프론트 쪽 하구요. 프로젝트를 하고 적용을 해봐야 완전히 내 것으로 만들 수 있을 것 같아요. 역시 직접 적용을 해봐야 정확히 알 수 있을 것 같습니다.서버를 배우고 있어요. 프론트 쪽 하구요. 자바스크립트라는 언어의 다양한 문법을 매일 체험해보고 있어서, 매일매일이 새롭습니다. (뭔가 이해할 만 하면 다른걸 배워서..) 프로젝트를 해봐야 완전히 내것으로 만들 수 있을 것 같아요.Q) 앞으로 어떤 개발자가 되고 싶으세요?거창하게 세상을 바꾸는 개발자! 이런 건 제 스타일은 아니에요(웃음) 저는 제가 하고 싶은 것을 하는 개발자. 만들고 싶은 것을 만드는 개발자가 되고 싶어요. 세상을 바꾸는 개발자도 내가 좋아하는 것, 내가 하고 싶은 것, 내가 만들고 싶은 것을 만드는 개발자가 되었을 때 가능하지 않을까요?Q) 프로젝트를 곧 하게 될 텐데 어떤 프로젝트를 하고 싶으신가요?제 경험에 기반한 프로젝트에요. 우리는 회사에서 주는 돈 그냥 받잖아요. 제가 회사를 나오고 받았던 돈들을 확인해보니 제대로 받지 못했다는 것을 알았어요. 그래서 사람들이 노동의 정당한 보상을 알고 받을 수 있도록 도와주는 프로그램을 만들고 싶어요. 주변만 봐도 대부분의 사람들이 이런 문제로 인해 문제를 가지고 있다고 생각해요.Q) 1년 후에 개발자가 되었다고 생각하면 어떤 모습일까요?개발자가 될 수 있을까요?(웃음) 아마 1년 후엔 야근에 쩔어있지 않을까요? 저는 이게 내 일이다라는 생각을 하면 엄청 파고드는 스타일이거든요. 개발자로 처음 들어간 직장에 남아 있거나 이직을 하고 있을 것 같아요. 사실 저는 계획을 잘 안 세우거든요. 그러니까 아무 준비 없이 퇴사하고 개발을 배우고 있죠. 설마 굶어죽기야 하겠어요?Q) 마지막으로 하고 싶은 말이 있나요?제가 퇴사하면서 방 정리도 같이 하게 됐어요. 정리를 하다 보니까 우연찮게 제 학창시절 생활기록부를 보게 되었습니다. 생활기록부에 장래희망을 적는 칸이 있잖아요. 근데 제가 깜짝 놀란 게 거기에 중학교 때부터 고등학교 때까지 줄곧 프로그래머로 적혀있던 거에요. 그동안 까맣게 잊고 살았는데 신기했어요.그리고 또 생각을 해보니까 대학교 때도 제가 컴공과는 아니지만 공대라서 C++을 해야했는데 그 과목에서 처음으로 A+을 받은 기억이 나더라구요. 이런 생각이 들면서 결국 나는 프로그래머를 선택할 운명이었나? 이런 생각도 들고. 결국에는 돌아돌아 이 길로 온 것 같아요. 그래도 돌아왔다고 해서 늦었다거나 아쉽지는 않아요. 제가 지금까지 걸어온 길이 분명히 프로그래밍을 하는데 도움이 된다고 생각하고 있으니까요.네 지금까지 환 까를로스님과의 인터뷰를 진행했었는데요. 정말 비하인드스토리가 엄청나네요. Immersive 성공적으로 수료하시고 원하시는 개발자가 되기를 바랍니다. 앞으로도 다양한 스토리를 가진 Immersive 수강생분들의 이야기로 찾아뵙겠습니다.
조회수 1793

나는 이쁜 데일리룩을 보고 싶은걸? \w pose estimation

안녕하세요. 스타일쉐어 백엔드 개발자 김동현입니다.2018년의 스타일쉐어에서는 뷰티, 중고 그리고 데일리룩이라는 피드가 추가로 등장했는데요, 그중 제가 작업했던 데일리룩 피드를 만들게 된 배경과 개발 방향에 대해 공유드리고자 합니다.스타일쉐어 데일리룩#데일리룩 #ootd / 타자 치는 것은 귀찮아데일리룩에 관련된 스타일들만 뽑아내는 방법 중에 가장 간단한 방법은 텍스트로 분리해내는 방법이었을 것입니다.하지만 #데일리룩 #ootd는 사진이나 내용이 관계가 없더라도 들어가 있는 경우가 많았습니다.또한 위의 피드처럼 정성스러운 글을 써주는 유저도 많긴 했지만 자신의 데일리 로그를 남기면서 글을 작성하지 않는 경우도 더러 있었습니다.즉, 단순히 텍스트로만 구별해내기에는 이미지에 대한 질을 확신할 수 없었고, 텍스트가 주된 서비스가 아니다 보니 설명 없는 좋은 이미지들이 많았는데요.우리는 이 이미지들을 놓치고 싶지 않았습니다.그래서 결과적으로 텍스트 대신 이미지를 사용하는 방향을 선택하게 되었습니다.이미지로 어떻게 구별해낼까?다행히도 R-CNN의 높은 인식률과 Pre-Trained 된 모델의 label 중 person이 이미 학습되어있던 터라 별도의 Transfer Learning 없이 이미지 내에서 body parts가 있는지 없는지 찾아내는 것은 아주 어렵지 않았습니다.다만 문제가 있다면 body parts에 들어가는 모든 부분을 person이라고 예측하던 부분이었죠.예를 들자면 아래와 같습니다.다음과 같이 제가 사용한 모델에서는 body parts를 person이라는 라벨로 처리하고 있었습니다.단순히 R-CNN의 person 라벨만을 믿기에는 의도했던 데일리룩 외에도 너무나도 많은 것들이 데일리룩이라는 이름으로 필터링될 것 같았습니다.그래서 또 다른 필터가 하나 더 필요하다는 생각이 들었습니다.Pose EstimationBody Parts 중 우리가 원하는 부분이 사진에 있으면 좋겠다!라는 생각을 곰곰이 하다 보니 우연히 머릿속에 스쳐 지나가는 하나의 장면이 있었습니다.Source: http://graphics.berkeley.edu/papers/Kirk-SPE-2005-06/바로 3D 모델링 중에서 Motion Tracker 에 관련된 장면이었는데요. 이것을 Tracker가 아니라 이미지에서 stick figure를 뽑아낼 수 있으면 되지 않을까?라는 생각이 들었습니다.놀라운 딥러닝의 세계에는 이미 여러 명의 Stick Figure를 뽑아낼 수 있는 경지에 도달해 있었습니다.Source: https://github.com/ZheC/Realtime_Multi-Person_Pose_EstimationPose Estimation 딥러닝 모델을 사용하여 아래와 같은 결과물을 얻어낼 수 있었는데요.이미지 내의 Body Parts의 존재 여부를 알게 되었으니 우리가 원하는 Body Parts가 이미지 내에 있는지 검사할 수 있게 되었습니다.하지만 해당 모델이 마냥 가볍지는 않았기에 사용자의 업로드가 많은 순간에는 예측 Task가 밀리기 시작했습니다.그래서 아주 단순하지만, 효과적인 아이디어들을 적용하였는데요.pose estimation을 하기 전에 R-CNN을 돌린 후 person으로 예측된 bounding box가 있다면 pose estimation 모델을 돌리도록 했습니다.하지만 위의 필터를 통했음에도 원하는 결과물이 안 나오는 경우가 종종 있었는데요.바로 다음과 같은 경우입니다.생각보다 작은 사람의 stick figure도 잘 추출 내어서 해수욕장으로 떠나 찍은 사진 속의 저 멀리 있는 휴양객을 데일리룩으로 잡는 일이 종종 발생했거든요.그래서 위의 조건에 더불어서 person이라고 예측된 bounding box size가 전체 이미지 크기 대비 n % 이상의 크기 일 경우 Pose Estimation을 진행하자는 것이었죠.적당한 크기 이상의 데일리룩들을 뽑아내고 싶었고 사람이 너무 작아서 안 보이는 경우도 피할 수 있었습니다.빠른 분류 속도는 덤이었고요.덕분에 유저들이 올린 콘텐츠 중 데일리룩이라는 범주에 속하는 콘텐츠를 잘 뽑아낼 수 있었습니다.아래는 위의 과정을 거쳐서 Pose Estimation까지 처리되어 데일리룩 사진이라고 판별된 이미지입니다.이다음으론 무엇을 더 해볼 수 있을까요?사진 속의 자세를 알 수 있게 되었으니 좀 더 재밌는 것을 할 수 있을 것 같은데요.예를 들면 K-Means를 적용하면 비슷한 모습의 데일리룩들만 모아볼 수도 있고 스타일쉐어 유저들이 자주 찍는 자세 라던가 유저 별 자세 선호도 등등 재밌는 것들을 할 수 있을 것 같습니다.날 따라 해 봐요 같은 것도 해볼 수 있겠네요 :)같이 해보지 않을래요?아직도 재밌는 것들이 많이 남은 스타일쉐어 에서는 더 많은 것을 하기 위해 개발자분들을 모시고 있습니다 :)백엔드 개발자라고 해서 백엔드 개발에만 국한되지 않고 하고 싶은 것들을 해도 된다, 할 수 있다고 이야기해 주는 회사라고 생각합니다.스타일쉐어를 좀 더 알고 싶으시다면 여기를 눌러 주세요 :)#스타일쉐어 #개발팀 #개발자 #백엔드개발 #개발인사이트 #경험공유 #후기
조회수 3083

사물인터넷(IOT) 란? 

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

React 공식 튜토리얼 한글 번역

<button type="button">메뉴</button>* 오역 및 오탈자가 있을 수 있습니다. 발견하시면 댓글로 제보해주세요!** 브런치 에디터의 한계로 마크다운 적용이 되지 않아 가독성이 떨어지고 복사 기능이 지원되지 않습니다. 이왕이면 이곳에서 보시기를 권장합니다. >> 가독성 좋은 문서로 보기React 공식 튜토리얼 바로가기시작하기 전에무엇을 구현할 것인가대화형 틱택토 게임을 구현하려고 합니다.원한다면 최종 결과물을 여기에서 확인할 수 있습니다. 아직 코드가 이해되지 않거나 문법이 낯설어도 걱정하지 마세요. 튜토리얼에서 차근차근 틱택토 게임을 구현하는 방법을 배울테니까요.게임을 플레이해보세요. 이동 리스트에 있는 버튼을 클릭하여 클릭한 때로 돌아가고, 그 때로 돌아간 후 보드가 어떻게 보이는지 확인할 수 있습니다.게임에 익숙해지셨다면 탭을 닫으세요. 다음 섹션에서 간단한 템플릿을 가지고 시작할 것입니다.사전 준비HTML과 JavaScript에 익숙할 것으로 생각합니다. 하지만 HTML과 JavaScript를 사용해본 적이 없더라도 튜토리얼을 따를 수 있어야 합니다.JavaScript를 다시 봐야한다면 이 가이드를 추천합니다. 튜토리얼에서 JavaScript의 최신 버전인 ES6의 몇 가지 특징들인 화살표 함수, 클래스, let, const를 사용할 것입니다. Babel REPL을 사용하여 ES6 코드가 어떻게 컴파일되는지 확인해볼 수 있습니다.튜토리얼을 공부하는 방법튜토리얼을 공부하기 위한 두 가지 방법이 있습니다. 브라우저에서 코드를 작성하거나 컴퓨터의 로컬 개발 환경을 설치할 수 있습니다. 편한 방법을 선택하여 공부하시면 됩니다.브라우저에서 코드를 작성하기 원한다면가장 빨리 시작할 수 있습니다!새로운 탭에서 시작 코드를 여세요. 빈 틱택토 필드를 볼 수 있습니다. 튜토리얼에서는 이 코드를 수정하여 진행합니다.다음 섹션인 로컬 개발 환경 설정을 스킵할 수 있습니다. 바로 개요 섹션으로 넘어가세요.사용하던 에디터에서 코드를 작성하기 원한다면다른 방법으로 사용하는 컴퓨터에 프로젝트를 설치할 수 있습니다.이 방법은 필수가 아닌 선택 사항입니다!더 많은 준비 작업이 필요하지만 에디터의 편리함을 누리며 공부할 수 있습니다.만약 이 방법으로 공부하기를 원한다면 필요한 단계들이 있습니다.  1. 설치된 Node.js가 최신 버전인지 확인해보세요.2. 새로운 프로젝트를 생성하기 위해 설치 방법을 따르세요.$ npm install -g create-react-app$ create-react-app my-app3. 새 프로젝트의 src/ 폴더에 있는 모든 파일들을 삭제해주세요. (폴더 안의 내용만 삭제하되 폴더는 삭제하지 마세요)$ cd my-app$ rm -f src/*4. 이 CSS 코드를 src/ 폴더에 index.css 파일로 추가해주세요.5. 이 JS 코드를 src/ 폴더에 index.js 파일로 추가해주세요.6. src/ 폴더에 있는 index.js의 최상단에 아래 세 줄을 추가해주세요.import React from 'react';import ReactDOM from 'react-dom';import './index.css';이제 프로젝트 폴더에서 npm start 명령어를 실행하고 브라우저에서 http://localhost:3000 를 여세요. 빈 틱택토 필드를 볼 수 있습니다.에디터에서 문법 하이라이팅 설정을 하고 싶다면 이 문서를 따르세요.도와주세요! 막히는 부분이 있어요!막히는 부분이 생겼다면 지원하는 커뮤니티를 확인해보세요. 특히 Reactiflux chat은 빠르게 도움을 받을 수 있는 좋은 방법입니다. 어떤 커뮤니티에서도 필요한 대답을 듣지 못했다면 이슈를 제출하세요. 우리가 도와드립니다.다 끝났으면 시작해봅시다!개요React란 무엇인가요?React는 유저 인터페이스 구현을 위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리입니다.React는 여러 종류의 컴포넌트들을 가지고 있지만 우리는 React.Component의 서브클래스를 사용하여 시작할 것입니다.  class ShoppingList extends React.Component {  render() {    return (            Shopping List for {this.props.name}                Instagram         WhatsApp         Oculus                );  }}// Example usage:XML과 비슷한 재밌는 태그들을 사용할 것입니다. 작성한 컴포넌트는 React에게 무엇을 랜더링하고 싶은지 알려줍니다. 그러면 React는 데이터가 변경될 때 올바른 컴포넌트들을 업데이트하고 랜더링합니다.여기에서 ShoppingList는 React 컴포넌트 클래스 혹은 React 컴포넌트 타입입니다. 하나의 컴포넌트는 props라 불리는 파라미터를 사용하고, render 메서드를 통해 표시할 뷰 계층 구조를 반환합니다.render 메서드는 랜더링하길 원하는 내용을 반환하면 React는 그 내용을 가져와 스크린에 랜더링합니다. 특히 render는 랜더링할 간단한 내용인 React 엘리먼트를 반환합니다. 대부분의 React 개발자들은 이 구조를 더 쉽게 작성할 수 있게 해주는 JSX라는 특별한 문법을 사용합니다.라 쓰면 빌드 시 React.createElement('div')로 변환됩니다. 위의 코드는 아래의 코드와 동일합니다.  return React.createElement('div', {className: 'shopping-list'},  React.createElement('h1', /* ... h1 children ... */),  React.createElement('ul', /* ... ul children ... */));전체 코드는 여기에서 볼 수 있습니다.createdElement()에 대해 더 많은 내용이 궁금하다면 API reference 에 자세한 설명이 있습니다. 튜토리얼에서는 createdElement()를 직접적으로 사용하지 않습니다. 대신 JSX를 사용할 것입니다.JSX에서는 중괄호 안에 JavaScript 문법을 사용할 수 있습니다. 각 React 엘리먼트는 변수에 저장하거나 프로그램에 여기저기에 전달할 수 있는 실제 JavaScript 객체입니다.ShoppingList 컴포넌트는 내장된 DOM 컴포넌트만 랜더링하지만  코드를 작성하여 커스텀 React 컴포넌트를 쉽게 구성할 수 있습니다. 각 컴포넌트는 캡슐화되어 독립적으로 동작할 수 있습니다. 이때문에 간단한 컴포넌트들로 복잡한 UI를 구현할 수 있습니다.시작하기시작 코드를 가지고 시작해봅시다.이 코드는 우리가 구현할 틱택토 게임의 틀을 가지고 있습니다. 필요한 스타일들을 준비해두었기 때문에 JavaScript만 신경쓰면 됩니다.세 가지 컴포넌트로 구성되어 있습니다.- Square- Board- GameSquare 컴포넌트는 하나의 <button>을 랜더링합니다. Board 컴포넌트는 9개의 사각형을 랜더링합니다. Game 컴포넌트는 나중에 우리가 채워 넣어야 할 공백이 있는 하나의 보드를 랜더링합니다. 지금 이 컴포넌트들은 아무런 동작도 하지 않습니다.props를 통해 데이터 전달하기본격적으로 시작하기 위해 Board 컴포넌트에서 Square 컴포넌트로 데이터를 전달해봅시다.Board의 renderSquare 메서드에서 Square 컴포넌트 prop에 value 값을 전달하도록 코드를 변경해주세요.  class Board extends React.Component {  renderSquare(i) {    return ;  }value 값을 보여주기 위해 Square 컴포넌트의 render 메서드 안의 코드 {/* TODO */}를 {this.props.value}로 변경해주세요.  class Square extends React.Component {  render() {    return (      <button className="square">        {this.props.value}      </button>    );  }}변경 전:변경 후: 랜더링된 결과에서는 각 사각형 안에 숫자가 위치합니다.지금까지의 코드는 이곳에서 볼 수 있습니다.대화형 컴포넌트클릭 시 "X"로 채워지는 Square 컴포넌트를 만들어봅시다. Square의 render() 함수에서 반환된 버튼 태그를 다음과 같이 변경해주세요.  class Square extends React.Component {  render() {    return (      <button className="square" onClick={() => alert('click')}>        {this.props.value}      </button>    );  }}이제 사각형을 클릭하면 브라우저에서 알럿창이 뜨는걸 확인할 수 있습니다.새로운 JavaScript 문법인 화살표 함수를 사용하였습니다. onClick prop에 함수를 전달하였습니다. onClick={alert('click')} 코드를 작성하고 버튼을 클릭하면 알럿창 대신 경고가 뜨게됩니다.React 컴포넌트는 생성자에서 this.state를 설정하여 상태를 가질 수 있습니다. 상태는 각 컴포넌트마다 가지고 있습니다. 사각형의 현재 value 값을 상태에 저장하고 클릭할 때 바뀌도록 만들어봅시다.먼저 상태를 초기화하기 위해 클래스에 생성자를 추가해주세요.  class Square extends React.Component {  constructor(props) {    super(props);    this.state = {      value: null,    };  }  render() {    return (      <button className="square" onClick={() => alert('click')}>        {this.props.value}      </button>    );  }}JavaScript 클래스에서 서브클래스의 생성자를 정의할 때 super(); 메서드를 명시적으로 호출해줘야 합니다.Square의 render 메서드에서 현재 상태의 value 값을 표시하고 클릭할 때 바뀌도록 수정해주세요.- <button> 태그 안의 this.props.value 를 this.state.value로 변경해주세요.- () => alert() 이벤트 핸들러를 () => this.setState({value: 'X'})로 변경해주세요.<button> 태그는 다음과 같습니다.  class Square extends React.Component {  constructor(props) {    super(props);    this.state = {      value: null,    };  }  render() {    return (      <button className="square" onClick={() => this.setState({value: 'X'})}>        {this.state.value}      </button>    );  }}this.setState가 호출될 때마다 컴포넌트가 업데이트되므로 업데이트된 상태가 전달되어 React가 이를 병합하고 하위 컴포넌트와 함께 다시 랜더링합니다. 컴포넌트가 랜더링될 때 this.state.value는 'X'가 되어 그리드 안에 X가 보이게 됩니다.이제 사각형을 클릭하면 그 안에 X가 표시됩니다.지금까지의 코드는 이곳에서 볼 수 있습니다.개발자 도구크롬과 파이어폭스의 React 개발자 도구 확장 프로그램은 React 컴포넌트 트리를 브라우저의 개발자 도구 안에서 검사할 수 있게 해줍니다.트리 안의 컴포넌트들의 props와 상태를 검사할 수 있습니다.설치 후 페이지에서 검사하길 원하는 컴포넌트를 오른쪽 클릭하고 "Inspect"를 클릭하여 개발자 도구를 열면 오른쪽 마지막 탭에 React 탭이 보입니다.CodePen을 사용하여 이 확장 프로그램을 동작시키고 싶다면 추가적으로 필요한 작업들이 있습니다.1. 로그인 혹은 회원가입을 하고 이메일을 인증받으세요.2. "Fork" 버튼을 클릭하세요.3. "Change View"를 클릭하고 "Debug mode"를 선택하세요.4. 새롭게 열린 탭에서 React 탭이 있는 개발자 도구를 볼 수 있습니다.상태 들어올리기이제 틱택토 게임을 위한 기본 블록들이 있습니다. 하지만 아직 각 Square 컴포넌트 안에 상태들이 캡슐화되어 있습니다. 더 원활하게 동작하는 게임을 만들기 위해 한 플레이어가 게임에서 이겼는지를 확인하고 사각형 안에 X와 O를 번갈아 표시해야 합니다. 누가 게임에서 이겼는지 확인하기 위해 Square 컴포넌트들을 쪼개지 않고 한 장소에서 9개의 사각형의 value 값을 모두 가지고 있어야 합니다.Board가 각 Square의 현재 상태가 무엇인지만 확인해야 한다고 생각할 수도 있습니다. 이 방법은 기술적으로 React에서 가능하기는 하나 코드를 이해하기 어렵고 불안정하고 리팩토링하기 힘들게 만듭니다.각 Square에 상태를 저장하는 대신에 Board 컴포넌트에 이 상태를 저장하는 것이 가장 좋은 방법입니다. 이 Board 컴포넌트는 이전에 각 사각형에 인덱스를 표시한 방법과 동일한 방법으로 무엇을 표시할지 각 Square에게 알릴 수 있습니다.여러 하위 컴포넌트로부터 데이터를 모으거나 두 개의 하위 컴포넌트들이 서로 통신하기를 원한다면 상위 컴포넌트 안으로 상태를 이동시키세요. 상위 컴포넌트는 props를 통해 하위 컴포넌트로 상태를 전달해줄 수 있습니다. 그러면 하위 컴포넌트들은 항상 하위 컴포넌트나 상위 컴포넌트와 동기할 수 있습니다.이와 같이 상태를 상위 컴포넌트로 들어올리는 것은 React 컴포넌트들을 리팩토링할 때 가장 많이 사용하는 방법입니다. 이 기회를 통해 연습해봅시다. Board에 생성자를 추가하고 9개의 사각형과 일치하는 9개의 null을 가진 배열을 포함한 상태로 초기화하세요.  class Board extends React.Component {  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),    };  }  renderSquare(i) {    return ;  }  render() {    const status = 'Next player: X';    return (             {status}                 {this.renderSquare(0)}         {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }}나중에 이것을 다음과 같이 생긴 보드로 채울 예정입니다.  [  'O', null, 'X',  'X', 'X', 'O',  'O', null, null,]현재 Board의 renderSquare 메서드는 다음과 같습니다.    renderSquare(i) {    return ;  }Square에 value prop를 전달하도록 수정하세요.    renderSquare(i) {    return ;  }지금까지의 코드는 이곳에서 볼 수 있습니다.이제 우리는 사각형이 클릭되면 발생할 변경 사항을 구현해야 합니다. Board 컴포넌트는 어떤 사각형이 채워졌는지 저장하고 있습니다. 그렇기 때문에 Square가 Board가 가지고 있는 상태로 업데이트할 방법이 필요합니다. 사각형의 컴포넌트 상태가 각자 정의되고 있기 때문에 Board가 Square의 상태를 가지고올 수 없습니다.보통의 패턴은 사각형이 클릭될 때 호출되는 함수를 Board로부터 Square에 전달하는 것입니다. Board 안의 renderSquare를 다시 변경해봅시다.    renderSquare(i) {    return (              value={this.state.squares[i]}        onClick={() => this.handleClick(i)}      />    );  }가독성을 위해 리턴 안의 요소들을 여러 줄로 나누고, 괄호를 추가하여 JavaScript가 세미콜론 없이 코드를 마무리하도록 했습니다.Board에서 Square로 value와 onClick 두 개의 props를 전달합니다. onClick Square의 rennder에 있는 this.state.value 를 this.props.value로 변경하세요.- Square의 rennder에 있는 this.state.value 를 this.props.value로 변경하세요.- Square의 rennder에 있는 this.setState() 를 this.props.onClick()로 변경하세요.- 더이상 각 Square가 상태를 가지지 않도록 Square에 정의한 constructor를 삭제하세요.모든 변경 사항을 구현한 Square 컴포넌트는 다음과 같습니다.  class Square extends React.Component {  render() {    return (      <button className="square" onClick={() => this.props.onClick()}>        {this.props.value}      </button>    );  }}이제 사각형이 클릭될 때 Board로부터 전달되는 onClick 함수를 호출합니다. 어떤 일이 일어나는지 되짚어봅시다.1. 내장된 DOM <button> 컴포넌트의 onClick prop는 React에게 클릭 이벤트 리스너를 설정하라고 알립니다.2. 버튼이 클릭될 때 React는 Square의 render() 메서드 안에 정의된 onClick 이벤트 핸들러를 호출합니다.3. 이 이벤트 핸들러는 this.props.onClick()을 호출합니다. Square의 props는 Board에서 명시한 것입니다.4. Board는 onClick={() => this.handleClick(i)}을 Square에 전달하고, 호출될 때 Board의 this.handleClick(i)가 동작합니다.5. Board에 있는 handleClick() 메서드는 아직 정의되지 않았으므로 코드는 오류가 발생합니다DOM <button> 엘리멘트의 onClick 속성이 React와는 다른 의미를 가집니다. Square의 onClick prop나 Board의 handleClick 메서드와는 다릅니다. React 애플리케이션에서는 속성에 on* 이름을 사용하고 핸들러 메서드에 handle* 을 사용하여 처리하는 것이 일반적입니다.사각형을 클릭해봅시다. handleClick을 아직 정의하지 않았으로 에러가 발생합니다. Board 클래스에handleClick 메서드를 추가해봅시다.  class Board extends React.Component {  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),    };  }  handleClick(i) {    const squares = this.state.squares.slice();    squares[i] = 'X';    this.setState({squares: squares});  }  renderSquare(i) {    return (              value={this.state.squares[i]}        onClick={() => this.handleClick(i)}      />    );  }  render() {    const status = 'Next player: X';    return (             {status}                 {this.renderSquare(0)}          {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }}지금까지의 코드는 이곳에서 볼 수 있습니다.이미 있는 배열을 수정하는 대신 squares 배열을 복사하기 위해 .slick()를 호출합니다. 왜 immutability이 중요한지 알고 싶다면 이 섹션으로 이동해주세요.이제 사각형을 클릭하여 다시 사각형을 채울 수 있어야 하지만 상태가 각 Square가 아닌 Board 컴포넌트에 저장되어 있어 게임을 계속 구현해나가야 합니다. Board의 상태가 변경될 때마다 Square 컴포넌트들은 자동으로 다시 랜더링됩니다.Square은 더 이상 각 상태를 유지하지 않습니다. 이들은 상위 Board 컴포넌트로부터 데이터를 전달받고, 클릭될 때 알립니다. 우리는 이 제어된 컴포넌트 같은 컴포넌트들을 호출합니다.왜 immutability가 중요할까전의 예제 코드에서 이미 존재하는 배열을 수정하지 않고 변경 사항을 반영하기 위해 squares 배열을 .slice()연산자를 사용하여 복사하였습니다. 이는 무엇을 의미하며 왜 이 컨셉이 중요할까요.mutation을 사용한 데이터 변경  var player = {score: 1, name: 'Jeff'};player.score = 2;// Now player is {score: 2, name: 'Jeff'}mutation을 사용하지 않은 데이터 변경  var player = {score: 1, name: 'Jeff'};var newPlayer = Object.assign({}, player, {score: 2});// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}// Or if you are using object spread syntax proposal, you can write:// var newPlayer = {...player, score: 2};mutation을 사용하지 않더라도(기본 데이터를 변경하여도) 결과적으로는 다를게 없습니다. 하지만 컴포넌트와 전체 애플리케이션의 성능을 향상시키는 장점이 있습니다.쉽게 Undo/Redo와 시간 여행하기immutability는 이 복잡한 기능들을 훨씬 더 쉽게 구현할 수 있게 해줍니다. 예를 들어 이 튜토리얼에서 우리는 게임의 다른 단계들 사이에 시간 여행을 구현할 것입니다. 데이터 변경을 피하면 우리가 이전 버전의 데이터를 계속 참조할 수 있게 해주고 원할 때 변경할 수 있게 해줍니다.변경 사항 트래킹하기변경되는 객체가 변경 사항이 있는지 아는 방법은 변경 사항이 객체로 만들어지기 때문에 복잡합니다. 그러면 이전 버전을 복사하기 위해 전체의 객체 트리를 현재 버전과 비교하고 각 변수와 값들을 비교해야 합니다. 이 과정은 갈수록 복잡해집니다.immutable 객체가 변경 사항이 있는지 아는 방법은 쉬워집니다. 만약 참조되고 있는 객체가 이전과 다르다면 이 객체는 변경된 것입니다. 이게 끝입니다.React에서 언제 다시 랜더링할지 결정하기React에서 immutability의 가장 큰 장점은 간단한 순수 컴포넌트들이 다시 랜더링될 때를 결정하기 쉽다는 점입니다.shouldComponentUpdate()에 대해 더 배우고 싶고 어떻게 순수 컴포넌트들을 성능 최적화 할 수 있는지 알고 싶다면 이 글을 보세요.함수 컴포넌트우리는 생성자를 지웠습니다. 사실 React는 render 메서드만으로 구성된 Square와 같은 컴포넌트 타입을 위해 함수 컴포넌트라 불리는 간단한 문법을 지원합니다. React.Component를 확장한 클래스를 정의하는 것보다 간단하게 props를 가져오고 랜더링 해야할 것을 반환하는 함수를 작성하는 것이 좋습니다.다음과 같은 함수를 사용해 Square 클래스를 변경하세요.  function Square(props) {  return (    <button className="square" onClick={props.onClick}>      {props.value}    </button>  );}여기서는 this.props를 둘 다 props로 바꿔야 합니다. 애플리케이션에 있는 여러 컴포넌트들은 함수 컴포넌트로 구현할 수 있습니다. 함수 컴포넌트는 더 쉽게 작성할 수 있고 React가 더 효율적으로 최적화할 수 있습니다.코드를 깔끔하게 만들면서 onClick={() => props.onClick()}을 onClick={props.onClick}으로 바꿨습니다. 함수를 전달하는 것은 이 코드만으로 분합니다. onClick={props.onClick()}는props.onClick을 호출하기 때문에 동작하지 않습니다.지금까지의 코드는 이곳에서 보실 수 있습니다.변화 가져오기지금 우리의 게임의 단점은 오로지 X만 플레이할 수 있다는 점입니다. 고쳐봅시다.기본적으로 첫 이동을 'X'가 되도록 설정해봅시다. Board 생성자에서 초기 상태를 수정해주세요.  class Board extends React.Component {  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),      xIsNext: true,    };  }이동할 때마다 xIsNext의 불린 값은 바뀌면서 상태에 저장되어야 합니다. Board의 handleClick 함수를xIsNext 값이 바뀔 수 있도록 수정해봅시다.    handleClick(i) {    const squares = this.state.squares.slice();    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      squares: squares,      xIsNext: !this.state.xIsNext,    });  }이제 X와 O가 순서대로 번갈아 나타납니다. 다음에 무엇이 표시될 때 보여주기 위해 Board의 render에서 "status" 텍스트를 바꿔봅시다.    render() {    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    return (      // the rest has not changed변경 사항을 적용한 Board 컴포넌트는 다음과 같습니다.  class Board extends React.Component {  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),      xIsNext: true,    };  }  handleClick(i) {    const squares = this.state.squares.slice();    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      squares: squares,      xIsNext: !this.state.xIsNext,    });  }  renderSquare(i) {    return (              value={this.state.squares[i]}        onClick={() => this.handleClick(i)}      />    );  }  render() {    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    return (             {status}                 {this.renderSquare(0)}          {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }}지금까지의 코드는 이곳에서 볼 수 있습니다.승자 알려주기언제 게임에서 이기는지 표시해봅시다. 파일 맨 하단에 헬퍼 함수를 추가해주세요.  function calculateWinner(squares) {  const lines = [    [0, 1, 2],    [3, 4, 5],    [6, 7, 8],    [0, 3, 6],    [1, 4, 7],    [2, 5, 8],    [0, 4, 8],    [2, 4, 6],  ];  for (let i = 0; i < lines>    const [a, b, c] = lines[i];    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {      return squares[a];    }  }  return null;}Board의 render 함수에서 누가 게임에서 이겼는지 확인할 수 있도록 호출할 수 있습니다. 또 누군가 이겼을 떄 "Winner: [X/O]" 상태 텍스트를 표시할 수 있습니다.Board의 render에서 status를 선언을 수정해주세요.    render() {    const winner = calculateWinner(this.state.squares);    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }    return (      // the rest has not changedBoard에서 handleClick을 일찍 반환하여 이미 누군가 이긴 게임에서 클릭하거나 이미 칠해진 사각형을 클릭하는 경우 무시하도록 변경할 수 있습니다.축하합니다! 틱택토 게임을 완성하셨습니다! 이제 React의 기초를 알았습니다. 여기서 진짜 승자는 여러분입니다.지금까지의 코드는 이곳에서 볼 수 있습니다.히스토리 저장하기보드의 이전 상태로 되돌려 이전 상태가 표시되도록 만들어봅시다. 이동이 있을때마다 새 squares 배열을 만들었습니다. 덕분에 이전 상태의 보드를 쉽게 저장할 수 있습니다.상태에 이와 같은 객체를 저장해봅시다.  history = [  {    squares: [      null, null, null,      null, null, null,      null, null, null,    ]  },  {    squares: [      null, null, null,      null, 'X', null,      null, null, null,    ]  },  // ...]우리는 이동 리스트를 표시하여 응답할 수 있는 더 수준 높은 Game 컴포넌트를 만들고 싶습니다. 그래서 Square 상태를 Board로 들어올린 것처럼 Board의 상태를 Game으로 들어올려 최 상위 레벨에서 필요한 모든 정보를 저장해봅시다.먼저 생성자를 추가해 Game의 초기 상태를 설정해주세요.  class Game extends React.Component {  constructor(props) {    super(props);    this.state = {      history: [{        squares: Array(9).fill(null),      }],      xIsNext: true,    };  }  render() {    return (                                              {/* status */}         {/* TODO */}                );  }}그 다음 Board를 수정하여 props를 거쳐 squares를 가져오고 이전에 Square에서 했던 것처럼 Game에서 지정한 onClick prop를 만들어줍시다. 각 사각형의 위치를 클릭 핸들러로 전달하여 어떤 사각형이 클릭되었는지 알 수 있습니다. 필요한 변경 사항은 다음과 같습니다.- Board의 constructor를 삭제하세요.- Board의 renderSquare에 있는 this.state.squares[i]를 this.props.sqaures[i]로 대체하세요.- Board의 renderSquare에 있는 this.handleClick(i)를 this.props.onClick(i)로 대체하세요.변경 사항을 반영한 Board 컴포넌트는 다음과 같습니다.  class Board extends React.Component {  handleClick(i) {    const squares = this.state.squares.slice();    if (calculateWinner(squares) || squares[i]) {      return;    }    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      squares: squares,      xIsNext: !this.state.xIsNext,    });  }  renderSquare(i) {    return (              value={this.props.squares[i]}        onClick={() => this.props.onClick(i)}      />    );  }  render() {    const winner = calculateWinner(this.state.squares);    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }    return (             {status}                 {this.renderSquare(0)}          {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }}Game의 render는 히스토리 전체를 보고 게임 상태를 계산하여 가져올 수 있어야 합니다.  render() {    const history = this.state.history;    const current = history[history.length - 1];    const winner = calculateWinner(current.squares);    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }    return (                                  squares={current.squares}            onClick={(i) => this.handleClick(i)}          />                        {status}         {/* TODO */}                );  }Game에 상태를 랜더링하고 있기 때문에{status}를 지우고 Board의 render 함수로부터 상태를 계산하는 코드를 지울 수 있습니다.  render() {    return (                      {this.renderSquare(0)}          {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }그 다음 Board에서 Game으로 handleClick 메서드를 옮겨야 합니다. Board 클래스에서 잘라내기를 하고 Game 클래스로 붙여넣을 수 있습니다.Game 상태는 다르기 때문에 수정해야 할 것이 조금 있습니다. Game의 handleClick은 히스토리 항목을 연결하여 새로운 배열을 만들어 스택에 푸시해야 합니다.  handleClick(i) {    const history = this.state.history;    const current = history[history.length - 1];    const squares = current.squares.slice();    if (calculateWinner(squares) || squares[i]) {      return;    }    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      history: history.concat([{        squares: squares,      }]),      xIsNext: !this.state.xIsNext,    });  }여기에서 Board는 renderSquare와 render만 필요합니다. 상태 초기화와 클릭 핸들러는 둘 다 Game에서 동작합니다.지금까지의 코드는 이곳에서 보실 수 있습니다.이동 표시하기지금까지 게임에서 진행된 이동을 표시해봅시다. 이전에 React 컴포넌트가 클래스로 JS 객체이고 그 덕에 데이터를 저장하고 전달할 수 있다고 배웠습니다. React에서 여러 아이템들을 랜더링하기 위해 React 요소의 배열을 전달했습니다. 배열을 빌드하는 가장 흔한 방법은 데이터 배열에서 map을 이용하는 것입니다. Game의 render 메서드에서 해봅시다.    render() {    const history = this.state.history;    const current = history[history.length - 1];    const winner = calculateWinner(current.squares);    const moves = history.map((step, move) => {      const desc = move ?        'Go to move #' + move :        'Go to game start';      return (                 <button onClick={() => this.jumpTo(move)}>{desc}</button>             );    });    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }    return (                                 squares={current.squares}            onClick={(i) => this.handleClick(i)}          />                        {status}         {moves}                );  }지금까지의 코드는 이곳에서 볼 수 있습니다.히스토리의 각 단계에서 <button>이 있는 리스트 아이템을 만들었습니다. 이 리스트 아이템은 우리가 곧 구현할 클릭 핸들러를 가지고 있습니다. 코드에서 다음과 같은 경고 메시지와 함께 게임에서 만들어지는 이동 목록을 볼 수 있습니다.Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of “Game”.경고: 배열이나 이터레이터에 있는 각 자식은 유니크 "key" prop을 가져야한다. "Game"의 render 메서드를 확인해보세요.이 경고의 의미가 무엇인지 얘기해봅시다.Keys아이템 리스트를 랜더링할때 React는 항상 리스트에 있는 각 아이템에 대한 정보를 저장합니다. 만약 상태를 가진 컴포넌트를 랜더링한다면 컴포넌트가 어떻게 실행되는지와 관계없이 상태는 저장 되어야 하고 React는 네이티브 뷰의 뒤에 참고할 것을 저장한다.리스트를 업데이트할 때 React는 무엇을 바꿀지 결정해야 합니다. 리스트에 아이템들을 추가하고, 지우고, 재배열하고, 수정할 수 있습니다.이 코드가 아래의 코드로 변경된다고 상상해봅시다.  Alexa: 7 tasks leftBen: 5 tasks leftBen: 9 tasks leftClaudia: 8 tasks leftAlexa: 5 tasks left사람의 눈에는 Alexa와 Ben의 자리가 바뀌고 Claudia가 추가된 것처럼 보인다. 하지만 React는 단순한 컴퓨터 프로그램이므로 여러분의 의도를 알지 못합니다. React는 리스트의 각 요소에서 key 속성을 지정해달라고 요청합니다. 문자열은 형제로부터 각 컴포넌트들을 구분합니다. 이 경우에 alexa, ben, claudia는 구분할 수 있는 키가 됩니다. 만약 아이템들이 데이터베이스의 객체와 일치시켜야 한다면 데이터베이스 ID을 사용하세요.{user.name}: {user.taskCount} tasks leftkey는 React에서 제공되는 특별한 속성입니다(ref에서 더 확장된 기능). 엘리먼트가 만들어질때 React는 key 속성을 가져오고 반환된 엘리먼트에 직접적으로 key를 저장합니다. key가 props의 한 부분으로 보일지라도 이것은 this.props.key로 참조할 수 없습니다. React는 어떤 하위 엘리먼트가 수정될지 결정하는 동안 알아서 key를 사용합니다. 컴포넌트가 자신의 키를 알 수 있는 방법은 없습니다.리스트가 랜더링될 때 React는 새로운 버전의 각 엘리먼트를 가져오고 이전 리스트에서 매칭되는 키를 가진 것을 찾습니다. key가 세트에 추가될 때 컴포넌트는 만들어집니다. 키가 삭제될 때 컴포넌트는 소멸됩니다. 키들은 React가 각 요소를 구별할 수 있도록하여 다시 랜더링하는 것을 무시하고 상태를 유지할 수 있게 합니다. 만약 컴포넌트의 키를 바꾼다면 완전히 지운 후 새롭게 생성됩니다.동적으로 리스트를 빌드할 때마다 적당한 키를 할당할 것을 강력 추천합니다. 만약 적당한 키를 가지지 못한다면 이를 위해 데이터를 재구성하여야 할지도 모릅니다.특정한 키를 구분하지 못한다면 React는 경고를 주고 배열 인덱스를 키로 사용합니다. 이는 올바른 선택이 아닙니다. 만약 리스트에 있는 엘리먼트들을 정렬하거나 리스트에 있는 버튼을 통해 지우거나 추가하면 명시적으로 key={i}를 전달하는 방법을 사용한다면 경고를 표시하지는 않지만 동일한 문제를 발생시키므로 대부분의 경우에 추천하지 않습니다.컴포넌트의 키가 전부 다를 필요는 없지만 관련있는 형제들 사이에서는 유니크해야 합니다.시간 여행 실행하기이동 리스트를 위해 우리는 각 단계에서 유니크 ID를 가졌습니다. Game의 render 메서드에서 키는로 추가하면 경고는 표시되지 않습니다.    const moves = history.map((step, move) => {      const desc = move ?        'Go to move #' + move :        'Go to game start';      return (                 <button onClick={() => this.jumpTo(move)}>{desc}</button>             );    });지금까지의 코드는 이곳에서 보실 수 있습니다.아직 junmTo가 정의되지 않았기 때문에 이동 버튼을 클릭하면 에러가 발생합니다. 지금 표시된 단계가 무엇인지 알기 위해 Game 상태에 새로운 키를 추가해봅시다.먼저Game의 constructor에  stepNumber: 0를 추가해주세요.class Game extends React.Component {  constructor(props) {    super(props);    this.state = {      history: [{        squares: Array(9).fill(null),      }],      stepNumber: 0,      xIsNext: true,    };  }그 다음 각 상태를 업데이트하기 위해 Game의 jumpTo 메서드를 정의해봅시다. 이 메서드에서는 xIsNext를 업데이트하고, 이동의 인덱스가 짝수라면 xIsNext를 true로 설정합니다.Game 클래스에jumpTo 메서드를 추가해주세요.handleClick(i) {    // this method has not changed  }  jumpTo(step) {    this.setState({     stepNumber: step,      xIsNext: (step % 2) === 0,    });  }  render() {    // this method has not changed  }Game handleClick에 상태를 업데이트 하기위해 stempNumber:history.length를 추가하여 새로운 이동이 있을 때마다  stepNumber를 업데이트 합니다. 현재 보드의 상태를 읽을 때 handleClick이 stepNumber라고 보고 클릭하는 시간대로 상태를 되돌릴 수 있습니다.  handleClick(i) {    const history = this.state.history.slice(0, this.state.stepNumber + 1);    const current = history[history.length - 1];    const squares = current.squares.slice();    if (calculateWinner(squares) || squares[i]) {      return;    }    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      history: history.concat([{        squares: squares      }]),      stepNumber: history.length,      xIsNext: !this.state.xIsNext,    });  }이제 히스토리의 각 단계를 알기 위해 Game의 render를 수정할 수 있습니다.  render() {    const history = this.state.history;    const current = history[this.state.stepNumber];    const winner = calculateWinner(current.squares);    // the rest has not changed지금까지의 코드는 이곳에서 보실 수 있습니다.이제 이동 버튼을 클릭하면 보드는 즉시 그때 표시된 게임으로 변경됩니다.마무리틱택토 게임을 플레이 해보세요.- 틱택토 게임을 플레이 해보세요.- 한 명의 플레이어가 게임에서 이길 때를 이를 알려줍니다.- 게임이 진행되는 동안 이동 기록이 저장됩니다.- 게임 보드의 에전 버전을 표시하기 위해 시간을 되돌릴 수 있습니다.잘 동작하네요! React가 어떻게 동작하는지 잘 아셨기를 바랍니다.최종 결과물은 여기에서 확인하세요.시간이 더 있거나 새로운 스킬들을 연습해보고 싶다면 해볼 수 있는 몇 가지 아이디어가 있습니다. 점점 더 어려운 순으로 배치해두었습니다.1. 움직임 리스트에서 (col, row) 형태에 각 움직임 위치를 표시하세요.2. 움직임 리스트의 선택된 아이템을 볼드처리하세요.3. 하드 코딩한 것들 대신 사각형을 두 개의 루프를 사용하여 Board를 다시 작성하세요.4. 오름차순 혹은 내림차순 뭐든지 움직임을 정렬하는 버튼을 추가해보세요.5. 누군가 이겼을 때 무엇 때문에 이겼는지 세 개의 사각형을 하이라이트하세요.튜토리얼이 진행되는 동안 우리는 엘리먼트, 컴포넌트, props, 상태를 포함한 React의 수많은 컨셉들을 다뤘습니다. 각 주제에 대한 깊은 설명을 원한다면 남은 문서를 확인하세요. 컴포넌트 정의에 대해 더 많이 배우고 싶다면 이 문서를 확인하세요.#트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유 #React #리액트 #리액트가이드 #한글 #번역 #문서번역
조회수 1061

레진 기술 블로그 - AWS Auto Scalinging Group 을 이용한 배포

레진코믹스의 서버 시스템은 잘 알려진대로 Google AppEngine에서 서비스되고 있지만, 이런저런 이유로 인해 최근에는 일부 컴포넌트가 Amazon Web Service에서 서비스되고 있습니다. AWS 에 새로운 시스템을 셋업하면서, 기존에 사용하던 PaaS인 GAE에서는 전혀 고민할 필요 없었던, 배포시스템에 대한 고민이 필요했습니다. 좋은 배포전략과 시스템은 안정적으로 서비스를 개발하고 운영하는데 있어서 필수적이죠.초기에는 Beanstalk을 이용한 운영에서, Fabric 을 이용한 배포 등의 시행착오 과정을 거쳤으나, 현재는 (스케일링을 위해 어차피 사용할 수밖에 없는) Auto Scaling Group을 이용해서 Blue-green deployment로 운영 중입니다. ASG는 여러 특징 덕분에 배포에도 유용하게 사용할 수 있습니다.ASG를 이용한 가장 간단한 배포는, Instance termination policy 를 응용할 수 있습니다. 기본적으로 ASG가 어떤 인스턴스를 종료할지는 AWS Documentation 에 정리되어 있으며, 추가적으로 다음과 같은 방식을 선택할 수 있습니다.OldestInstanceNewestInstanceOldestLaunchConfigurationClosestToNextInstanceHour여기서 주목할 건 OldestInstance 입니다. ASG가 항상 최신 버전의 어플리케이션으로 스케일아웃되게 구성되어 있다면, 단순히 인스턴스의 수를 두배로 늘린 뒤 Termination policy 를 OldestInstance 로 바꾸고 원래대로 돌리면 구버전 인스턴스들부터 종료되면서 배포가 끝납니다. 그러나 이 경우, 배포 직후 모니터링 과정에서 문제가 발생할 경우 기존의 인스턴스들이 이미 종료된 상태이기 때문에 롤백을 위해서는 (인스턴스를 다시 생성하면서) 배포를 다시 한번 해야 하는 반큼 빠른 롤백이 어렵습니다.Auto scaling lifecycle 을 이용하면, 이를 해결하기 위한 다른 방법도 있습니다. Lifecycle 은 다음과 같은 상태 변화를 가집니다.기본적으로,ASG의 인스턴스는 InService 상태로 진입하면서 (설정이 되어 있다면) ELB에 추가됩니다.ASG의 인스턴스는 InService 상태에서 빠져나오면서 (설정이 되어 있다면) ELB에서 제거됩니다.이를 이용하면, 다음과 같은 시나리오로 배포를 할 수 있습니다.똑같은 ASG 두 개를 구성(Group B / Group G)하고, 그 중 하나의 그룹으로만 서비스를 운영합니다.Group B가 라이브 중이면 Group G의 인스턴스는 0개입니다.새로운 버전을 배포한다면, Group G의 인스턴스 숫자를 Group B와 동일하게 맞춰줍니다.Group G가 InService로 들어가고 ELB healthy 상태가 되면, Group B의 인스턴스를 전부 Standby로 전환합니다.롤백이 필요하면 Standby 상태인 Group B를 InService 로 전환하고 Group G의 인스턴스를 종료하거나 Standby로 전환합니다.문제가 없다면 Standby 상태인 Group B의 인스턴스를 종료합니다.이제 훨씬 빠르고 안전하게 배포 및 롤백이 가능합니다. 물론 실제로는 생각보다 손이 많이 가는 관계로(특히 PaaS인 GAE에 비하면), 이를 한번에 해주는 스크립트를 작성해서 사용중입니다. 대략 간략하게는 다음과 같습니다. 실제 사용중인 스크립트에는 dry run 등의 잡다한 기능이 많이 들어가 있어서 걷어낸 pseudo code 입니다. 스크립트는 사내 PyPI 저장소를 통해 공유해서 사용 중입니다.def deploy(prefix, image_name, image_version): '''Deploy specified Docker image name and version into Auto Scaling Group''' asg_names = get_asg_names_from_tag(prefix, 'docker:image:name', image_name) groups = get_auto_scaling_groups(asg_names) # Find deployment target set future_set = set(map(lambda g: g['AutoScalingGroupName'].split('-')[-1], filter(lambda g: not g['DesiredCapacity'], groups))) if len(future_set) != 1: raise ValueError('Cannot specify target auto scaling group') future_set = next(iter(future_set)) if future_set == 'green': current_set = 'blue' elif future_set == 'blue': current_set = 'green' else: raise ValueError('Set name shoud be green or blue') # Deploy to future group future_groups = filter(lambda g: g['AutoScalingGroupName'].endswith(future_set), groups) for group in future_groups: asg_client.create_or_update_tags(Tags=[ { 'ResourceId': group['AutoScalingGroupName'], 'ResourceType': 'auto-scaling-group', 'PropagateAtLaunch': True, 'Key': 'docker:image:version', 'Value': image_version, } ]) # Set capacity, scaling policy, scheduled actions same as current group set_desired_capacity_from(current_set, group) move_scheduled_actions_from(current_set, group) move_scaling_policies(current_set, group) # Await ELB healthy of instances in group await_elb_healthy(future_groups) # Entering standby for current group for group in filter(lambda g: g['AutoScalingGroupName'].endswith(current_set), groups): asg_client.enter_standby( AutoScalingGroupName=group['AutoScalingGroupName'], InstanceIds=list(map(lambda i: i['InstanceId'], group['Instances'])), ShouldDecrementDesiredCapacity=True ) def rollback(prefix, image_name, image_version): '''Rollback standby Auto Scaling Group to service''' asg_names = get_asg_names_from_tag(prefix, 'docker:image:name', image_name) groups = get_auto_scaling_groups(asg_names) def filter_group_by_instance_state(groups, state): return filter( lambda g: len(filter(lambda i: i['LifecycleState'] == state, g['Instances'])) == g['DesiredCapacity'] and g['DesiredCapacity'], groups ) standby_groups = filter_group_by_instance_state(groups, 'Standby') inservice_groups = filter_group_by_instance_state(groups, 'InService') # Entering in-service for standby group for group in standby_groups: asg_client.exit_standby( AutoScalingGroupName=group['AutoScalingGroupName'], InstanceIds=list(map(lambda i: i['InstanceId'], group['Instances'])) ) # Await ELB healthy of instances in standby group await_elb_healthy(standby_groups) # Terminate instances to rollback for group in inservice_groups: asg_client.set_desired_capacity(AutoScalingGroupName=group['AutoScalingGroupName'], DesiredCapacity=0) current_set = group['AutoScalingGroupName'].split('-')[-1] move_scheduled_actions_from(current_set, group) move_scaling_policies(current_set, group) 몇 가지 더…Standby 로 돌리는 것 이외에 Detached 상태로 바꾸는 것도 방법입니다만, 인스턴스가 ASG에서 제거될 경우, 자신이 소속된 ASG를 알려주는 값인 aws:autoscaling:groupName 태그가 제거되므로 인스턴스나 ASG가 많아질 경우 번거롭습니다.cloud-init 를 어느 정도 최적화해두고 ELB healthcheck 를 좀 더 민감하게 설정하면, ELB 에 투입될 때까지 걸리는 시간을 상당히 줄일 수 있긴 하므로, 단일 ASG로 배포를 하더라도 롤백에 걸리는 시간을 줄일 수 있습니다. 저희는 scaleout 시작부터 ELB에서 healthy 로 찍힐 때까지 70초 가량 걸리는데, 그럼에도 불구하고 아래의 이유 때문에 현재의 방식으로 운영중입니다.같은 방식으로 단일 ASG로 배포를 할 수도 있지만, 배포중에 혹은 롤백 중에 scaleout이 돌면서 구버전 혹은 롤백 버전의 인스턴스가 투입되어버리면 매우 귀찮아집니다. 이를 방지하기 위해서라도 (Blue-green 방식의) ASG 두 개를 운영하는게 안전합니다.같은 이유로, 배포 대상의 버전을 S3나 github 등에 기록하는 대신 ASG의 태그에 버전을 써 두고 cloud-init 의 user-data에서 그 버전으로 어플리케이션을 띄우게 구성해 두었습니다. 이 경우 인스턴스의 태그만 확인해도 현재 어떤 버전이 서비스되고 있는지 확인할 수 있다는 장점도 있습니다.다만 ASG의 태그에 Tag on instance 를 체크해 두더라도, cloud-init 안에서 이를 조회하는 경우는 주의해야 합니다. ASG의 태그가 인스턴스로 복사되는 시점은 명확하지 않습니다. 스크립트 실행 중에 인스턴스에는 ASG의 태그가 있을 수도, 없을 수도 있습니다.굳이 인스턴스의 Lifecycle 을 Standby / InService 로 전환하지 않고도 ELB 를 두 개 운영하고 route 53 에서의 CNAME/ALIAS swap 도 방법이지만, DNS TTL은 아무리 짧아도 60초는 걸리고, JVM처럼 골치아픈 동작 사례도 있는만큼 선택하지 않았습니다.물론 이 방법이 최선은 절대 아니며(심지어 배포할때마다 돈이 들어갑니다!), 현재는 자원의 활용 등 다른 측면에서의 고민 때문에 새로운 구성을 고민하고 있습니다. 이건 언젠가 나중에 다시 공유하겠습니다. :)
조회수 1554

비트윈 시스템 아키텍처 - VCNC Engineering Blog

VCNC는 커플을 위한 모바일 앱 비트윈을 서비스하고 있습니다. 비트윈은 사진, 메모, 채팅, 기념일 등 다양한 기능을 제공하며, 오픈 베타 테스트를 시작한 2011년 11월부터 현재까지 연인 간의 소통을 돕고 있습니다. 그동안 비트윈 시스템 아키텍처에는 많은 변화가 있었으며 다양한 결정을 하였습니다. 비트윈 아키텍처를 발전시키면서 배우게 된 여러 가지 노하우를 정리하여 공유해보고자 합니다. 그리고 저희가 앞으로 나아갈 방향을 소개하려 합니다.소프트웨어 스택Java: 비트윈 API서버는 Java로 작성되어 있습니다. 이는 처음 비트윈 서버를 만들기 시작할 때, 서버 개발자가 가장 빨리 개발해낼 수 있는 언어로 프로그래밍을 시작했기 때문입니다. 지금도 자바를 가장 잘 다루는 서버 개발자가 많으므로 여전히 유효한 선택입니다.Netty: 대부분의 API는 HTTP로 호출되며, 채팅은 모바일 네트워크상에서의 전송 속도를 위해 TCP상에서 프로토콜을 구현했습니다. 두 가지 모두 Netty를 통해 사용자 요청을 처리합니다. Netty를 선택한 것은 뛰어난 성능과 서비스 구현 시 Thrift 서비스를 통해 HTTP와 TCP 프로토콜을 한 번에 구현하기 쉽다는 점 때문이었습니다.Thrift: API서버의 모든 서비스는 Thrift 서비스로 구현됩니다. 따라서 TCP뿐만 아니라 HTTP 또한 Thrift 인터페이스를 사용합니다. HTTP를 굳이 Thrift서비스로 구현한 이유는, TCP로 메세징 전송 시 똑같은 서비스를 그대로 사용하기 위함이었습니다. 덕분에 빠른 채팅 구현 시, 이미 구현된 서비스들을 그대로 사용할 수 있었습니다. 또한, 채팅 패킷들은 패킷 경량화를 위해 snappy로 압축하여 송수신합니다. 모바일 네트워크상에서는 패킷이 작아질수록 속도 향상에 크게 도움이 됩니다.HBase: 비트윈의 대부분 트랜젝션은 채팅에서 일어납니다. 수많은 메시지 트랜젝션을 처리하기 위해 HBase를 선택했으며, 당시 서버 개발자가 가장 익숙한 데이터베이스가 HBase였습니다. 서비스 초기부터 확장성을 고려했어야 했는데, RDBMS에서 확장성에 대해 생각하는 것보다는 당장 익숙한 HBase를 선택하고 운영하면서 나오는 문제들은 차차 해결하였습니다.ZooKeeper: 커플들을 여러 서버에 밸런싱하고 이 정보를 여러 서버에서 공유하기 위해 ZooKeeper를 이용합니다. Netflix에서 공개한 오픈 소스인 Curator를 이용하여 접근합니다.AWS비트윈은 AWS의 Tokyo리전에서 운영되고 있습니다. 처음에는 네트워크 및 성능상의 이유로 국내 IDC를 고려하기도 했으나 개발자들이 IDC 운영 경험이 거의 없는 것과, IDC의 실질적인 TCO가 높다는 문제로 클라우드 서비스를 이용하기로 하였습니다. 당시 클라우드 서비스 중에 가장 안정적이라고 생각했던 AWS 를 사용하기로 결정했었고, 지금도 계속 사용하고 있습니다.EC2: 비트윈의 여러 부가적인 서비스를 위해 다양한 종류의 인스턴스를 사용 중이지만, 메인 서비스를 운용하기 위해서는 c1.xlarge와 m2.4xlarge 인스턴스를 여러 대 사용하고 있습니다.API 서버: HTTP 파싱이나 이미지 리시아징등의 연산이 이 서버에서 일어납니다. 이 연산들은 CPU 가 가장 중요한 리소스이기 때문에, c1.xlarge를 사용하기로 했습니다.Database 서버: HDFS 데이터 노드와 HBase 리전 서버들이 떠있습니다. 여러 번의 테스트를 통해 IO가 병목임을 확인하였고, 따라서 모든 데이터를 최대한 메모리에 올리는 것이 가장 저렴한 설정이라는 것을 확인하였습니다. 이런 이유 때문에 68.4GB의 메모리를 가진 m2.4xlarge를 Database 서버로 사용하고 있습니다.EBS: 처음에는 HBase상 데이터를 모두 EBS에 저장하였습니다. 하지만 일정 시간 동안 EBS의 Latency가 갑자기 증가하는 등의 불안정한 경우가 자주 발생하여 개선 방법이 필요했는데, 데이터를 ephemeral storage에만 저장하기에는 안정성이 확인되지 않은 상태였습니다. 위의 두 가지 문제를 동시에 해결하기 위해서 HDFS multiple-rack 설정을 통해서 두 개의 복제본은 ephemeral storage에 저장하고 다른 하나의 복제본은 PIOPS EBS에 저장되도록 구성하여 EBS의 문제점들로부터의 영향을 최소화하였습니다.S3: 사용자들이 올리는 사진들은 s3에 저장됩니다. 사진의 s3키는 추측이 불가능하도록 랜덤하게 만들어집니다. 어차피 하나의 사진은 두 명밖에 받아가지 않고 클라이언트 로컬에 캐싱되기 때문에 CloudFront를 사용하지는 않습니다.ELB: HTTP는 사용자 요청의 분산과 SSL적용을 위해 ELB를 사용합니다. TCP는 TLS를 위해 ELB를 사용합니다. SSL/TLS 부분은 모두 AWS의 ELB를 이용하는데, 이는 API서버의 SSL/TLS처리에 대한 부담을 덜어주기 위함입니다.CloudWatch: 각 통신사와 리전에서 비트윈 서버로의 네트워크 상태와 서버 내의 요청 처리 시간 등의 메트릭을 CloudWatch로 모니터링 하고 있습니다. 따라서 네트워크 상태나 서버에 문제가 생긴 경우, 이메일 등을 통해 즉각 알게 되어, 문제 상황에 바로 대응하고 있습니다. Netflix의 Servo를 이용하여 모니터링 됩니다.현재의 아키텍처처음 클로즈드 베타 테스트때에는 사용자 수가 정해져 있었기 때문에 하나의 인스턴스로 운영되었습니다. 하지만 처음부터 인스턴스 숫자를 늘리는 것만으로도 서비스 규모를 쉽게 확장할 수 있는 아키텍쳐를 만들기 위한 고민을 하였습니다. 오픈 베타 이후에는 발생하는 트래픽에 필요한 만큼 여러 대의 유연하게 서버를 운영하였고, 현재 채팅은 TCP 위에서 구현한 프로토콜을 이용하여 서비스하고 있습니다.HTTP 요청은 하나의 ELB를 통해 여러 서버로 분산됩니다. 일반적인 ELB+HTTP 아키텍처와 동일합니다.채팅은 TCP 연결을 맺게 되는데, 각 커플은 특정 API 서버로 샤딩되어 특정 커플에 대한 요청을 하나의 서버가 담당합니다. 비트윈에서는 커플이 샤딩의 단위가 됩니다.이를 통해, 채팅 대화 내용 입력 중인지 여부와 같이 굉장히 빈번하게 값이 바뀌는 정보를 인메모리 캐싱할 수 있게 됩니다. 이런 정보는 휘발성이고 매우 자주 바뀌는 정보이므로, HBase에 저장하는 것은 매우 비효율적입니다.Consistent Hashing을 이용하여 커플을 각 서버에 샤딩합니다. 이는 서버가 추가되거나 줄어들 때, 리밸런싱되면서 서버간 이동되는 커플들의 수를 최소화 하기 위함입니다.클라이언트는 샤딩 정보를 바탕으로 특정 서버로 TCP연결을 맺게 되는데, 이를 위해 각 서버에 ELB가 하나씩 붙습니다. 어떤 서버로 연결을 맺어야 할지는 HTTP 혹은 TCP 프로토콜을 통해 알게 됩니다.Consistent Hashing을 위한 정보는 ZooKeeper를 통해 여러 서버간 공유됩니다. 이를 통해 서버의 수가 늘어나거나 줄어들게 되는 경우, 각 서버는 자신이 담당해야 하는 샤딩에 대한 변경 정보에 대해 즉각 알게 됩니다.이런 아키텍처의 단점은 다음과 같습니다.클라이언트가 자신이 어떤 서버로 붙어야 하는지 알아야 하기 때문에 프로토콜 및 아키텍처 복잡성이 높습니다.서버가 늘어나는 경우, 순식간에 많은 사용자 연결이 맺어지게 됩니다. 따라서 새로 추가되는 ELB는 Warm-up이 필요로 하며 이 때문에 Auto-Scale이 쉽지 않습니다.HBase에 Write연산시, 여러 서버로 복제가 일어나기 때문에, HA을 위한 Multi-AZ 구성을 하기가 어렵습니다.한정된 자원으로 동작 가능한 서버를 빨리 만들어내기 위해 이처럼 디자인하였습니다.미래의 아키텍처현재 아키텍처에 단점을 보완하기 위한 해결 방법을 생각해보았습니다.Haeinsa는 HBase상에서 트렌젝션을 제공하기 위해 개발 중인 프로젝트입니다. 구현 완료 후, 기능 테스트를 통과하였고, 퍼포먼스 테스트를 진행하고 있습니다. HBase상에서 트렌젝션이 가능하게 되면, 좀 더 복잡한 기능들을 빠르게 개발할 수 있습니다. 서비스에 곧 적용될 예정입니다.Multitier Architecture를 통해 클라이언트와 서버 간에 프로토콜을 단순화시킬 수 있습니다. 이 부분은 개발 초기부터 생각하던 부분인데, 그동안 개발을 하지 못하고 있다가, 지금은 구현을 시작하고 있습니다. 커플은 특정 Application 서버에서 담당하게 되므로, 인메모리 캐싱이 가능하게 됩니다. 클라이언트는 무조건 하나의 ELB만 바라보고 요청을 보내게 되고, Presentation 서버가 사용자 요청을 올바른 Application 서버로 릴레이 하게 됩니다.Multitier Architecture를 도입하면, 더 이상 ELB Warm-up이 필요하지 않게 되므로, Auto-Scale이 가능하게 되며, 좀 더 쉬운 배포가 가능하게 됩니다.Rocky는 API 서버의 Auto-Failover와 커플에 대한 샤딩을 직접 처리하는 기능을 가진 프로젝트입니다. 현재 설계가 어느 정도 진행되어 개발 중에 있습니다. 알람이 왔을 때 서버 팀이 마음을 놓고 편히 잠을 잘 수 있는 역할을 합니다.기본적인 것은 위에서 언급한 구조와 동일하지만 몇 가지 기능이 설정을 추가하면 Multi-AZ 구성이 가능합니다.특정 커플에 대한 모든 정보는 하나의 HBase Row에 담기게 됩니다.HBase의 특정 리전에 문제가 생긴 경우, 일정 시간이 지나면 자동으로 복구되긴 하지만 잠시 동안 시스템 전체에 문제가 생기가 됩니다. 이에 대해 Pinterest에서 Clustering보다는 Sharding이 더 낫다는 글을 쓰기도 했습니다. 이에 대한 해결책은 다음과 같습니다.원래는 Consistent Hashing을 사용하여 커플들을 Application 서버에 샤딩하였습니다. 하지만 이제는 HBase에서 Row를 각 리전에 수동으로 할당하고, 같은 리전에 할당된 Row에 저장된 커플들은 같은 Application 서버에 할당하도록 합니다.이 경우에, 같은 커플들을 담당하는 Application 서버와 HBase 리전 서버는 물리적으로 같은 머신에 둡니다.이렇게 구성 하는 경우, 특정 HBase 리전이나 Application 서버에 대한 장애는 특정 샤드에 국한되게 됩니다. 이와 같이 하나의 머신에 APP과 DB를 같이 두는 구성은 구글에서도 사용하는 방법입니다.이와 같이 구성하는 경우, Multi-AZ 구성이 가능하게 됩니다.AWS에서 같은 리전에서 서로 다른 Zone간 통신은 대략 2~3ms 정도 걸린다고 합니다.Presentation의 경우, 비동기식으로 동작하기 때문에 다른 리전으로 요청을 보내도 부담이 되지 않습니다.HBase에서 Write가 일어나면 여러 복제본을 만들게 됩니다. 하나의 사용자 요청에 대해 Write가 여러번 일어나기 때문에 HBase연산의 경우에는 서로 다른 Zone간 Latency가 부담으로 작용됩니다. Haeinsa가 적용되면, 한 트렌젝션에 대해서 연산을 Batch로 전송하기 때문에 AZ간 Latency 부담이 적습니다.프리젠테이션다음은 2월에 있었던 AWS 유저 그룹 세미나에서 발표했던 자료 입니다. 비트윈 서버 아키텍처에 대해서 배포 방법을 중심으로 설명이 되어 있습니다. 비슷한 내용이 많이 있으니 살펴보시기 바랍니다.<iframe class="speakerdeck-iframe" frameborder="0" src="//speakerdeck.com/player/e4af60d05bb6013025f71231381b23b3?" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 750px; height: 563px;">
조회수 1327

EOS Smart Contract 배포

Smart Contract 배포를 위한 준비 과정은 이전글 확인 부탁드립니다.저번시간과 연계하여 이번 시간엔 스마트 컨트랙트를 배포해 보도록 하겠습니다. 지갑 key와 계정 이름등은 본 포스팅에서 그대로 사용하시면 됩니다.배포할 컨트랙트는 eosio.token 으로 eos 개발환경 세팅 시 존재하는 코드를 컴파일하여 실제 사용하는 계정에 setting 하겠습니다. 먼저 컴파일을 위해 ../eos/contracts/eosio.token 으로 이동 하겠습니다.eos/contracts/eosio.token이동 하면 위와 같은 파일들을 확인 하실 수 있습니다.hpp : cpp 파일에서 사용하는 변수, 상수, 함수를 담는 헤더파일cpp : contract 함수를 구현하는 소스 파일eosiocpp 를 통해 소스코드를 컴파일 해보겠습니다. eosiocpp 는 WASM 및 ABI 컴파일러 로써 블록체인에 업로드 되는 .wasm, .wast, .abi 파일을 생성합니다. 또한 기본 스켈레톤 파일을 제공합니다eosiocppwasm 컴파일wasm 파일은 아래 명령어를 사용하여 컴파일 만들 수 있습니다.$ eosiocpp -o eosio.token.wast eosio.token.cppeosiocpp 명령어를 사용하여 컴파일 하게 되면 .wast 파일과 .wasm 파일을 생성하게 됩니다. 각 확장자는 다음을 의미합니다.wast : 텍스트 파일로써 읽을 수 있는 webAssembly 파일wasm : 컴퓨터가 실제로 이해할 수 있는 webAssembly 파일abi 파일 생성$ eosiocpp -g eosio.token.abi eosio.token.cppabi 파일은 JSON과 Binary 간에 사용자 작업을 변환하는 방법에 대해 설명해주는 파일입니다. 실제로 이 JSON 파일을 통해 블록체인 위에서 개발자와 사용자간 상호작용 하는데 도와주게 됩니다.위 2과정을 통해 abi 파일 과 wast 파일을 생성하게 됩니다.compile 결과Contract 세팅하기아래 명령어를 입력하여 contract 를 set 해줍니다.$ cleos set contract hexlanthenry ../eos/build/contracts/eosio.token account : contract 를 배포할 계정이름contract-dir : 계정에 set 할 contract 가 저장된 directoryset contract 수행 결과만약 해당 계정이 RAM 을 보유하고 있지 않다면 다음과 같은 에러가 나타날 것입니다. 이를 해결하기 위해 RAM 을 구매합니다.RAM을 보유없이 contract$ cleos system buyram hexlanthenry hexlanthenry "100.0000 EOS"payer : EOS 를 지불할 계정receiver : RAM 을 사용할 계정amount : 지불할 EOS의 양 ( eos 1.1 기준 소수점4개 자리와 symbol을 무조건 넣어주어야 정상 동작 합니다)contract 확인계정에 contract가 잘 배포 되었는지 확인해 보겠습니다.$ cleos get code hexlanthenry배포한 contract 가 있을때의 code hash배포한 contract 가 없을때의 code hash또한 abi 를 통해서도 확인할 수 있습니다.$ cleos get abi hexlanthenryget abi위 과정을 통해 해당 계정에 실제로 contract 가 잘 배포 되었는지 확인 할 수 있습니다.다음 시간에는 배포된 contract 를 통하여 토큰을 발행 해보고 token에 대한 balance 체크 및 transfer 하는 과정을 진행해 보도록 하겠습니다.+또한 abi를 분석하여 struct 와 action 을 어떻게 확인 하는지에 대한 자세한 방법은 다른 포스팅에서 다루도록 하겠습니다.감사합니다.#헥슬란트 #HEXLANT #블록체인 #개발자 #개발팀 #기술기업 #기술중심
조회수 1531

HBase 설정 최적화하기 - VCNC Engineering Blog

커플 필수 앱 비트윈은 여러 종류의 오픈 소스를 기반으로 이루어져 있습니다. 그 중 하나는 HBase라는 NoSQL 데이터베이스입니다. VCNC에서는 HBase를 비트윈 서비스의 메인 데이터베이스로써 사용하고 있으며, 또한 데이터 분석을 위한 DW 서버로도 사용하고 있습니다.그동안 두 개의 HBase Cluster 모두 최적화를 위해서 여러 가지 설정을 테스트했고 노하우를 공유해 보고자 합니다. 아랫은 저희가 HBase를 실제로 저희 서비스에 적용하여 운영하면서 최적화한 시스템 구성과 설정들을 정리한 것입니다. HBase를 OLTP/OLAP 목적으로 사용하고자 하는 분들에게 도움이 되었으면 좋겠습니다. 아래 구성을 최적화하기 위해서 했던 오랜 기간의 삽질기는 언젠가 따로 포스팅 하도록 하겠습니다.HBaseHBase는 Google이 2006년에 발표한 BigTable이라는 NoSQL 데이터베이스의 아키텍처를 그대로 따르고 있습니다. HBase는 뛰어난 Horizontal Scalability를 가지는 Distributed DB로써, Column-oriented store model을 가지고 있습니다. 사용량이 늘어남에 따라서 Regionserver만 추가해주면 자연스럽게 Scale-out이 되는 구조를 가지고 있습니다. 또한, Hadoop 특유의 Sequential read/write를 최대한 활용해서 Random access를 줄임으로 Disk를 효율적으로 사용한다는 점을 특징으로 합니다. 이 때문에 HBase는 보통의 RDBMS와는 다르게 Disk IO가 병목이 되기보다는 CPU나 RAM 용량이 병목이 되는 경우가 많습니다.HBase는 많은 회사가 데이터 분석을 하는 데 활용하고 있으며, NHN Line과 Facebook messenger 등의 메신저 서비스에서 Storage로 사용하고 있습니다.시스템 구성저희는 Cloudera에서 제공하는 HBase 0.92.1-cdh4.1.2 release를 사용하고 있으며, Storage layer로 Hadoop 2.0.0-cdh4.1.2를 사용하고 있습니다. 또한, Between의 데이터베이스로 사용하기 위해서 여러 대의 AWS EC2의 m2.4xlarge 인스턴스에 HDFS Datanode / HBase Regionserver를 deploy 하였습니다. 이는 m2.4xlarge의 큰 메모리(68.4GB)를 최대한 활용해서 Disk IO를 회피하고 많은 Cache hit이 나게 하기 위함입니다.또한 Highly-Available를 위해서 Quorum Journaling node를 활용한 Active-standby namenode를 구성했으며, Zookeeper Cluster와 HBase Master도 여러 대로 구성하여 Datastore layer에서 SPOF를 전부 제거하였습니다. HA cluster를 구성하는 과정도 후에 포스팅 하도록 하겠습니다.HDFS 최적화 설정dfs.datanode.handler.countHDFS에서 외부 요청을 처리하는 데 사용할 Thread의 개수를 정하기 위한 설정입니다. 기본값은 3인데 저희는 100으로 해 놓고 사용하고 있습니다.dfs.replicationHDFS 레벨에서 각각의 데이터가 몇 개의 독립된 인스턴스에 복사될 것 인가를 나타내는 값입니다. 저희는 이 값을 기본값인 3으로 해 놓고 있습니다. 이 값을 높이면 Redundancy가 높아져서 데이터 손실에 대해서 더 안전해지지만, Write 속도가 떨어지게 됩니다.dfs.datanode.max.transfer.threads하나의 Datanode에서 동시에 서비스 가능한 block 개수 제한을 나타냅니다.과거에는 dfs.datanode.max.xcievers라는 이름의 설정이었습니다.기본값은 256인데, 저희는 4096으로 바꿨습니다.ipc.server.tcpnodelay / ipc.client.tcpnodelaytcpnodelay 설정입니다. tcp no delay 설정은 TCP/IP network에서 작은 크기의 패킷들을 모아서 보냄으로써 TCP 패킷의 overhead를 절약하고자 하는 Nagle's algorithm을 끄는 것을 의미합니다. 기본으로 두 값이 모두 false로 설정되어 있어 Nagle's algorithm이 활성화되어 있습니다. Latency가 중요한 OLTP 용도로 HBase를 사용하시면 true로 바꿔서 tcpnodelay 설정을 켜는 것이 유리합니다.HBase 최적화 설정hbase.regionserver.handler.countRegionserver에서 외부로부터 오는 요청을 처리하기 위해서 사용할 Thread의 개수를 정의하기 위한 설정입니다. 기본값은 10인데 보통 너무 작은 값입니다. HBase 설정 사이트에서는 너무 큰 값이면 좋지 않다고 얘기하고 있지만, 테스트 결과 m2.4xlarge (26ECU) 에서 200개 Thread까지는 성능 하락이 없는 것으로 나타났습니다. (더 큰 값에 관해서 확인해 보지는 않았습니다.)저희는 이 값을 10에서 100으로 올린 후에 약 2배의 Throughput 향상을 얻을 수 있었습니다.hfile.block.cache.sizeHBase 의 block 들을 cache 하는데 전체 Heap 영역의 얼마를 할당한 것인지를 나타냅니다. 저희 서비스는 Read가 Write보다 훨씬 많아서 (Write가 전체의 약 3%) Cache hit ratio가 전체 성능에 큰 영향을 미칩니다.HBase 에서는 5분에 한 번 log 파일에 LruBlockCache (HBase 의 Read Cache) 가 얼마 만큼의 메모리를 사용하고 있고, Cache hit ratio가 얼마인지 표시를 해줍니다. 이 값을 참조하셔서 최적화에 사용하실 수 있습니다.저희는 이 값을 0.5로 설정해 놓고 사용하고 있습니다. (50%)hbase.regionserver.global.memstore.lowerLimit / hbase.regionserver.global.memstore.upperLimit이 두 개의 설정은 HBase에서 Write 한 값들을 메모리에 캐쉬하고 있는 memstore가 Heap 영역의 얼마만큼을 할당받을지를 나타냅니다. 이 값이 너무 작으면 메모리에 들고 있을 수 있는 Write의 양이 한정되기 때문에 디스크로 잦은 flush가 일어나게 됩니다. 반대로 너무 크면 GC에 문제가 있을 수 있으며 Read Cache로 할당할 수 있는 메모리를 낭비하는 것이기 때문에 좋지 않습니다.lowerLimit와 upperLimit의 두 가지 설정이 있는데, 두 개의 설정이 약간 다른 뜻입니다.만약 memstore 크기의 합이 lowerLimit에 도달하게 되면, Regionserver에서는 memstore들에 대해서 'soft'하게 flush 명령을 내리게 됩니다. 크기가 큰 memstore 부터 디스크에 쓰이게 되며, 이 작업이 일어나는 동안 새로운 Write가 memstore에 쓰일 수 있습니다.하지만 memstore 크기의 합이 upperLimit에 도달하게 되면, Regionserver는 memstore들에 대한 추가적인 Write를 막는 'hard'한 flush 명령을 내리게 됩니다. 즉, 해당 Regionserver이 잠시 동안 Write 요청을 거부하게 되는 것입니다. 보통 lowerLimit에 도달하면 memstore의 크기가 줄어들기 때문에 upperLimit까지 도달하는 경우는 잘 없지만, write-heavy 환경에서 Regionserver가 OOM으로 죽는 경우를 방지하기 위해서 hard limit가 존재하는 것으로 보입니다.hfile.block.cache.size와 hbase.regionserver.global.memstore.upperLimit의 합이 0.8 (80%)를 넘을 수 없게 되어 있습니다. 이는 아마 read cache 와 memstore의 크기의 합이 전체 Heap 영역 중 대부분을 차지해 버리면 HBase의 다른 구성 요소들이 충분한 메모리를 할당받을 수 없기 때문인 듯합니다.저희는 이 두 개의 설정 값을 각각 0.2, 0.3으로 해 놓았습니다. (20%, 30%)ipc.client.tcpnodelay / ipc.server.tcpnodelay / hbase.ipc.client.tcpnodelayHDFS의 tcpnodelay 와 비슷한 설정입니다. 기본값은 전부 false입니다.이 설정을 true로 하기 전에는 Get/Put 99%, 99.9% Latency가 40ms 와 80ms 근처에 모이는 현상을 발견할 수 있었습니다. 전체 요청의 매우 작은 부분이었지만, 평균 Get Latency가 1~2ms 내외이기 때문에 99%, 99.9% tail이 평균 Latency에 큰 영향을 미쳤습니다.이 설정을 전부 true로 바꾼 후에 평균 Latency가 절반으로 하락했습니다.Heap memory / GC 설정저희는 m2.4xlarge가 제공하는 메모리 (68.4GB)의 상당 부분을 HBase의 Read/Write cache에 할당하였습니다. 이는 보통 사용하는 Java Heap 공간보다 훨씬 큰 크기이며 심각한 Stop-the-world GC 문제를 일으킬 수 있기 때문에, 저희는 이 문제를 피하고자 여러 가지 설정을 실험하였습니다.STW GC time을 줄이기 위해서 Concurrent-Mark-and-sweep GC를 사용했습니다.HBase 0.92에서부터 기본값으로 설정된 Memstore-Local Allocation Buffer (MSLAB) 을 사용했습니다. hbase.hregion.memstore.mslab.enabled = true #(default)hbase-env.sh 파일을 다음과 같이 설정했습니다. HBASE_HEAPSIZE = 61440 #(60GB) HBASE_OPTS = "-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps"GC log를 Python script로 Parsing해서 STW GC 시간을 관찰하고 있습니다. 지금까지 0.2초 이상의 STW GC는 한 번도 발생하지 않았습니다.그 밖에 도움이 될 만한 설정들hbase.hregion.majorcompactionHBase는 하나의 Region에 대해서 여러 개의 StoreFile을 가질 수 있습니다. 그리고 주기적으로 성능 향상을 위해서 이 파일들을 모아서 하나의 더 큰 파일로 합치는 과정을 진행하게 됩니다. 그리고 이 과정은 많은 CPU usage와 Disk IO를 동반합니다. 그리고 이때 반응 속도가 다소 떨어지게 됩니다. 따라서 반응 속도가 중요한 경우에는, 이 Major compaction을 off-peak 시간대를 정해서 manual 하게 진행하시는 것이 좋습니다.저희는 사용자의 수가 상대적으로 적은 새벽 시간대에 crontab 이 실행시키는 script가 돌면서 전체 Region에 대해서 하나하나 Major Compaction이 진행되도록 하였습니다.기본값은 86,400,000 (ms)로 되어 있는데, 이 값을 0으로 바꾸시면 주기적인 Major Compaction이 돌지 않게 할 수 있습니다.hbase.hregion.max.filesizeHBase는 하나의 Region이 크기가 특정 값 이상이 되면 자동으로 2개의 Region으로 split을 시킵니다. Region의 개수가 많지 않을 때는 큰 문제가 없지만, 계속해서 데이터가 쌓이게 되면 필요 이상으로 Region 수가 많아지는 문제를 나을 수 있습니다. Region 수가 너무 많아지면 지나친 Disk IO가 생기는 문제를 비롯한 여러 가지 안 좋은 점이 있을 수 있기 때문에, split 역시 manual 하게 하는 것이 좋습니다. 그렇다고 Table의 Region 수가 너무 적으면 Write 속도가 떨어지거나 Hot Region 문제가 생길 수 있기 때문에 좋지 않습니다.HBase 0.92.1 에서는 기본값이 1073741824(1GB)로 되어 있는데, 저희는 이 값을 10737418240(10GB)로 늘인 후에 manual 하게 split을 하여 Region의 개수를 조정하고 있습니다.hbase.hregion.memstore.block.multipliermemstore의 전체 크기가 multiplier * flush size보다 크면 추가적인 Write를 막고 flush가 끝날때까지 해당 memstore는 block 됩니다.기본값은 2인데, 저희는 8로 늘려놓고 사용하고 있습니다.dfs.datanode.balance.bandwidthPerSec부수적인 설정이지만, HDFS의 Datanode간의 load balancing이 일어나는 속도를 제한하는 설정입니다. 기본값은 1MB/sec로 되어 있지만, 계속해서 Datanode를 추가하거나 제거하는 경우에는 기본값으로는 너무 느릴 때가 있습니다. 저희는 10MB/sec 정도로 늘려서 사용하고 있습니다.dfs.namenode.heartbeat.recheck-intervalHDFS namenode에만 해당되는 설정입니다.Datanode가 응답이 없는 경우에 얼마 후에 Hadoop cluster로부터 제거할 것인지를 나타내는 값입니다.실제로 응답이 없는 Datanode가 떨어져 나가기까지는 10번의 heartbeat가 연속해서 실패하고 2번의 recheck역시 실패해야 합니다. Heartbeat interval이 기본값인 3초라고 하면, 30초 + 2 * recheck-interval 후에 문제가 있는 Datanode가 제거되는 것입니다.기본값이 5분으로 되어 있는데, fail-over가 늦어지기 때문에 사용하기에는 너무 큰 값입니다. 저희는 문제가 있는 Datanode가 1분 후에 떨어져 나갈 수 있도록 이 값을 15,000 (ms) 으로 잡았습니다.Read short-circuitRegionServer가 로컬 Datanode로부터 block을 읽어올 때 Datanode를 통하지 않고 Disk로부터 바로 읽어올 수 있게 하는 설정입니다.데이터의 양이 많아서 Cache hit이 낮아 데이터 대부분을 디스크에서 읽어와야 할 때 효율적입니다. Cache hit에 실패하는 Read의 Throughput이 대략 2배로 좋아지는 것을 확인할 수 있습니다. OLAP용 HBase에는 매우 중요한 설정이 될 수 있습니다.하지만 HBase 0.92.1-cdh4.0.1까지는 일부 Region이 checksum에 실패하면서 Major compaction이 되지 않는 버그가 있었습니다. 현재 이 문제가 해결되었는지 확실하지 않기 때문에 확인되기 전에는 쓰는 것을 추천하지는 않습니다.설정하는 방법은 다음과 같습니다. dfs.client.read.shortcircuit = true #(hdfs-site.xml) dfs.block.local-path-access.user = hbase #(hdfs-site.xml) dfs.datanode.data.dir.perm = 775 #(hdfs-site.xml) dfs.client.read.shortcircuit = true #(hbase-site.xml)Bloom filterBloom filter의 작동방식에 대해 시각적으로 잘 표현된 데모 페이지HBase는 Log-structured-merge tree를 사용하는데, 하나의 Region에 대해서 여러 개의 파일에 서로 다른 version의 값들이 저장되어 있을 수 있습니다. Bloom filter는 이때 모든 파일을 디스크에서 읽어들이지 않고 원하는 값이 저장된 파일만 읽어들일 수 있게 함으로써 Read 속도를 빠르게 만들 수 있습니다.Table 단위로 Bloom filter를 설정해줄 수 있습니다.ROW와 ROWCOL의 두 가지 옵션이 있는데, 전자는 Row key로만 filter를 만드는 것이고, 후자는 Row+Column key로 filter를 만드는 것입니다. Table Schema에 따라 더 적합한 설정이 다를 수 있습니다.저희는 데이터 대부분이 메모리에 Cache 되고 하나의 Region에 대해서 여러 개의 StoreFile이 생기기 전에 compaction을 통해서 하나의 큰 파일로 합치는 작업을 진행하기 때문에, 해당 설정을 사용하지 않고 있습니다.결론지금까지 저희가 비트윈을 운영하면서 얻은 경험을 토대로 HBase 최적화 설정법을 정리하였습니다. 하지만 위의 구성은 어디까지나 비트윈 서비스에 최적화되어 있는 설정이며, HBase의 사용 목적에 따라서 달라질 수 있음을 말씀드리고 싶습니다. 그래서 단순히 설정값을 나열하기보다는 해당 설정이 어떤 기능을 하는 것인지 저희가 아는 한도 내에서 설명드리려고 하였습니다. 위의 글에서 궁금한 점이나 잘못된 부분이 있으면 언제든지 답글로 달아주시길 바랍니다. 감사합니다.
조회수 4632

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.#데일리 #데일리호텔 #개발 #개발자 #개발팀 #인사이트
조회수 1619

Database를 왜 사용할까요?

개발자들이 Database 프로그램을 선택한 이유Database(이하 DB) 프로그램을 처음 접한 건 Dos에서 사용하는 Database III plus였습니다. 이때는 학생이었기 때문에 프로그램 개발에 관심이 많았지만 대량의 데이터를 다룰 일은 없었습니다. 다음으로 접한 건 clipper였습니다. 과거 C언어를 하던 사람이면 자료 처리를 위해 한 번쯤은 접해봤을 겁니다. 이때까지는 Dos를 주로 사용했고, 간단한 자료를 다루었기 때문에 File 처리만으로도 충분한 결과를 얻을 수 있었죠.그렇다면 DB는 다중 사용자 환경이 되고 바로 사용하게 되었을까요? 예전에 다중 사용자들이 사용했던 걸 꼽자면 PC 통신과 Web이 있을 것입니다. 초창기의 Web은 PHP, ASP가 개발되기 전이었고 Java는 C보다 성능이 낮아 CGI를 C로 구현했으니 게시판이나 자료실 등도 C로 개발했습니다.규모가 큰 PC 통신은 DB를 사용했지만 사설 BBS나 01410 등에 들어가는 외부 업체는 File로 처리했습니다. 이 시기에 사설 BBS나 01410 서비스를 제공하는 업체들은 Workstation을 구입하거나 x86 계열을 구입해 운영체제 (SCO UNIX, Free BSD, Linux 등)를 사용했지만 이때 역시 C로 개발을 했었습니다. 이런 환경에서 점점 File 처리의 한계가 나타나기 시작했던 것이죠.C File lock 예)int iFd, iResult; iFd = open(“LockTest”,O_RDWR);  iResult = lockf(iFd, F_LOCK,10L); /* 필요한 작업 처리 */ close(iFd); 유저가 늘어나고 운영 체제 내부적으로 동시에 처리하는 프로세스가 증가하면서 자료가 깨지는 현상이 나타납니다. 개발자들은 어쩔 수없이 DB를 선택하기 시작했습니다.DB의 장점들DB를 도입하면 여러 가지 장점이 생깁니다. SQL 문장만 익히면 프로그램으로 일일이 구현해야 했던 것들을 명령어만으로 수행할 수 있고 자료의 무결성 또한 보장해 주며, 개발의 생산성까지 높입니다. 만약 특정 날짜의 자료들을 읽어와서 제목 순으로 보여줘야 할 경우, 프로그램으로 개발한 자료를 날짜 별로 읽어 배열에 담고 Quick sort 알고리즘을 적용해 정렬한 후 자료를 보여줘야 합니다. 하지만 DB에서 SQL 문장을 사용하면 간단하게 완성할 수 있습니다. SELECT * FROM TABLE WHERE DATETIME = 날짜 ORDER BY TITLE ; 조심 또 조심!하지만 DB 역시 만능은 아니기 때문에 모든 자료를 처리할 수는 없습니다. 예를 들어 문서(pdf, doc, hwp등) , 이미지(jpg, gif 등), 압축(zip,rar 등) 등의 바이너리 파일입니다. (물론 DB에서 BLOB 자료형을 지원하므로 하드웨어 자원과 성능만 받쳐준다면 불가능한 것은 아닙니다.) 하드웨어 자원과 성능에는 한계가 있기 때문에 DB로 해야 할 일과 하지 말아야 할 일을 구분해야 합니다. 만약 이를 생각하지 않고 DB에 모든 자료를 넣는다면 어떤 문제가 생길까요? 크게 두 가지가 있습니다.첫 번째는 바이너리를 파일을 읽고 쓸 때 발생하는 시간이 문제가 될 수 있습니다. 그 이유는 DB가 Connection Pool로 접속을 관장하는데, 이는 한정된 자원으로 최소한의 시간을 사용해야 많은 유저가 사용할 수 있기 때문입니다. 만약 바이너리 파일을 DB에 올리면서 오랜 시간 접속을 유지한다면 그만큼 다른 유저가 사용할 수 없을 테고, 결국은 DB에서 감당할 수 있는 유저의 수가 줄어들 것입니다.두 번째는 백업의 문제가 있습니다. 우리는 DB에 장애가 발생할 때를 대비해 DB 전체 백업을 합니다. 그런데 DB에 바이너리 파일이 들어가면 백업 시간이 많이 늘어나 원하는 시간 안에 백업을 하지 못하는 일이 발생할 수도 있습니다. 따라서 DB에 바이너리 파일을 넣을 때는 아주 적은 용량의 파일만 넣어야 합니다. 배치에 대하여: OLTP, OLAPDB 용량이 커지면 Query를 수행해도 원하는 결과를 볼 수 없고 DB에 부담을 많이 주는 Query가 발생합니다. 그래서 주기적으로 Query를 돌려 결과를 테이블에 넣고 필요할 때마다 이를 볼 수 있게 배치 처리를 하며 해결합니다. 일, 월, 년 단위의 집계 자료를 구축하면서 시스템에 부하를 줄 수 있기 때문에 보통 야간에 처리를 하죠. 그런데 만약 DB 용량이 너무 커져서 전일자 집계를 배치로 처리하지 못하는 일이 발생하면 어떻게 할까요?여기서 사용할 수 있는 것이 OLAP(OnLine Analytical Processing) DB입니다. 일반적으로 유저가 사용하는 건 OLTP(OnLine Transaction Processing) 입니다. 대표적으로 Oracle, MySQL PostgreSQL 등이 있습니다. 여기서 MySQL 을 제외하고 Oracle과 PostgreSQL 은 Partition, HASH 조인, Parallel을 지원하여 OLAP 환경에서도 어느 정도 사용 가능합니다.OLAP DB는 주로 DW 환경에서 사용하며 대표적으로 Teradata와 Oracle Exadata 등이 있습니다. OLAP DB 와 비교가 안 될 정도를 빠르게 배치 작업을 처리할 수 있습니다. (자세한 내용은 다음 글에서 설명하겠습니다.)Conclusion지금까지의 이야기를 정리하면 ‘여러 유저가 동시에 안정적으로 자료 처리를 하려면 DB를 사용하고, 자료의 양과 처리 형태(OLAP, OLTP) 에 따라 DB를 선택하면 된다’는 것이었습니다. 자세한 설명을 하자면 각 DB별 특성을 기술해야 하기 때문에 오늘은 전체적인 내용부터 살펴봤습니다. 다음 글에서는 유저가 사용하는 OLTP에 대해 살펴보겠습니다. 글한석종 부장 | R&D 데이터팀[email protected]#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 953

Jeykll에서 플러그인 없이 sitemape 생성하기

오늘은 구글에서 블로그를 검색할 수 있도록 설정하는데에서 크게 삽질했다.. 구글 웹마스터에 사이트맵을 등록해야 했는데 그 사이트맵이 자꾸 테스트를 통과못해서 3시간이나 삽질했다.. ㅠㅠ계속 삽질하다가 찾은 이유는.. _config.yml 파일에 url 속성이 없어서 url을 가져오지 못해 생긴 문제였다. ㅠㅠ 정말 허무하고 신나고.. 아무튼 모든 문제를 해결하여 성공적으로 완료했으니 그 방법에 대해 정리하도록 하겠음.참고한 블로그: 스우의 게임서버와 클라이언트! 미친듯이 영어 검색어들로 오류를 찾으며 삽질했었는데 의외로 한글 블로그에서 이 부분에 대해 언급되어 있어 해결할 수 있었다. 감사합니다 ㅠㅠsitemap 생성하기1. sitemap.xml 파일 생성블로그의 root 디렉토리에 sitemap.xml 파일 생성.2. sitemap.xml 파일 작성하단의 코드를 복사하여 만들어준 sitemap.xml 파일에 붙여넣기.            3. url 설정추가_config.yml 파일에 url 설정이 없는 경우 url 설정을 추가하여 sitemap.xml에서 site.url 변수값을 사용할 수 있도록 해줌. (이 부분 때문에 무한 삽질 ㅠㅠ)4. 구글 웹마스터 툴에서 테스트 혹은 제출구글 웹마스터 툴에서 테스트 혹은 제출을 통해 만들어준 sitemap이 제대로 동작하는지 확인.여태 GA나 기타 여러가지를 설정하느라 공개하지 않았는데 이제서야 공개합니다.제 블로그는 https://heelog.github.io/about/ 입니다!#트레바리 #개발자 #안드로이드 #앱개발 #Jeykll #백엔드 #인사이트 #경험공유

기업문화 엿볼 때, 더팀스

로그인

/