스토리 홈

인터뷰

피드

뉴스

조회수 592

오픈서베이 이건노 CTO가 ‘공동성장 가능해야 좋은 개발팀’이라 말하는 이유

오픈서베이 이건노 CTO(이하 폴)는 훌륭한 개발팀의 첫 단추로 ‘일단 매출이 나는 서비스 만들기’를 꼽는 현실주의 개발자입니다. 돈을 벌어야 생존 가능한 환경이 갖춰지고 이때부터 좋은 팀에 대해 고민을 할 수 있다면서요.  동시에 모두가 즐겁게 일하며 공동성장할 수 있는 팀이라는 이상을 꿈꿉니다. 지속가능한 서비스는 지속가능한 개발팀에서 비롯되는데, 즐겁지 않은 업무 환경에서는 그런 개발팀이 나올 수 없다고 믿기 때문입니다.  이런 현실주의적 몽상가는 주니어로 입사해 개발 조직 리더까지 지낸 이스트소프트에서의 경험으로 오픈서베이 개발팀을 리빌딩합니다. 구성원 모두가 즐겁게 일할 수 있는 환경을 위해 폴은 어떤 고민과 노력을 했을까요?   오픈서베이 이건노(폴) CTO   폴, 안녕하세요!  안녕하세요. 스타트업은 처음이라 직원 간 출퇴근 시간도 다르고 영어 이름으로 불리고 하는 게 익숙하지 않았는데, 17년 4월에 조인해서 벌써 만으로 2년째 다니고 있네요(웃음). 오픈서베이 개발팀을 총괄하고 있는 폴입니다.    현실주의적 몽상가의 오픈서베이 합류 계기가 궁금합니다(웃음).  경영진 미팅이었어요. 저는 주변 후배들이 이직 고민할 때도 그 회사의 경영진을 꼭 보라고 이야기하는 편인데, 제가 오픈서베이에 조인한 결정적인 계기도 하이(황희영 대표)와의 미팅이었어요. 하이를 만나보니 제품에 대한 애정이 정말 크다는 걸 느꼈어요. 대표가 제품에 애정이 많다는 뜻은 정말 하고 싶은 일을 한다는 의미잖아요. 사실 제품에는 관심 없고 주식, 엑싯 이런 거만 고민하는 대표도 있거든요. 그런데 하이처럼 제품에 애정이 정말 많은 대표가 있는 회사라면 저도 정말 믿고 다닐 수 있겠다고 생각했어요.  “대표가 제품에 애정이 많다는 뜻은  정말 하고 싶은 일을 한다는 의미라고 생각해요”   현재 개발팀 구성은 어떻게 되나요?  오픈서베이 개발팀은 현재 프로젝트 매니저 3명, 프론트엔드 개발자 2명, 백엔드 개발자 4명, 그리고 앱 개발자 2명까지 총 11명으로 구성돼 있어요. 최근 회사가 빠르게 성장하면서, 올해는 개발팀도 여러모로 성장하는 한 해가 될 것 같아서 기대됩니다.   팀이 성장하면 어떤 변화가 생기나요? 기업의 규모는 천차만별이지만 업무의 범위는 크게 다르지 않아요. 그러다 보니 인원이 적은 스타트업은 한 명이 얕고 넓게 일하는 구조인데, 회사가 성장하면서 직원이 늘면 각 구성원의 역할을 좀 더 세분화하고 전문화할 수 있게 돼요.  오픈서베이 개발팀도 기존에는 프로젝트 매니저가 기획·QA 등 다양한 역할을 모두 소화했다면 최근에는 좀 더 한 분야에 집중해서 깊게 일하는 식으로 역할의 변화를 주고 있어요. 그래서 서비스 기획자·백엔드 개발자·QA 엔지니어 등 다양한 직군을 채용하고 있고요.   오픈서베이 개발팀의 채용 정보를 알고 싶다면? (링크)   조셉(김경만 오베이 PM) 인터뷰를 보면, 개발팀 세미나 제도를 통해 긍정적인 영향을 많이 받은 것 같아요.  사실 세미나 자체가 주니어 개발자의 발언 기회를 위한 제도예요. 개발자는 스스로 제품이나 기술에 대해 주도적으로 고민할 때 역량이 극대화된다고 생각해요. 그런데 주니어 시절에는 아무래도 고민의 결과를 제품이나 기술에 반영하기 힘들죠. 세미나는 이런 갈증을 느끼는 주니어가 좀 더 의욕적으로 일할 수 있는 창구를 제공하기 위해 도입했어요. 구성원분들 덕에 우리 개발팀에는 잘 적용됐지만, 무작정 도입만 한다고 알아서 잘 작동하진 않는 것 같아요. 세미나가 주니어에게는 동등하게 업무 커뮤니케이션을 할 기회지만, 시니어에게는 사실 귀찮고 번거로운 일이 될 수 있거든요. 해왔던 대로 하는 게 편하다는 생각도 들 수 있고 새로운 기술을 팀에 적용하는 과정에서 드는 리소스도 크고요.    주니어 관점일 때는 생각지도 못했는데 이야기를 들어보니 그렇네요.  사실 저도 마찬가지예요. 저도 CTO이기 전에 개발자니까 주니어 개발자의 신선한 아이디어나 최신 기술을 업무에 적용해보고 싶은 마음도 있어요. 주니어분들의 세미나를 통해 저도 새롭게 배우는 점이 많아요. 그런데 CTO 입장에서는 이를 도입했을 때 우리 제품이나 업무 환경에 어떤 영향이 미칠지를 계산하지 않을 수 없어요. 좋아 보인다고 무작정 도입하면 문제가 터졌을 때 대처할 준비가 안 된 거니까 부담이 너무 크거든요.  그래서 이런 제도는 신중하게 도입하고 모든 구성원이 꾸준히 노력해야 잘 유지되는 것 같아요. 저도 간혹 이런 데서 내적 갈등이 생길 때마다 세미나 제도의 긍정적인 영향을 생각하며 이겨내는 편이에요(웃음). 실제로도 팀 업무 환경 개선이나 기술 수준 향상에 많은 도움이 되고 있고요.   “주니어에게도 고민 내용을 공유할 기회를 주려고 해요. 개발자는 주도적으로 제품을 고민할 때 역량이 극대화되거든요”    ‘코드리뷰’도 비슷한 제도라고 들었어요.  맞아요. 코드리뷰는 제품이나 소프트웨어의 변경사항을 체계적으로 관리하는 형상 관리의 일환이기도 해요. 오픈서베이 개발팀은 각 개발 담당자가 코드를 업데이트하면 슬랙에 자동으로 알림이 와요. 그럼 구성원들은 자유롭게 코드를 열어보고 의견을 내는 거죠. 여기서 담당자가 놓친 오류나 실수를 점검해 주거나, 더 효율적이고 경제적인 코드에 대한 의견을 주고받기도 해요.    그럼 코드리뷰는 보통 어떤 식으로 진행되나요?  마이너한 코드는 비대면 방식으로 온라인에서 수시로 리뷰를 진행하는데, 메이저한 코드 업데이트가 있을 때는 따로 회의를 열어서도 해요. 경우에 따라 방식은 다르지만 적극적으로 진행하려는 편이에요.  코드리뷰 제도를 도입하고 장려하는 이유는 명확해요. 개발자는 여럿이 함께 일할수록 시너지가 난다고 생각하거든요. 개발자 개인의 실력이 아무리 좋아도 본인이 작성한 코드의 오류나 실수를 다 잡아내기는 힘드니까요.  코드리뷰 자체가 구성원들이 의견을 주고받으면서 시스템 전반을 두루 이해하고 배우는 과정이기도 해요. 탄탄한 개발팀은 한명의 개발자가 하나의 시스템을 맡는 게 아니라 여러 구성원이 여러 시스템을 보조하는 구조거든요. 그래야 특정 담당자가 공백일 때 다른 구성원이 대신 문제를 처리해줄 수 있고, 개인의 부담도 줄어드니까요.  개발팀의 조셉과 로빈이 코드리뷰 중인 화면   그러다 자칫 잘못하면 서로의 감정을 상하게 하거나, 주니어를 향한 시니어의 훈육 공간으로 전락할 수도 있을 것 같아요.  그래서 어떤 제도가 좋다고 해도 무턱대고 받아들이면 안 된다는 생각을 해요. 도입에 앞서 서로 인신공격을 하면 안 된다거나 리뷰 내용은 공개된 채널에서만 주고받아야 한다는 등의 세세한 규칙을 정할 필요가 있어요. 코드에 대한 의견이 상대방을 헐뜯으려는 게 아니라 제품 개선과 모두의 성장을 위해서라는 상호 신뢰도 충분히 형성돼 있어야 하고요.    구성원들이 서로 자극을 주거나 보고 배우면서 함께 성장하는 팀을 지향하는군요. 정확히 맞아요. 개발자는 연차나 경력과 무관하게 개인별로 역량의 편차가 좀 있는 편이에요. 하나의 팀에 똑같은 수준의 역량을 갖춘 개발자만 모여 있는 게 오히려 부자연스러울 정도로요. 그래서 서로 다른 역량을 가진 팀원들이 함께 일할 때 시너지가 날 수 있는 방법이 필요해요.  저도 예전에는 이렇게 생각하지 않았어요. 일 잘하는 개발자에게 일을 몰아 주고 그 친구가 일을 다 하게 했죠. 거기에 따라 보상도 많이 주고요. 처음에는 일이 되는 것 처럼 보였는데 그런 식으로 한두 명이 일을 많이 하니까 금방 지치더라고요. 결국 서로 도와 가면서 팀으로 일하는 게 필요하더라고요.   그렇게 생각한 특별한 계기가 있나요? 개발팀장 시절에 새로 합류한 구성원 한명이 기억나네요. 좀 독특했어요. 에러·버그 등 문제가 생기면 원인을 파악하고 해결하는 개발업무를 ‘트러블슛(Trouble Shoot)’이라고 하는데, 그 친구는 다른 구성원이 담당하는 시스템의 트러블슛도 함께 고민하는데 시간을 많이 쓰더라고요.  사실 전 그때까지만 해도 팀원은 자신에게 주어진 일을 잘하면 되고 팀장은 역량에 맞춰 일을 잘 분배해주면 된다고 생각했어요. 그래서 이 친구 모습을 보면서 왜 자기 일은 안 하고 다른 사람 일을 야근까지 하면서 보고 있을까 생각했었죠.  그런데 시간이 지날수록 팀원들의 역량과 그 친구의 역량이 동반 성장하더라고요. 그것도 눈에 바로 보일 정도로 빠르게요. 서로 자극을 주고 함께 고민하면서 결국은 팀 전체가 성장한 거예요. 서로 시너지가 났던 거죠. 그러면서 자연스럽게 성과도 잘 나왔어요.   하지만 의지만으로는 자신의 업무를 넘어서 다른 구성원의 업무를 함께 고민하기는 힘들 것 같아요. 팀에서 함께 일할 수 있는 환경을 잘 만들어 주는 게 중요했던 것 같아요. 이슈 관리, 형상 관리, 버전 관리, 테스트, 릴리즈 등 개발과 운영을 위해서 필요한 기본적인 이해가 서로 있어야 해요. 그리고 개발 업무에만 집중할 수 있게 빌드, 배포와 같은 반복적인 업무의 자동화나 운영 툴 개발과 같은 것도 중요합니다.  그리고 가장 중요한 건 서로에 대한 배려심이라고 생각해요. 이번에 도와주고 다음에 도움을 받고 하는 서로에 대한 배려가 없다면 함께 일하는 문화는 자리 잡을 수 없어요. 일이 나뉘어 있기도 하지만 결국 같은 목적으로 일하고 있는 동료와 팀이라는 것을 알고 서로 배려해 주는 거죠. “좋은 제품은 좋은 개발 환경에서 나온다고 생각해요.  그래서 좋은 환경을 만들어주고 싶어요”   레드(김승엽 개발자) 인터뷰만 봐도, 로빈(권장호 개발자)를 통해 자극을 받아 공동성장하는 모습이 인상적이더라고요. 폴은 이런 레드에게 팀장을 넘어 멘토로서도 다양한 조언을 해주려는 것 같아요. 개발팀 구성원들이 회사 안에서의 성장에 갇히길 바라지 않아요. 좋은 인연으로 만났는데 회사라는 틀 안에서 팀장과 팀원 관계로 한정할 필요는 없다고 생각해요. 틀을 벗어나면 제가 할 수 있는 조언의 범위도 넓어지고 깊이도 훨씬 풍부해지기도 하고요. 인생 관점에서 조언해줄 수 있잖아요. 그럼 반대로 저도 레드를 비롯한 다른 구성원들을 통해 많이 보고 배울 수 있게 돼요.    쉽게 가지기 힘든 생각 같은데, 폴의 주니어 때 팀장님을 통해 배우신 건가요? 아니요. 사실 저는 팀장님과의 기억이 많지 않아요. 주니어로 입사한 지 2년 만에 엉겁결에 팀장이 됐거든요. 처음에는 기존 팀장님이 갑작스럽게 자리를 비우면서 팀장 업무를 임시로 맡았어요. 이때는 인사권 같은 건 전혀 없고 그냥 팀 업무를 할당받아서 각 구성원에게 배분하는 역할만 잠깐 한다고 생각했죠. 그런데 회사에서 “이왕 한 거 너가 계속해라”라며 아예 팀장을 시켜버리더라고요. 그때는 많이 당황했어요. 저도 완전 주니어일 때라 좋은 팀장님 밑에서 이것저것 배우고 싶었거든요. 그런데 이렇게 일찍 팀장이 되면서 이젠 내게 가르쳐줄 사람이 없는 걸까 싶어서 앞이 깜깜했어요 “나도 개발 잘할 수 있는데 왜 매니저 역할을 주는 거지? 내가 개발을 잘 못 한다는 건가?”라는 삐뚤어진 생각도 했고요. 얼마나 막막했으면 구글에 ‘팀장이 하는 일’ 같은 걸 검색한 적도 있어요(웃음).   위기를 어떻게 극복했을지 궁금해요. 그 과정이 지금의 폴 인사 철학에 많은 영향을 줬을 것 같아요.  그래도 어쨌든 팀장 일을 해내야 하니까 시선을 좀 넓혀봤어요. 제게는 이제 직속 팀장은 없지만 저보다 경력 많고 실력 좋은 선배 개발자가 팀원으로 있었고, 다른 팀에 훌륭한 시니어 개발자나 선배 팀장님도 계셨죠. 시선을 넓히니 오히려 제가 보고 배울 사람이 더 많더라고요. 그분들에게 궁금한 걸 적극적으로 묻거나 보고 배우면서 중요한 시기를 잘 보낼 수 있었던 것 같아요.  그때 처음으로 깨달은 게 아닐까 싶어요. 제가 엉겁결에 팀장이 되면서 다른 많은 분들을 보면서 보고 배운 것처럼, 팀원들도 꼭 팀장이 아니더라도 다른 팀원들을 서로 보고 배우면서 긍정적인 자극을 받으며 성장할 수 있다는 걸요. ‘팀장은 팀원에게 가르쳐주는 사람이야’란 생각에 갇혀 있었으면 절대 깨닫지 못했을 것 같아요. 얘기를 해보니 정말로 저 난관을 통해서 지금과 같은 생각을 할 수 있게 된 것 같네요(웃음).  “팀장과 팀원이라는 틀을 벗어나면 훨씬 풍부하게 긍정적인 자극을 주고받을 수 있어요”   폴의 경험과 고민이 결국은 팀 업무 환경이나 문화를 통해 드러나는 것 같네요. 그렇게 봐주시면 정말 고맙죠. 사실 어떻게 하면 더 일하기 좋은 회사와 팀을 만들 수 있을지는 계속 고민되는 부분이에요. 돈 많이 주는 회사가 일하기 좋은 회사라고 간단히 생각해버릴 수도 있는데, 저는 일하기 좋은 회사를 이루는 요건은 좀 더 다양한 것 같거든요. 일단 하는 일이 어떤 가치를 가지고 있고 이것을 쓰는 사람들에게 어떤 가치를 제공해 주는지 알고 있어야 해요. 그래야 내가 하는 일에서 보람을 찾을 수 있으니까요. 물론, 회사에서도 보람을 가질 수 있는 방법을 함께 고민해 줘야겠죠.  보람만으로 회사에 다닐 수 없으니 역세권 사무실, 맛있는 커피, 좋은 경영진, 좋은 팀원 등 중요한 요건들이 엄청 많은데요. 오픈서베이는 서로에게 자극을 주거나 보고 배우며 스스로 성장할 수 있는 좋은 구성원들이 많은 것 같아요. 저도 요즘  구성원들에게 많이 배우고 있습니다.    마지막 질문입니다. 좋은 팀을 위한 폴의 역할은 무엇인가요?  장점을 찾아 주는 게 저의 중요한 일이죠. 잘하는 일을 해야 역량도 극대화되거든요. 장점이 없는 사람은 없다고 생각해요. 장점을 잘 모르는 친구들이 있는데 이런 친구들의 장점을 찾아주고 또 그 장점이 회사에서 잘 발현되도록 도와주는 조력자 역할을 잘 해내는 게 제가 할 일이라고 생각해요.  좋은 장점이 잘 발현되면 개발자는 한 단계 더 성장할 기회가 생겨요. 예를 들어 초기 제품 기능을 빠르게 잘 만드는 게 장점인 친구는 신사업 중심의 일을 할 수 있게 해주면 그 역량이 잘 발현되거든요. 거기서 가치를 인정받고 탄력이 붙으면 지속적으로 좋은 성과를 낼 수 있게 되고요. 이 과정을 저는 “알을 깨고 나온다”라고 표현하는데, 이렇게 알을 깨고 나오면 좀 더 제품이 주는 본질적인 가치를 알게 된다고 생각해요. 개발자로서 일하는 가치와 방법을 알게 되는 거죠. 그런 만큼 오픈서베이의 예비 구성원분들도 좋은 자극을 받으며 성장할 수 있는 계기가 되셨으면 좋겠어요. 회사를 그저 출근해서 일만 해주고 돈을 받아가는 공간이라고 생각하면 회사 다니기가 우울하잖아요. 사람마다 얻어갈 수 있는 건 다 다르겠지만, 긍정적인 영향을 받아 성장하는 계기가 오픈서베이가 될 수 있을 것같아요.    “폴과 함께 즐겁게 일하고 싶으시다면 지금 바로 오픈서베이 입사지원을 해보세요”  
조회수 6314

[SQL 데이터분석] 증감율 구하는 간단한 방법

sql에서는 = 등호가 비교연산자로 사용됩니다.대신 := 이렇게 콜론(:)과 등호(=)를 같이 쓰면 대입연산자로 쓸 수 있어요.select @prev := users.id // @prev 라는 임시변수에 users.id 값을 넣어라. from users가입일자로 사용자수를 구해보면, 아래처럼 가입일로 group_by 를 해서 구하죠.select date(created_at) as '가입일' , count(1) as '가입자수' from users group by 1 order by 1 desc;// 가입일 | 가입자수 // --------------------------- // 2017-08-02 100 // 2017-08-01 50그럼 전일 대비 증감율을 구하려면 어떻게 할까요?select date(created_at) as '가입일' , @prev as '전일 가입자수' , (count(1) - @prev) / @prev as '증감율' , @prev := count(1) as '가입자수' from users group by 1 order by 1 desc;// 가입일 | 전일 가입자수 | 증감율 | 가입자수 // -------------------------------------------------------- // 2017-08-02 50 1.0 100 // 2017-08-01 50 0 50증감율을 계산하는 count(1) / @prev까지는 @prev 에 전일 가입자수가 저장되어 있구요.@prev := count(1) 에서 당일 가입자수로 할당이 됩니다.저는 := 이 연산자를 알기 전엔 self-join 형태로 증감율을 구했는데데이터를 가오는 속도는 := 이 연산자가 훨씬 빠른것 같습니다.다음엔 self-join 으로 증감율을 구하는 법도 한 번 올려볼께요.#티엘엑스 #TLX #개발 #개발팀 #개발자 #꿀팁 #인사이트 #조언
조회수 2922

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 #모니터링 #인사이트 #서버개발 #운영 #꿀팁
조회수 1188

EOS Smart Contract 를 위한 준비

EOS Smart Contract 를 위한 준비와 토큰 발행 그리고 C++를 활용해 토큰의 간단한 기능을 개발해 보겠습니다.환경 구성 및 지갑 생성은 SAM 님의 아래 2글을 참고해 주시기 바립니다.EOS — 설치 및 실행 (1/2)EOS — 동작구조 및 환경설정(2/2)지갑 생성하기SAM 님의 포스트를 참고 하셨다면 아마 다음과 같이 ‘default’ (별도의 이름을 지정하지 않았을 시) 지갑을 생성 하셨을 겁니다.이 지갑을 사용하여 계정을 Create 한 후 Key 를 Import 하겠습니다.Key 생성하기$ cleos create key위 명령을 실행 하시면 다음과 같은 화면을 얻을 수 있습니다.create key 명령의 결과**주의 : Private Key는 Public Key의 소유를 증명하는 중요한 개념으로 절대 타인에게 노출하면 안됩니다.AdditionalKey 생성 후 지갑에 import 하기 귀찮으시다면 생성된 지갑에서 바로 Key 를 생성하셔도 됩니다.$ cleos wallet create_key위와같이 key가 생성 됩니다. 하지만 public key 만 보이기 때문에 하단 명령 입력 후 지갑 key를 입력하면 private key를 확인할 수 있습니다.$ cleos wallet private_keys지갑에 Key import하기지갑은 Public Key — Private Key를 저장하는 저장소 입니다. 생성된 키를 지갑에 저장하기 위해 다음과 같은 명령어를 입력합니다.$ cleos wallet import-n : 옵션을 사용하면 지갑의 이름을 지정합니다. 지정하지 않는다면 기본 생성된 default 지갑으로 지정됩니다.위 명령을 입력 하면 key 가 임포트 되었다는 결과를 확인 할 수 있습니다.** 만약 지갑을 Unlock 한 상태가 아니라면 ‘private key: Error 3120003: Locked wallet’ Exception 이 나옵니다.unlock 을 위해 다음 명령을 실행한 후 wallet 생성시 저장했던 Key를 입력하여 Unlocked 상태로 만들어 줍니다.$ cleos wallet unlock password: Unlocked: default(Optional) 지갑에 저장된 Key 리스트 확인다음 명령어를 입력하여 지갑에 key 가 잘 import 됐는지 확인합니다.$ cleos wallet keys계정 생성eosio.token 이라는 이름으로 계정을 생성하도록 하겠습니다.** 지갑과 Key 그리고 계정에 관해서는 Hexlant 미디움에 게재될 예정입니다.$ cleos create account eosio eosio.token EOS63kstp8kthzJY3rAotp1LAxUDbWk4MywReG578R2ddbktrDHYKcreator : eosioaccount name : eosio.tokenowner key : 지갑에 import 된 keyAdditional본 포스팅은 local 환경에서 빌드 후 System Contract 들이 적용되지 않은 상황을 가정하였습니다. 만약 Public Network 환경에서 접속 시 eosio 와 eosio.token을 사용할 수 없습니다.또한 계정이름은 다음과 같은 규칙을 따릅니다.- 12문자- 12345abcdefghijklmnopqrstuvwxyz 만 사용 가능** 만약 ‘Error 3090003: provided keys, permissions, and delays do not satisfy declared authorizations’ 에러 발생 시 eosio 에 대한 key 를 지갑에 import 해야 합니다.eosio 에 대한 정보는 다음과 같습니다.public key: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CVprivate key: 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3위 과정을 모두 마쳤다면, EOS 지갑과 키 그리고 계정에 대한 권한을 모두 가지고 있는 상태가 됩니다. 다음 포스팅에서는 이 계정을 사용 하여 Token 을 발행하는 방법을 알아보도록 하겠습니다.감사합니다#헥슬란트 #HEXLANT #블록체인 #개발자 #개발팀 #기술기업 #기술중심
조회수 653

스마트링크 시즌2 : 은하철도 프로젝트

스마트링크 시즌2 채용공고에 보내주신 뜨거운 반응 감사합니다!! 정말 많은 분들의 열정과 관심에 분주하지만 즐거운 만남들을 여럿 가질 수 있었습니다. 그리고 드디어!! 은하철도에 함께 탑승할 5명의 동료가 최종 선발되셨습니다. 뜨거운 관심과 지원에 다시 한번 감사드리며 아쉽지만 이번에 함께하지 못한 분들도 저희가 좌석을 보다 넉넉하게 꾸리게되면 함께할 수 있는 날이 오면 좋겠습니다.여기서 잠깐!그렇다고해서 스마트링크 시즌2 채용이 완전히 완료된 것은 아닙니다. 스마트링크는 언제나 좋은 분들과 함께할 준비가 되어있습니다. 상시채용 형태로 계속 이어나갈 예정이니 스마트링크 은하철도에 관심있는 분들은 언제나 문을 두드려 주시면 감사하겠습니다. 그럼 새로운 동료들과 슬슬 날아갈 준비를 하러 이만 :) - 2019. 6. 25 어느 기분좋은 화요일---------------------------------------------------------------------안녕하세요. 스마트링크의 Mike 라고 합니다. 기획과 마케팅을 담당하고있죠. 스마트링크는 작년부터 저희와 함께할 분들을 애타게 찾고 있습니다. 그 사이에 많은 분들을 뵙고 기회를 도모하기도 했습니다. 여러 다양한 경험을 축적하기도 했구요. 이렇게 여러 과정을 거치던 와중에 그동안 아기다리고기다리던, 그리고 열심히 준비했던 성과들이 하나둘 나오기 시작했습니다. 마치 미드에서 시즌이 바뀌는 것처럼 우리에게 근본적인 패러다임의 변화가 있었다랄까요? 이런 변화를 염두하며 지난 채용공고를 봤는데...안되겠어. 다시 써야겠어!그래서 이렇게 시즌2 만을 위한 채용공고를 작성하는 중입니다. 스마트링크의 시즌2는 어떻게 진행되고 그래서 어떤 분들과 함께하고 싶은지 지금부터 이런저런 이야기를 해보도록 하겠습니다.  뭐하는 회사임?스마트링크는 소프트웨어 개발사 입니다. 끝. 참 쉽죠? 그런데 세상은 넓고 소프트웨어 개발사는 넘치고 넘칩니다. 그런데 뭐가 그렇게 다른가? 라고 물으신면! MVP(Minimum Viable Product) 소프트웨어 개발 컨설팅 전문 업체라고 말씀드릴 수 있겠습니다. 이게 뭔말이냐 하면 덩치 큰 SI도 진행하지만 주로 스타트업 또는 초기 사업 아이디어가 빠르게 시장에 진입할 수 있도록 기획, 디자인, 개발, 테스팅, 데브옵스까지 (물론 견적에 따라 달라집니다! 단호! ㅋㅋ) 풀 패키지로 작업하는걸 좋아하는 업체라고 보시면 되겠네요. 그래서 프로젝트 기간이 짧고 굵은게 많죠. 늘어지는 프로젝트 별로 안좋아 합니다. AtoZ로 빠르게, 효율적으로, 효과적으로! 일하는걸 선호하고 실제로 그렇게 일을 진행합니다. 그런데 아마 이런 의문이 드실거에요. 왜 작은일 맡는걸 좋아하지? 사실, 규모가 중요한게 아니라 AtoZ 라는게 중요합니다. (심지어 예산 높은 큰 프로젝트 요청을 까기도 합니다. 꽤 자주;;) 그 이유는? 면접때 질문 주시면 신나게 답해드리도록 하죠 ㅎㅎ 다 이유가 있습니다!  누가 일하고 있는데?AtoZ, 풀패키지로 일하는걸 좋아한다는 대목에서 아시겠지만 있을 사람은 다 있습니다. 기획, 디자인, 개발 인력 모두 있구요. 그래야 일이 되겠죠? 다만 현재 사람수가 많지는 않아요. 소수정예! 하지만 모두 각 분야에서 베테랑들이라 자부합니다. 특히 개발사이니만큼 모든 분야는 개발을 중심으로 돌아가구요, 각 영역을 생판 모르는 분야로 치부하지않고 서로를 끊임없이 알아가고 파악하고 융화되는 방식으로 일합니다. 예를 들면 기획과 개발은 DB구조나 Convention을 공유하고, 디자인은 Front-end 최적화된 디자인과 UI/UX를 뽑아냅니다. 여기서 일일이 언급하기는 뭐하지만 일 잘하는 사람들이 모여있다고 자부하고 있고, 앞으로 동료들도 일 잘하는 사람을 가장 원하고 바라고 있습니다. 일을 잘한다는 기준이 절대적일 수는 없겠지만, 예를 들면 이런거죠. 최대한 정확하고, 낭비나 누수없이, 빠르게 문제를 해결하기 위해 계속 꼼수를 쓰는 사람들! 이랄까요? 세상에 (노는것 포함) 할일이 얼마나 많은데! 극단적 효율을 추구하는 집단이라고 보시면 되겠습니다.  제대로된 꼼수는 사실 탄탄한 정석 바탕에서 나올 수 있다죠.다만 아직 목마릅니다. 일을 더 잘하고 싶어요. 그래서 우리는 시즌1을 보내면서 내부를 다지는 일도 지속적으로 탄탄하게 단내 나도록 해왔습니다. 그리고 슬슬 그 결과들이 눈 앞에 펼쳐지고 있네요. 그래서 결심할 수 있었습니다. 이제 확장의 시기가 왔다! 시즌2로 나아갈 때가 되었다!   시즌2라...시즌1엔 어떻게 했고, 시즌2에서는 어떻게 할건데??시즌1에서 스마트링크 작업방식을 정의내리자면 이렇습니다.천상천하유아독존!!네, 그렇습니다. 각자 부여된 일을 독자적으로 수행해서 최종 결과물을 내는 방식이었죠. 내부적으로 진행하는 일이야 Agile 방법론을 적극 도입한다해도 외부 프로젝트를 진행하는 경우에는 어쩔 수 없는 Waterfall 방식이었습니다. 기획 작업을 마무리하면, 받아서 디자인 작업을 하고, 마지막으로 개발을 완료하는 방식이었죠. 특히 개발은 Ownership을 기반으로한 책임개발제(라 쓰고 독박이라 읽는다)로 운영되고 있었습니다. 이 방식으로 운영했던 이유는 모호한 업무분담과 그로 인한 누수를 최소화하기 위한 방책이었죠. 사공이 많으면 배가 산으로 간다는 속설을 극복할 방법이기도 했구요. 실력있는 개발자를 중심으로 이 방법은 한동안 잘 유지되는듯 했습니다. 그런데 계속 이렇게 운영하다보니 이런 상황이 발생했습니다.될놈될, 안될안 ㅠ 개발 결과물의 빈부격차 ㅠ책임개발제는 결과물이 사람에 의해 결정된다는 의미 입니다. 실무자의 경험이나 실력에 따라 천차만별일 수 밖에 없는거죠. 그러다보니 퀄리티 확보를 위해서는 결국 다시 여러 사람들의 손을 거쳐야하는 이슈들이 종종 발생했습니다. 사실 이는 필연적인 부분일지도 모르겠습니다. Full-Stack 개발을 추구한다해도 결국 저마다 가지고있는 개성과 강점은 다르니까요. 그럼에도 불구하고 지금까지는 딱히 문제 없었습니다. 다만 미래를 염두하면 걱정되는 부분들이 있더군요. 인력이 늘어나고 보다 다양한 사람들이 함께하게된다면 과연 이 시스템이 버틸 수 있을까? 라는 근본적 의문이 드는겁니다. (그래서 이번 채용은 Front-end와 Back-end를 구분해서 진행합니다.) 그리고...Ownership이고 뭐고 다 좋은데 왜 외롭냐...외롭기도 하더군요. 기획, 디자인, 개발 모두가 그랬고 특히 개발자들은 그냥 말 그대로 굉장히 외롭게 되었습니다. 복작이며 한 팀으로 일하는 방식이라기보다는 프리랜서들 조합과 같은 이 상황은 구성원들을 각자 개인의 울타리로 고립시키는 결과로 이어졌습니다. 기획, 디자인, 개발은 각자 나름의 방식으로 일하면 결국 서로 Sync를 맞추기 위한 작업이 추가될 수 밖에 없습니다. 효과적인 분업도 좋지만 결국 우리는 함께 일하는 회사라는 공동체 안에 있습니다. 능률, 효율과 더불어 협업도 굉장히 중요하죠. 적당한 균형점을 찾는게 중요해졌습니다. 앞으로 사공은 엄청 많아질거거든요. 그것도 다양한 특징과 강점을 가진 각양각색의 사공들이 말이죠. 이렇게 사공이 많아져도 배가 산으로 가면 안되죠.  우주로 가는건 괜찮을지도... 사공이 많은 배라면 차라리 이런걸 만들면 어떨까?사공이 많은 멋진 배를 만드는 방법이란 뭘까? 누수 없는 업무처리와 능률을 모두 잡는 방법은 무엇일까? 이런 고민을 하던 와중에 우리에게 필요한건 엔진이란걸 알게 되었습니다. 이 엔진은 이런 조합으로 구성되어야 했습니다.목표한 기능을 정확하고 안정적으로 구현할 수 있는 동력자칫 시야를 좁힐 수 있는 미시적 요소들을 과감하게 skip할 수 있는 돌파력누수없이 매끄럽게 진행되는 안정적 업무 전달계통그리고 이 과정을 우리 모두 함께하고 있다는 응집력 뭔가 뜬구름 잡는 이야기들로 보일지도 모르겠습니다. 하지만 이 조합은 연역적이라기보다는 귀납적입니다. 실제 우리가 고민해온 부분을 해결하고자한 일들의 결과물이 위와 같은 역할을 하고있다는 것이 보다 정확한 표현이겠네요. 그리고 이 엔진은 한 단어로 귀결됩니다.그렇습니다. 컴포넌트.그리고 우리는 Components 를 엔진 삼아 우주전함 대신 은하철도 시스템을 구축했습니다. 이른바 스마트링크 시즌2 은하철도 프로젝트!  은하철도 프로젝트라니... 뭥미?? - 스마트링크 시즌2 은하철도 프로젝트보통 스타트업이 성장하는 모습을 로켓에 비유하기도 합니다. 빠르고 가파르게 수직상승하는 모습을 본딴 것이겠죠. 하지만 우리는 조금 다르게 생각합니다. 한가지 아이템으로 절체절명의 상황을 이겨내고 급성장하는 방법도 좋겠지만 우리는 오히려 안정성과 지속가능성에 더 초점을 맞추고 있습니다. 이를 위해서 스마트링크는 꽤 오랜시간 공들여 Component 구축을 진행했고 그 결실이 드디어 빛을 봤습니다! 장기적으로 효율적이고도 생산적인 구조를 위해 이제까지의 내부 프로세스를 과감하게 변경하고 새롭게 아래와 같은 구조로 진행합니다. 반영구적 Components 엔진을 돌리면서 모두를 리딩하는 곳, 기관실우리의 엔진 Components를 계속 다듬고 발전시킵니다. 내부 프로젝트도 진행하죠.실무자들의 즐거운 놀이터, 1등석이미 잘 구축된 Components로 안락하고 쾌적하고 빠르게 할당된 프로젝트를 진행합니다. 특히 개발자에게는 상용 서비스에서 활용 가능한 React Skill을 마음껏 연마하는 과정이기도 합니다 :)초심자들의 탄탄한 학습의 장, 일반석숙련도와 경험이 적은 초보자들은 체계적인 교육과 안정적인 Components 활용법을 익히고 1등석에 옮겨탈 준비를 합니다.뭔가 괜찮은 열차죠? 은하철도 프로젝트는 크게 이런 구조로 작동하게 됩니다. 이번 채용공고를 통해 모시고자하는 자리는 1등석과 일반석 입니다.베테랑들은 탑승한 동료들을 위해 열심히 기관실을 돌리면서 최대한 안정적이고 쾌적한 작업환경을 위해 움직입니다. 물론 내부적인 방향과 비전을 위한 고민, 세팅도 주도하겠지만 최종적으로는 모든 구성원들과 함께 공유하고 의견을 모아 진행합니다. 기관실과 객석들 역시 유기적이고 탄탄하게 연결돼야 하니까요.가즈아~ 기관실은 구비되어있다!!기관실과 객석이 설국열차처럼 꽉 막혀있지 않습니다. 본인이 원한다면 일정정도 열정과 의지로 기관실에 옮겨탈 수도 있습니다. 이건 순전히 본인의 취향에 달려있다고 생각해요. 세상은 넓고 사람은 다양하고 가치관도 제각각입니다. 그저 선택의 문제일 뿐이죠. 우리는 그저 보다 많은 사람들이 우리의 은하철도에 올라탈 수 있기를 바랄 뿐입니다. 그래서 선택할 수 있는 자리를 마련한 것 뿐이구요. 실무자들이 실무에만 집중할 수 있는 구조는 회사라는 공동체에서 매우 중요하다고 생각합니다. 선택은 여러분의 몫입니다.  1등석과 일반석이라... 좀 더 설명해보지?고민의 공간, 기관실.1등석과 일반석을 설명하자면 먼저 기관실 설명을 하지 않을 수 없습니다. 기관실은 끊임없이 소프트웨어 Core를 생산하는 곳이라고 보시면 되겠습니다. 그 중심은 당연히 Components 겠죠. 세상의 모든 서비스를 커버하겠다는 야심과 함께 사용자에게는 쾌적한 경험을, 개발자들에게는 효율적이고 신속한 개발환경을 선사하는 영역입니다. 그래서 개발언어를 잘 이해하고 보다 핵심적인 영역을 손대고 싶은 사람에게 적합합니다. 실력도 당연히 동반되어야겠지만 이제까지 경험으로 보자면 자기주도적인 취향도 핵심이더군요. 기관실은 이런 사람들이 모여있습니다. 사용자경험 뿐 아니라 내부 개발진들의 의견을 끊임없이 추적하고 해결하는 고민의 공간 입니다.기관실이 잘 할테니까 팔로팔로미~ ㅎㅎ효율의 공간, 1등석위에서 '취향'에 대해 언급했는데요. 1등석은 취향에 따라 자신의 업무방향을 선택할 수 있는 공간 입니다. 잘 짜여진 Components와 Convention에 따라 실제 상용서비스를 만들거나 관리하는 역할을 합니다. 고민의 폭은 줄이고, 실질적인 결과물에 초점을 맞추는 효율의 공간이라고 보시면 되겠어요. 새로운 결과물을 세상에 선보이고, 이들을 잘 작동시키는 사람들이 모여있는 곳입니다. 그러다가 지금 쓰고있는 Components 개선이 좀 더 필요할거 같다 싶으면 자체적으로 해결해도되고 기관실로 넘길 수도 있습니다. 이 부분이 바로 취향의 영역이라고 볼 수 있는데요. 본인의 실력과 더불어 이 취향에 따라서 기관실로 갈지, 1등석에서 작업할지 결정할 수도 있습니다.학습의 공간, 일반석일반석은 다른 말로 초심자의 영역이라고 보시면 되겠습니다. 세상은 급변하고 소프트웨어 변화 역시 엄청나죠. 우리는 끊임없이 학습하고 발전해야만하는 영역에서 일하고 있습니다. 그래서 이 부분을 절대 간과해선 안된다고 생각하고 있어요. 다만 취미 정도의 학습이라면 각자 개인의 소양 정도로 진행하는 것이 적절하겠죠. 일반석은 실제 상용 서비스에 적용 가능한 수준의 학습이 이뤄지는 공간입니다. 그 핵심은 React, Meteor, MongoDB 라고 보시면 되겠구요. 고퀄 서비스들을 실제로 만들어낼 수 있는 핵심 역량을 키울 수 있는 곳입니다. 사람들은 각자 일하는 방법이나 인생설계 방향을 가지고 있습니다. 그리고 여기에 따라 너무 다양한 나름의 스타일을 가지고있죠. 우리는 이 부분을 간과해서는 안된다고 생각해요. 우리가 말하는 취향은 바로 이런 것입니다. 취향에 따라 내가 주도적인지 수동적인지, 스스로 설계하는 스타일인지 주어진 과제를 잘 해결하는 스타일인지 나뉘는게 당연하겠죠. 이 부분은 실력과는 또 다른 축인거 같습니다. 한가지 방식을 강요해봤자 상황이 제대로 돌아갈리는 만무하고 또 그래서도 안됩니다. 일을 잘 하고싶은 스마트링크는 그래서 우리가 운영 가능한 범위 내에서 최대한의 공간과 가능성을 만들고 싶었습니다. 그래서 이런 구조를 생각해낸거구요.좀 더 솔직히 말하자면, 네. 이거 준비하는데 힘들었습니다 ㅠ 그냥 실력있는 사람들이 머리를 맞대고 모이기만 한다면야 이런 고민과 구상이 필요 없을지도 몰라요. 오히려 그게 편하기도 하구요. 척 하면 척~ 착 하면 착~ 아시죠? 그리고 이 은하철도 프로젝트를 채용공고에서 공개하는 것이 과연 좋을까? 라는 고민이 있었던것도 사실입니다. 우리 자뻑모드로로 보자면 중요한 영업비밀일지도 모른다고 생각했거든요. 하지만 채용공고가 다소 길지라도 가능한 범위 내에서는 충분히 미리 공유하는게 좋겠다고 생각했습니다. 사실 이런 생각까지는 쉬운데 실제로 이렇게 구조를 잡는건 생각보다 매우매우 오래걸리고 어렵거든요. 그리고 그 어려운걸 우리는 해냈습니다. Components를 잘 구축해놨다 이겁니다 ㅎㅎㅎ다시 한번 말하자면 스마트링크는 로켓이 아니라 은하철도 입니다!! 날아오른다!!! 이거시 바로 은하철도!!!  알겠고, 그렇다면 구체적인 채용정보를 내놓아라!그래서 누굴 뽑는것인가? 라고 물으신다면 개발자 0명 찾습니다! 0명은 무엇이냐? 좋은 사람이 있으면 있는만큼 욕심을 낼것이다! 이런 욕구와 목마름이 있다는 것이죠! 많이 지원해 주세요! 공통적으로 체크해보실 수 있는 정보를 우선 드릴까요? 현재 사용중인 기술 스택 및 도구공통: Google Drive, Trello, Slack기획: FramerX, Adobe XD디자인: FramerX, Adobe XD 포함 Adobe 모든 제품군, ZeplinFront-end: Semantic UI, React, React NativeBack-end: MeteorTesting: Mocha, JestDevOps: Jenkins, Docker, Phusion Passenger, Nginx, AWSDatabase: MongoDB 근무환경최상의 사무 환경 및 공간 제공 (넓고 쾌적한 책상! 빵빵하고 쾌적한 냉난방시설! 막 엎어져서 작업하는 소파! 등) 식대 지원 (중식/석식) 4대 보험 주5일 근무 Refresh 휴가 출근시간 선택제 (8-5 / 9-6 / 10-7 / 11-8)경조사비 지원 근무지: 서울시 서초구 양재동 4-14 3층워크샵이라 하면 적어도 뷔페와 함께하는 야간 요트 유람 정도는 해줘야하는거 아닙니까? (사실 명목은 지스타…)  알겠고, 개발자 채용요건을 내놓아라! 네, 드...드리겠습니다. 아래를 봐주세요. 참고로 위에서 충분히 설명했듯 우선 1등석과 일반석에 모셔요~ ㅎㅎ Global Spec과 실무경험을 국내에서 탑재할 수 있는 기회를 놓치지 마세요! 이제 개알못 기획자는 아웃! React 코드를 보고 이렇게 반응하는 사람이라면 우리는 이렇게 됩니다 ㅎㅎ기술 스택스마트링크는 2001년 부터 C > C++ > Java > Object Pascal > PHP > JSP > Rails > Python 등의 개발 언어 기반으로 많은 프로젝트를 수행하여 왔습니다. 현재는 Javascript, Nodejs, React, React Native, Meteor, MongoDB의 매력에 흠뻑 빠져 있지만, 프로젝트 진행의 효율을 더(even more productive) 개선할 수 있는 새로운 기술이나 방법론에 대한 목마름으로 언제든 Early Adapter가 될 준비가 되어 있습니다.   모집분야 : 각 영역의 Front-end 혹은 Back-end 개발자를 모십니다.Javascript/Nodejs/Meteor 기반의 웹/모바일 애플리케이션 개발자 React + Meteor + MongoDB 기술 기반의 Web Application 개발 React Native + Meteor + MongoDB 기술 기반의 Mobile Native Application 개발  자격요건 : 개발에 미친 사람!!! 자유로운 소통과 공유의 가치를 잘 이해하고, 자기주도적인 환경에서 최대의 능력을 발휘하며, 긍정에너지 발산이 가능한 분 논리적이고 체계적인 문제해결 능력 및 오픈 마인드 커뮤니케이션 능력 전산 관련학과 학사 이상 또는 동일한 자격 (경력 무관)  우대조건 React, React Native 등의 JavaScript SPA(Single Page Application) 프레임워크 경험 Nodejs + MongoDB 기반 Micro Service Architecture 서비스 개발 경험 영어 커뮤니케이션 능력 (특히, 영문서 이해 능력: 해외 최신 기술을 주로 이용하다보니 한글 자료가 없는 경우가 많습니다.) AWS 등 클라우드 서비스 운영 경험 Git 포트폴리오: 직접 작성한 패키지, 오픈소스 기여 경험Docker 컨테이너 기반 서비스 구축 및 운영 경험 CI 시스템 구축 및 운영 경험 Mocha, Jest 등의 테스팅 프레임워크 또는 TDD(Test Driven Development) 경험  어떻게 지원하면 되는거임? 아래 루트로 지원해주시면 서류검토 후 면접일정을 직접 안내해 드립니다. 이메일과 핸드폰 연락처가 모두 기재되어있으면 참 좋겠죠? 면접이 진행되면 스마트링크에 궁금한 것, 알아보고 싶은 모든 것을 물어보실 수 있습니다! 함께 대화하는 자리라고 생각하시는게 가장 좋을거 같네요. 1. 이메일로 지원하세요! [email protected]해당 정보들도 함께 보내시면 금상첨화!이력서 (희망연봉포함)포트폴리오개발 경력 자료 (github 주소 환영합니다!) 2. 로켓펀치에서도 지원하실 수 있습니다!일반석 채용공고 https://www.rocketpunch.com/jobs/574961등석 채용공고 https://www.rocketpunch.com/jobs/57499 3. 잡코리아도 됩니다!스마트링크 은하철도에 탑승할 개발자 정규직 채용(신입&경력)http://www.jobkorea.co.kr/Recruit/GI_Read/28711079?Oem_Code=C1 4. 사람인도 됩니다!스마트링크 은하철도에 탑승할 개발자 정규직 채용(신입&경력)http://www.saramin.co.kr/zf_user/jobs/relay/view?rec_idx=36338553&view_type=etc   지금 망설이고 있다면???국내에서는 중소기업, 특히 신생기업이나 스타트업에 대한 인식이 그렇게 좋지않죠. 이런 현실적인 부분도 감안해서 저희는 직접적인 코딩테스트나 압박면접 같은건 진행하지 않습니다. 차분하고 진실된 마음의 대화가 가장 중요하다고 생각해요. 본인의 평소 생각을 그저 편안하게 나눈다 생각하고 부담없이 관심만 가지고 다가와주세요 :)이 짤처럼 무서운거 아니에요 ㅋㅋㅋ 편하게 드루와 드루와~지금까지 소개해드린 스마트링크 시즌2, 은하철도 프로젝트 느낌이 어떠신가요? 저희의 설렘과 기대가 잘 전달이 되었을지 모르겠어요. 같은 설렘과 기대가 느껴지신다면 망설이지 마세요! 우리의 은하철도에 탑승할 분들을 그야말로 간절한 마음으로 기다리고 있습니다.  지금 당신은 지원 메일을 보내고있다~!!!
조회수 3067

챗봇과 인공지능 머신러닝 ㅡ Part 1/2

스타워즈를 보신 분이라면 거기에 나오는 난쟁이 로봇 R2D2와 키다리 로봇 C3P0를 아실 것이다. 친근한 R2D2는 전자음을 조정해 인간과 대화를 하며 주로 말 잘하고 박식한 로봇인 C3P0가 통역을 해준다.이런 충실하면서 똑똑한 친구들이 옆에서 항상 나를 도와준다면 어떨까? 정말 좋을 것이다. 만약 매일 보는 스마트폰 안에서도 나의 질문에 답해주는 이런 고마운 친구들이 있다면 얼마나 좋을까? 이런 저런 생각을 하다보면 우리는 대화형 로봇의 필요성을 느낀다.챗봇(Chatbot)이란?챗봇의 정의는 “대화형 인터페이스 상에서 규칙 또는 지능으로 유저와 소통하는 서비스”이다. 이 말을 하나하나 풀어보자.먼저, 대화형 인터페이스란 뭐지? 어렵다. 쉽게 설명해 보자. 인터페이스는 사람과 컴퓨터를 연결하는 장치라고 한다. 역시 어렵다. 아! 그냥 스마트폰 앱으로 보면 된다. 그럼 소통한다는 말은 대화한다는 것이므로 스마트폰 앱에서 일방향이 아닌 양방향이 가능하다는 얘기다. 어! 이상하다. 양방향이라면 나의 말에 응대하는 로봇은 뭐로 움직이는 거지? 궁금하다. 누가 일정한 규칙으로 만들어 논건지 아니면 우리처럼 지능이 있는 건지. 지능이 있다면 그런 지능은 뭐지? 점차 우리는 자연스럽게 인공지능에 다가간다.인공지능(Artificial Intelligence)이라는 용어는 1956년 미국 다트머스의 한 학회에서 존 매카시가 처음 사용했다고 한다. 원래 인공지능은 소프트웨어인 정신을 말하고 로봇은 하드웨어인 육체를 말하는 것이지만 정신없이 육체가 존재할 수 없는 것처럼 로봇을 얘기하면 당연히 인공지능은 따라간다.학자들은 인공지능을 강(强)인공지능과 약(弱)인공지능으로 구분한다. 간단히 얘기하면 강인공지능이란 자의식이 있는 인간에 가까운 지능이고 약인공지능은 자의식이 없다. 자아가 없으며, 명령받은 일만을 수행한다. IBM의 왓슨(Watson), 작년에 인공지능의 붐을 가져온 구글의 알파고(Alpha-GO) 등은 모두 약인공지능이다. 이런 인공지능을 구현하는 기술은 무엇인가? 바로 기계한테 학습을 시키는 머신러닝(Machine Learning)이다.1959년 아서 사무엘은 머신러닝을 "기계가 일일이 코드로 명시하지 않은 동작을 데이터로 부터 학습하여 실행할 수 있도록 하는 알고리즘을 개발하는 연구 분야"라고 정의했다. 여기서 학습이란, 입력 값을 받아 결과 값을 내는 모델을 만드는 표현과 표현을 통해 주어진 업무가 얼마나 잘 수행됐는지 알아보는 평가, 그리고 평가에서 설정한 기준을 찾는 최적화로 구성된 일련의 과정을 말한다. 중요한건 우리가 시키지 않은 일도 학습에 의해 자율적으로 처리한다는 것이다. 정말 신기하지 않은가?이제 챗봇이 뭔지 감이 잡힌다. 스마트폰 앱상에 존재하는 로봇인데, 물론 육체는 화면의 아이콘으로 밖엔 안보이지만 인공지능을 가지고 머신러닝에 의해 동작을 하면서 우리와 대화를 하는 그분. 그렇다면 이제 남은 건 이분의 지능이 어느 정도인지 또 얼마나 일을 잘하는 지로 판가름 난다.우리는 평생 공부를 한다. 이제는 학교를 졸업하고 나서도 항상 배워야 한다. 학습이 없다면 지능도 없다. 학습은 일일이 지도받는 지도학습과 알아서 공부하는 자율학습이 있다. 알아서 공부하려면 먼저 머리에 지식이 많아야 한다. 역시 기계도 사람과 비슷하게 배운다.  다음시간엔 챗봇에게 학습을 시켜 지능을 가지게 하는 방법에 대해 알아본다.> Part 2에서 계속
조회수 917

Culturalization of Video Game Soundtracks: An Interview with Pierre Langer, Managing Director & Founder of Dynamedion

 Game culturalization, the process of cultural adaption, is the key to successfully launching video games in foreign markets. The main aspects are to make content suitable, understandable, and meaningful for the gamers of the targeted markets. To achieve these objectives, it is necessary to look into the five central pillars of culturalization: history, religion, ethnic and cultural tensions, geopolitical situations, and in-game elements.One in-game element that must be considered is music. To learn more, we interviewed the video game music expert and composer Pierre Langer, founder and managing director of Dynamedion based in Mainz, Germany. Pierre will tell us more about his internationally renowned company, the video game music business, and the culturalization process of video game soundtracks.  Pierre Langer  Dear Pierre, please let us know more about you and your company and the key services that you provide.  Pierre Langer: Dynamedion was founded by Tilman Sillescu and me in early 2000. We started with work-for-hire audio in the German games industry doing music composition, sound design and later also interactive audio integration and Live Orchestra production. We were the first to produce with live orchestra for a German game, and we eventually rolled this out as a service for other composers and game developers all over the world.Today we are one of the biggest game audio studios in the world with nearly 50 people doing music composition, music licensing, sound design, source sound recordings, audio integration, audio software development, live orchestra and live choir recording, and orchestration and arrangement for all sorts of media. We are still very much focused on video games, having worked on more than 1,800 games, but we also do a lot of movie trailers, TV series, and films.In 2009 we started a sub company of Dynamedion called BOOM library, which produces original sound effects collections as products that can be licensed by audio professionals throughout the world. BOOM Library is today recognized as one of the most popular and high-quality sound effects libraries in the world. Apart from that we also run two side labels with royalty-free stock music in a unique adaptive format (SmartSound) and a new product line of virtual software instruments (SONUSCORE). Our latest addition to our services is that we have become well known for high end vehicle recordings (cars, airplanes, helicopters, bikes, tanks, etc.) – that is a lot of fun, but also a huge challenge to source all sorts of rare or weird or super expensive vehicles.So, in short: we are specialists for everything that has to do with music & sound for games – everything except voice overs, and our music or sound effects or live productions have been used and heard in nearly every large game worldwide. As an example, we recently have been involved in these titles: Assassin’s Creed Series, Elder Scrolls Online, Monster Hunter Online, Battlefield V, League of Legends, Destiny 1 & 2, Lineage II, Horizon Zero Dawn, Fortnite, Mortal Kombat Series, World of Tanks, Hitman Series, Total War Series.Currently we are working on five super large unannounced titles, all international.  What part of the world do your requests mainly come from?  Pierre Langer: It is very international, really. Up until 2009 we had a very strong (overly strong I would say) position in Germany, working on nearly every German game title, quite some in France and some occasional overseas projects. Meanwhile this has completely changed: we are doing a good amount of German titles, but the major part comes from the US, UK, Scandinavia, Japan, Korea and China – China being one of the most important markets now.  Have you experienced a shift or a change over the years in game creation from Western countries to an international mix?  Pierre Langer: Absolutely! It seems that the five big “individual” markets (North America, Europe, China, Japan / Korea) are getting closer to each other. Even very self-sustaining markets, like the Japanese market, are opening up for more international projects coming in, but they are also looking into getting their own games distributed internationally, and of course into becoming as successful as possible worldwide. And then there is a huge amount of projects coming from all the emerging markets, so it seems that there is really no end to a lot of new great games. The biggest challenge with a new game certainly is to make yourself “heard” or do something special that your competition does not do, in order to stand out in a new market.  Orchestral Session - Dynamedion  What is culturalization in terms of video game soundtracks and sound effect production?  Pierre Langer: It is actually a very straightforward thing and kind of a no-brainer, since audio is a rather inexpensive asset for a game, while it has a huge emotional and atmospheric impact. Culturalization of a game means that you adapt the game to the specific requests of a new market. Western world audiences are used to different things than Chinese players, for example. So, if a Chinese game developer wants to push a game into the Western market, the game should be “westernized” so to say. This certainly already happens with gameplay mechanics and with graphics and – of course – with the localization. But simply changing the texts and voice over from Chinese to English doesn’t adapt a Chinese game to an EU or US audience. The look and feel of a game need to change as well, and this is where music and sound “culturalization” comes in: adapting the music and sounds (and the way of implementation and audio functionality in the game) to the specific audience that is being targeted. This does of course work in all directions – Japan to China, China to Europe, Europe to Korea, etc.  Can you give us some examples of audio culturalization in specific markets? (E.g. MENA, South America, China/Asia)  Pierre Langer: Let me go back a few years, to our very first larger game title we did music and sound culturalization for. It was “Runes of Magic” by Runewaker Entertainment, a developer based in Taiwan. The game was not extremely successful in Taiwan and Mainland China, but a German publisher by the time (Frogster) saw some great potential in that game. So, they licensed the title and got the rights to publish it in Europe and the US. In some respects, the game was a mess for a Western audience, partly due to the music and the sound + the implementation of all audio. The marketing people at Frogster understood this very quickly and started working on all these issues. The music and sound side was done in a matter of a few weeks: they asked us to replace the soundtrack by using music we had in our back catalogue (music for games that we had written, that either failed, or that had been unsuccessful – which we kept the rights to) and write a few new themes that would work as the iconic main themes of the game, so that the audience has something new and recognizable. We did that, with a full focus on writing and licensing music that would be ideal for the target audience. Then we did a similar thing with the sound effects: we simply threw out all the stuff that was in there and replaced it with sounds that where produced to fit a Western audience. To give you a very quick example: Asian players are used to high frequency sounds, very aggressive, very loud, the whole sound atmosphere being very crowded. European and US players are used to low frequency sounds – sub-bass, deep impacts, rumbling and more focused sound design (you hear one thing prominently, and everything else gets balanced down to make space for the one important sound going on). This is a very clear and super important difference – and it is also easy to fix with some new content and some new mixing.  What are typical issues that occur in sound culturalization?  Pierre Langer: Typical issues are that there needs to be some trust from the developer to the sound team. In most cases, the developer asks for culturalization from their home market to a foreign market. So, a US developer asking us to adapt the sound to fit a Chinese audience better needs to trust us that we know what we are doing, since the US developer doesn’t know themselves (otherwise they wouldn’t need us). Then there is always a big challenge with the correct audio integration. The most important bit is certainly to replace music and sound effects, to get a fitting new set of assets for the target market. However, even the best assets do not help if they are poorly integrated. Simply swapping them is not enough if the way they are being played back is not fitting. This then needs some more time and attention and focus, since we need to work with the developer directly to e.g. add some audio functionality, balance mix and master the audio, or introduce an interactive music system. It can be a very elaborate thing, but you can achieve a lot of additional quality with the most basic strategies that only cost a lower 5 digit budget.  Dear Pierre, thank you for your time and effort in providing us such enlightening insights into your work!About Pierre:Pierre was born near Frankfurt / Germany. After years of playing in bands as a guitar player in his teens, he decided to take his studies in classical music at the Johannes Gutenberg University in Mainz..A few months before his final exams he met Tilman Sillescu in early 2000, Dynamedion was founded a few weeks later. In the first years of Dynamedion Pierre worked on basically every single bit of the job you can do as an audio person in the games business: music composition, sound design, audio integration, audio management, design of audio tool chains, recording, mixing, mastering, project management, etc.As the thing grew and all the other guys joined in, Pierre focused more and more on the business side of things, leaving the creative work to the really focused experts.Nowadays Pierre enjoys keeping in touch with all the different clients of Dynamedion, thinking up new product lines and business ideas to further expand the reach and prominence of Dynamedion and all related sub-labels such as BOOM Library, Sonic Liberty, Sonuscore... and more to come.The Interview was conducted by Moritz Demmig. 
조회수 1348

레진 기술 블로그 - 모두를 위한 설계. 레진 웹 접근성 가이드라인.

레진엔터테인먼트는 글로벌(한국, 일본, 미국) 서비스를 운영하고 있기에 다양한 사람들의 재능과 욕구에 관심이 있습니다. 우리는 웹 접근성에 관심을 기울여 조금 특별한 욕구를 가진 사람들의 문제를 해결하려고 합니다. 소수의 특별한 욕구는 모두의 욕구와 연결되어 있다고 생각하기 때문입니다.조금 특별한 욕구를 가진 사람WHO는 세계 인구의 15%에 해당하는 사람들이 장애가 있는 것으로 파악하고 있습니다. 그리고 보건복지부 장애인 실태조사에 따르면 후천적 장애 발생률은 90% 수준입니다. 이런 통계에 따르면 한 개인이 일생을 살면서 장애인이 되거나 일시적으로 장애를 체험하게 될 확률은 무려 13.5%나 됩니다.저는 적록 색약입니다. 약한 수준의 장애로 분류할 수 있죠. 채도가 낮은 상태의 적색과 녹색을 쉽게 구별하지 못합니다. 충전 중 적색이었다가 완충이 되면 초록색으로 변하는 LED가 박혀있는 전자제품은 전부 망했으면 개선하면 좋겠어요. 전 세계 남성의 8%가 색약이고, 여성은 0.5%가 색약입니다. 대부분 적록 색약이고 마크 저커버그도 적록 색약입니다. 만화가 이현세 선생님도 적록 색약이고요. 한편 색약인 사람은 빛의 밝고 어두움을 구별하는 능력이 뛰어난 것으로 밝혀져 있어 저격과 관측에 탁월한 능력을 발휘합니다. 숨어있는 저격수 빨리 찾기 게임을 해 보세요. 위장 사진 1, 위장 사진 2, 위장 사진 3. 색약인 사람이 이길 것입니다.전맹 시각장애인은 마우스 포인터와 초점을 볼 수 없으므로 키보드만을 사용해서 웹을 탐색합니다. 키보드와 음성 낭독에 의존하지만, 키보드 기능을 정말 잘 다루죠. 그래서 키보드 접근성 문제를 해결하면 시각장애인뿐만 아니라 키보드를 능숙하게 사용하는 사람들의 사용성이 높아집니다. 소수의 특별한 요구사항을 해결하는 것이 모두를 위한 설계와 연결되어 있습니다.결국, 누구에게나 특별히 다른 측면이 있고 그것을 고려할 때 "모두를 즐겁게 하라!"라는 우리의 좌우명에 한 걸음 더 가까워질 수 있다고 믿습니다.도저히 풀 수 없을 것 같은 숙제웹 접근성을 소개할 때 많이 듣는 질문이 있습니다.장애인이 우리 서비스를 이용해요?매출에 도움이 돼요?시간과 비용이 많이 필요하지 않아요?이 질문에 대한 제 대답은 다음과 같습니다.이용한다면 기쁠 것 같아요.큰 도움은 안 될 거예요.조금은 그렇죠. 하지만 반환이 있어요.레진코믹스와 같이 이미지 기반의 콘텐츠를 서비스하는데 웹 접근성을 준수하려고 노력한다는 것은 무모한 도전에 가깝습니다. 왜냐하면, 현재로서는 전맹 시각장애인 고려가 없고 논의조차 쉽지 않기 때문입니다.하지만 달에 갈 수 없다고 해서 일찌감치 체념할 필요는 없겠지요. 쉬운 문제부터 하나씩 풀어 나아가길 기대합니다. 로켓에 올라탔으니까 금방 갈 수 있지 않을까요?W3C 표준을 우리 언어로W3C에서는 WCAG 2.1이라는 웹 콘텐츠 접근성 지침을 제시하고 있고요. 국내 표준 KWCAG 2.1 또한 있습니다. 국내 표준은 W3C 표준에서 중요도가 높은 항목을 우리 언어로 정리한 것이기 때문에 결국 어떤 지침을 선택해서 따르더라도 괜찮습니다.하지만 표준 문서는 너무 장황하고 전문 용어가 많아 다양한 분야 전문성을 가진 직원들과 함께 보기에는 한계가 있다고 생각했습니다. W3C 표준을 근간으로 하되 비전문가도 15분 정도면 읽고 이해할 수 있을 만큼 정리된 문서가 필요했고 레진 웹 접근성 가이드라인 사내 표준을 제안하고 공개하게 됐습니다.의미를 전달하고 있는 이미지에 대체 텍스트를 제공한다.전경 콘텐츠와 배경은 4.5:1 이상의 명도 대비를 유지한다.화면을 400%까지 확대할 수 있다.키보드만으로 조작할 수 있다.사용할 수 있는 충분한 시간을 제공한다.발작을 유발하는 콘텐츠를 제공하지 않는다.반복되는 콘텐츠 블록을 건너뛸 수 있다.모든 문서의 제목은 고유하고 식별할 수 있다.링크와 버튼 텍스트는 콘텐츠의 목적을 알 수 있다.섹션에는 의미있는 마크업과 헤딩이 있다.문서의 휴먼 랭귀지 속성을 제공한다.문맥 변경은 예측할 수 있다.폼 콘트롤 요소에 설명을 제공한다.실수를 예방하고 정정하는 것을 돕는다.HTML 문법을 준수한다.WCAG 2.1 지침의 1.1.1 항목 예를 들어 볼게요.All non-text content that is presented to the user has a text alternative that serves the equivalent purpose, except for the situations listed below. 사용자에게 제공되는 모든 텍스트 아닌 콘텐츠는 아래 나열된 상황을 제외하고 같은 목적을 수행하는 대체 텍스트를 제공한다.원문 표현보다 아래와 같이 다듬은 표현이 좋다고 보는 것이죠.의미를 전달하고 있는 이미지에 대체 텍스트를 제공한다.물론 사내 지침은 너무 단순하게 표현했기 때문에 지침마다 ‘부연 설명, 관련 예시, 기대 효과, 관련 표준, 평가 도구’ 텍스트와 링크를 간략하게 제공하고 있습니다. 사실상 W3C 표준에 대한 링크 페이지라고 생각해도 괜찮습니다. 사실이 그런걸요.맺음말레진 웹 접근성 가이드라인은 사내 유관 부서 담당자분들께 공유하고 동의를 얻어 사내 지침으로 결정하고 공개할 수 있게 됐습니다. 긍정적으로 검토해 주신 사우님들 감사합니다.레진 웹 접근성 가이드라인은 W3C 표준을 요약한 버전에 불과하므로 누구라도 복제(Fork), 개선 요청(Pull Requests), 문제 제기(Issues)할 수 있습니다."Design for all, amuse everyone!"
조회수 3802

iOS에서 간결한 API 클라이언트 구현하기 (like Retrofit+GSON)

이 글은 안드로이드 개발에서 웹 서버 API 클라이언트를 간결하게 구현할 수 있도록 도와주는 강력한 오픈소스 라이브러리인 Retrofit과 GSON의 조합을 iOS 개발에서도 따라해보고 싶은 분들을 위해 작성되었습니다. Retrofit+GSON를 실제로 사용하는 좋은 예제는 다른 블로그 글에서도 찾아볼 수 있습니다.배경리디북스 서비스가 발전하면서 점점 복잡해지고, 자연히 앱의 기능도 다양해지기 시작했습니다. 기능이 다양해지면서 웹 서버와의 연동을 위한 API 종류도 늘어났고 앱 내에서 API 호출이 필요한 부분도 다양해지면서 관련된 중복 코드가 이곳 저곳에 산재하게 되었고 전체적인 코드 퀄리티 향상을 위해 이를 최소화하고 모듈화 할 필요성이 생겼습니다.안드로이드에서는 Pure Java로 작성되어 어노테이션을 통한 간결한 코드를 사용할 수 있게 해주는 Retrofit을 GSON과 연동하여 JSON 응답을 손쉽게 객체에 맵핑 하여 사용함으로써 이러한 문제를 성공적으로 해결할 수 있었습니다. 이후 iOS 개발을 진행하면서 비슷한 역할을 할 수 있는 도구가 있을까 찾아봤지만 마땅하지 않아 결국 사용 가능한 도구들을 이용해 비슷하게 따라해보기로 했습니다.목표Retrofit+GSON 조합을 최대한 따라해서 iOS 앱의 코드 퀄리티를 높이기 위한 작업을 진행하기는 하지만 모방하는 것 자체가 목적이 될 수는 없으므로, 구체적인 목적은 다음과 같은 것들로 상정해보았습니다.API 통신 부분을 모듈화하여 관련 중복 코드를 최소화하기NSArray, NSDictionary를 직접 사용하여 제어 했던 JSON 처리 부분을 추상화하여 모델 클래스를 정의, JSON 응답을 자동으로 객체에 맵핑 해서 사용할 수 있도록 하기필요한 것Retrofit과 GSON의 동작에 대한 이해AFNetworking비동기 HTTP 요청 처리에 용이하므로 기존에도 이미 API 호출을 위해서도 사용하고 있었습니다.이 글의 내용은 버전 2.6.3 기준입니다.Swift 언어와 그에 대한 이해사실 Objective-C를 사용해도 무방하지만, 작업 당시 Swift가 발표된 지 얼마 되지 않은 시점 이었기 때문에 시험 삼아 선택 되었으며 실제로 Swift가 Objective-C 대비 가진 장점들이 적지 않게 활용되었습니다.이 글의 내용은 버전 2.0 기준입니다.구조와 동작클래스 이름 앞에 붙어 있는 RB는 리디북스에서 사용하는 클래스 접두어 입니다.RBApiServiceAPI 통신을 담당하는 부분의 핵심은 중앙의 RBApiService 클래스를 포함한 상속 구조라고 할 수 있으며 상술하면 다음과 같습니다.AFNetworking에서, HTTP 요청 작업의 큐잉부터 시작과 종료까지 라이프 사이클 전반을 관리하는 역할을 하는 AFHTTPRequestOperationManager를 상속받는 RBApiService 클래스를 정의각 API들은 역할군에 따라 RBBookService(책 정보 관련 API), RBAccountService(사용자 계정/인증 관련 API) 등과 같은 RBApiService의 하위 클래스들의 메소드로 정의됨이 하위 클래스들이 AFHTTPRequestOperationManager의 역할을 그대로 이어받아 자신을 통해 이루어지는 API HTTP 요청 작업들을 관리이 설명에 따르면 웹 서버의 /api/foo/bar API를 요청하는 메소드는 RBFooService 클래스에 다음과 같이 정의될 것입니다.func bar(param1: String, param2: String, success: RBApiSuccessCallback, failure: RBApiFailureCallback) -> AFHTTPRequestOperation! { let paramters = ["param1": param1, "param2": param2] responseSerializer = RBJSONResponseSerializer(responseClass: RBFooBarResponse.class) return GET("/api/foo/bar", parameters: parameters, success: success, failure: failure) }RBApiSuccessCallback과 RBApiFailureCallback은 요청과 응답이 완료되고 각각 성공, 실패일 때 호출되는 람다 함수(Objective-C의 block에 대응되는 개념) 타입으로 다음과 같이 typealias를 통해 선언되어 있습니다. typealias RBApiSuccessCallback = ((operation: AFHTTPRequestOperation, responseObject: AnyObject) -> Void)? typealias RBApiFailureCallback = ((operation: AFHTTPRequestOperation?, error: NSError) -> Void)?GET 메소드는 AFHTTPRequestOperationManager의 메소드로 새로운 HTTP GET 요청 작업을 생성하고 큐에 넣은 뒤 그 인스턴스를 반환합니다. bar 메소드는 이렇게 반환된 인스턴스를 다시 그대로 반환하는데 API 호출을 의도한 측에서는 이 인스턴스를 통해 필요한 경우 요청 처리를 취소할 수 있습니다. API에 따라 GET 이외의 다른 방식의 요청이 필요하다면 POST, PUT, DELETE등의 메소드들 또한 사용할 수 있습니다.RBFooBarResponse 클래스는 이 API 호출의 JSON 응답을 맵핑하기 위한 모델 클래스입니다. 이 API 요청의 응답은 RBJSONResponseSerializer 클래스를 통해 사전에 정의된 규칙에 따라 적절히 RBFooBarResponse 인스턴스로 변환되고 이 모든 과정이 성공적으로 진행되면 RBApiSuccessCallback의 responseObject 인자로 전달됩니다.모델 클래스와 RBJSONResponseSerializer앞서 이야기했듯이 RBJSONResponseSerializer는 JSON 형태로 온 응답을 특정 모델 클래스의 인스턴스로 맵핑시키는 작업을 수행합니다(Retrofit+GSON 조합에서 GsonConverter의 역할에 대응한다고 볼 수 있습니다).iOS 개발에서 전통적으로 JSON을 다루는 방식은 Cocoa 프레임워크에서 기본적으로 제공하는 NSJSONSerialization 클래스를 이용하여 JSON Array->NSArray로, 그 외의 JSON Object는 NSDictionary로 변환하여 사용하는 방식입니다. 이러한 방식을 사용할 경우 별다른 가공이 필요 없다는 장점이 있는 대신 다음과 같은 문제들에 직면할 수 있습니다.데이터가 명시적으로 정의된 프로퍼티로 접근되지 않고 문자열 키 기반의 키-밸류 형태로만 접근되므로 데이터의 타입이 명시적이지 않아 타입 검사와 캐스팅이 난무하게 되어 가독성을 해침오타와 같은 개발자의 단순 실수로 인한 버그를 유발할 가능성도 커짐특히 오타로 인한 버그의 경우 명시적인 모델 클래스의 프로퍼티로 맵핑 해서 사용한다면 IDE가 에러를 검출해주거나 최소한 빌드 타임 에러가 발생할테니 미연에 방지할 수 있습니다. 이러한 문제는 사소한 실수로 인해 찾기 힘든 버그가 발생한다는 점과 코드 리뷰를 통해서도 발견하기가 힘들다는 점에서 지속적으로 개발자를 괴롭힐 수 있습니다.RBJSONResponseSerializer를 통한 인스턴스로의 변환은 이런 문제 의식에서 출발했고 Retrofit에 GSON을 연계하여 사용하기 위한 GsonConverter가 해결을 위한 힌트를 제공한 셈입니다.// AFJsonResponseSerializer는 NSJSONSerializer를 이용해 NSArray/NSDictionary로 변환하는 기본적인 작업을 해줌 class RBJSONResponseSerializer: AFJSONResponseSerializer { var responseClass: NSObject.Type! override init() { super.init() } required init(responseClass: NSObject.Type!) { self.responseClass = responseClass super.init() } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func responseObjectForResponse(response: NSURLResponse?, data: NSData?, error: NSErrorPointer) -> AnyObject? { // 파서를 직접 구현하는 건 노력이 많이 필요하므로 우선 AFJSONResponseSerializer를 이용해 NSArray/NSDictionary로 변환 let responseObject: AnyObject! = super.responseObjectForResponse(response, data: data, error: error) if let dictionary = responseObject as? NSDictionary where responseClass != nil { // 변환 결과가 NSDictionary이면서 responseClass가 정의되어 있다면 변환 작업 시작 return responseClass.fromDictionary(dictionary, keyTranslator: PropertyKeyTranslator) } // NSArray라면 JSON이 top level array로 이루어졌다는 뜻이므로 변환 불가로 보고 그대로 반환 // 혹은 responseClass가 정의되어 있지 않아도 그대로 반환 return responseObject } }Key translatorfromDictionary 메소드 호출 시 함께 인자로 전달되는 keyTraslator는 JSON에서 사용되는 키로부터 모델 클래스의 프로퍼티 이름으로의 변환을 나타내는 람다 함수로 개발자가 원하는 규칙에 따라 정의하면 됩니다. 위의 코드에서 사용 중인 PropertyKeyTranslator는 리디북스 API에서 사용 중인 규칙 및 Swift의 네이밍 컨벤션에 따라 다음과 같이 언더스코어(_) 케이스로 된 이름을 카멜 케이스로 바꾸는 형태로 정의되었으며 이는 GSON의 FieldNamingPolicy 중 LOWERCASE_WITH_UNDERSCORES와 유사합니다.let PropertyKeyTranslator = { (keyName: String) -> String in let words = keyName.characters.split { $0 == "_" }.map { String($0) } var translation: String = words[0] for i in 1..NSObject.fromDictionary 메소드fromDictionary 메소드는 NSDictionary로 표현된 데이터를 실제 모델 클래스의 인스턴스로 변환하는 작업을 수행하며 NSObject의 extension(Objective-C의 category 개념과 유사합니다)으로 정의하여 원하는 모델 클래스가 어떤 것이든지 간에 공통적인 방법을 사용할 수 있게끔 했습니다.extension NSObject { class func fromDictionary(dictionary: NSDictionary) -> Self { // keyTranslator가 주어지지 않으면 디폴트 translator 사용 return fromDictionary(dictionary, keyTranslator: { $0 }) } class func fromDictionary(dictionary: NSDictionary, keyTranslator: (String) -> String) -> Self { let object = self.init() (object as NSObject).loadDictionary(dictionary, keyTranslator: keyTranslator) return object } func loadDictionary(dictionary: NSDictionary, keyTranslator: (String) -> String) { // 주어진 dictionary에 포함된 모든 키-밸류 쌍에 대해 작업 수행 for (key, value) in (dictionary as? [String: AnyObject]) ?? [:] { // keyTranslator를 이용해 키를 프로퍼티 이름으로 변환 let keyName = keyTranslator(key) // 프로퍼티 이름을 사용할 수 있는지 검사 if respondsToSelector(NSSelectorFromString(keyName)) { if let dictionary = value as? NSDictionary { // 밸류가 NSDictionary면 해당 프로퍼티의 타입에 대해 fromDictionary 메소드 호출 if let ecls = object_getElementTypeOfProperty(self, propertyName: keyName) as? NSObject.Type { setValue(ecls.fromDictionary(dictionary, keyTranslator: keyTranslator), forKey: keyName) } else { NSLog("NSObject.loadDictionary error: not found element type of property. (key: \(keyName), value: \(dictionary))") } continue } else if let array = value as? NSArray { var newArray = [NSObject]() // 밸류가 배열이면 각 요소별로 작업 수행 for object in array { if let dictionary = object as? NSDictionary { // 배열 요소가 NSDictionary면 프로퍼티의 배열 요소 타입에 대해 fromDictionary 메소드 호출한 뒤 배열에 추가 if let ecls = object_getElementTypeOfProperty(self, propertyName: keyName) as? NSObject.Type { newArray.append(ecls.fromDictionary(dictionary, keyTranslator: keyTranslator)) } else { NSLog("NSObject.loadDictionary error: not found element type of property. (key: \(keyName), value: \(dictionary))") } } else if let object = object as? NSObject { // NSDictionary가 아니면 그대로 배열에 추가 newArray.append(object) } else { NSLog("NSObject.loadDictionary error: can't cast element. (key: \(keyName), value: \(object))") } } setValue(newArray, forKey: keyName) continue } else if value is NSNull { continue } // NSDictionary, NSArray가 아니면서 null도 아니면 그대로 사용 setValue(value, forKey: keyName) } } } }주어진 dictionary에 존재하는 모든 키-밸류 쌍에 대해 밸류가 가진 타입과 이에 대응하는 프로퍼티의 타입에 따라 적절히 프로퍼티에 대응될 객체를 구한 다음 Cocoa 프레임워크에서 제공하는 KVC를 이용해 채워넣습니다.프로퍼티 타입 정보 가져오기모델 클래스가 반드시 Int, String, Float과 같은 기본적인 타입들로만 이루어져 있을 필요는 없고 다른 모델 클래스의 인스턴스나 배열을 포함하고 있어도 타입 정보를 런타임에 가져와 재귀적으로 데이터를 채워나가는 것이 가능합니다. 프로퍼티의 타입을 알아내는 과정은 다음과 같이 Swift에서 제공하는 Mirror 구조체를 통해 이루어지는데 이는 마치 (이름에서도 느낄 수 있듯이) Java의 리플렉션을 떠올리게 합니다.// 타입 이름에서 특정 접두어("Optional", "Array", "Dictionary" 등)를 찾아 제거 func encodeType_getUnwrappingType(encodeType: String, keyword: String) -> String { if encodeType.hasPrefix(keyword) { let removeRange = Range(start: encodeType.startIndex.advancedBy(keyword.length + 1), end: encodeType.endIndex.advancedBy(-1)) return encodeType.substringWithRange(removeRange) } else { return encodeType } } // object의 타입에서 propertyName의 이름을 갖는 프로퍼티의 타입 이름을 반환 func object_getEncodeType(object: AnyObject, propertyName name: String) -> String? { let mirror = Mirror(reflecting: object) let mirrorChildrenCollection = AnyRandomAccessCollection(mirror.children)! // object의 타입 구조 children 중에서 propertyName을 찾음 for (label, value) in mirrorChildrenCollection { if label == name { // Optional 타입인 경우 "Optional" 접두어를 제외 return encodeType_getUnwrappingType("\(value.dynamicType)", keyword: "Optional") } } return nil } // object의 타입에서 propertyName의 이름을 갖는 프로퍼티의 타입 인스턴스를 반환 func object_getElementTypeOfProperty(object: AnyObject, propertyName name: String) -> AnyClass? { // 타입의 이름을 가져옴 if var encodeType = object_getEncodeType(object, propertyName: name) { let array = "Array" // "Array" 접두어로 시작할 경우 (배열인 경우) if encodeType.hasPrefix(array) { // "Array" 에서 "Array" 제외하고 T를 반환 return NSClassFromString(encodeType_getUnwrappingType(encodeType, keyword: array)) } let dictionary = "Dictionary" if encodeType.hasPrefix(dictionary) { // "Dictionary" 에서 "Dictionary", "K"를 제외하고 V를 반환 encodeType = encodeType_getUnwrappingType(encodeType, keyword: dictionary) encodeType = encodeType.substringWithRange(Range(start: encodeType.rangeOfString(", ")!.endIndex.advancedBy(1), end: encodeType.endIndex)) return NSClassFromString(encodeType) } // 커스텀 클래스 접두어를 가지고 있다면 그 타입 그대로 반환 if encodeType.hasPrefix(RidibooksClassPrefix) { return NSClassFromString(encodeType) } } return nil }RidibooksClassPrefix는 커스텀 클래스들의 접두어를 나타내는 상수이며(리디북스의 경우 앞서 이야기했듯 “RB”), 이 접두어가 붙어있는 경우에만 모델 클래스로 간주해 해당 타입 인스턴스가 반환됩니다.예시앞서 정의한 PropertyKeyTranslator를 사용했을 때, 위에 예시로 사용했던 /foo/bar API 요청의 JSON 응답과 모델 클래스 및 생성되는 인스턴스 형태의 예를 들면 다음과 같을 것입니다.(Int, Bool, Float과 같은 기존 NSNumber 기반의 타입을 가지는 프로퍼티들은 아직 정확한 원인은 알 수 없으나 nil 이외의 값으로 초기화 해주지 않으면 프로퍼티가 존재하는지 확인하기 위해 사용하는 respondsToSelector 메소드가 false를 뱉게 되어 사용할 수 없으므로 클래스 선언시 적절한 초기값을 주어야 합니다.{ "success": true, "int_value": 1, "string_value": "Hello!", "float_value": null, "baz_qux": { "array_value": [1, 2, 3] } }class RBFooBarResponse : NSObject { var success = false // true var intValue = 0 // 1 var stringValue: String! // "Hello!" var floatValue: Float! = 0.0 // nil var bazQux: RBBazQux! } class RBBazQux : NSObject { var arrayValue: [Int]! // [1, 2, 3] }맺음말이런 작업들을 통해 당초 목표했던 두 가지, API 통신 관련 중복 코드를 최소화 하면서 JSON 응답을 가독성이 더 좋고 실수할 확률이 적은 모델 클래스의 인스턴스로 자동 변환 하도록 하는 것 모두 달성하는 데에 성공했습니다.다만 모든 것이 뜻대로 될 수는 없었는데 Retrofit+GSON과 비교했을 때 플랫폼 혹은 언어의 특성에 기인하는 다음과 같은 한계들 또한 존재했습니다.Retrofit에서는 Java 어노테이션을 이용해 API 메소드의 인터페이스만 정의하면 됐지만 iOS 구현에서는 GET, POST 등의 실제 요청 생성 메소드를 호출 하는 것 까지는 직접 구현해줘야 함키->프로퍼티 이름 변환 규칙에 예외 사항이 필요할 때 GSON에서는 @SerializedName 어노테이션을 통해 손쉽게 지정할 수 있지만 iOS 구현에서는 예외 허용을 위한 깔끔한 방법을 찾기가 힘듬 (다만, 예외가 필요한 경우가 특별히 많지는 않기 때문에 큰 문제는 되지 않음)향후에는 HTTP 통신을 위해 사용 중인 AFNetworking(Objective-C로 작성됨)을 온전히 Swift로만 작성된 Alamofire로 교체하는 것을 검토 중이며 기존에 비해 좀 더 간결한 코드를 사용할 수 있을 것으로 기대하고 있습니다. 다만 Alamofire의 최신 버전이 iOS 8 이상을 지원하고 있어 iOS 7을 아직 지원 중인 리디북스인 관계로 언제 적용할 수 있을지는 아직 미지수입니다.#리디북스 #개발 #개발자 #iOS #iOS개발 #API #API클라이언트 #GSON #Retrofit #중복코드 #최소화 #API통신 #웹서버 
조회수 1873

아키텍트, 개발 리더십의 변화...

보통, 하나의 서비스를 개발하는데 얼마나 걸리며, 그 시간 동안 어떤 일을 '구체적'으로 진행시켜야 하느냐에 따라서 아키텍팅의 관점이 변화된다.자주 쓰는 장표 중의 하나이다. 간단하게 설명하면 과거의 비즈니스와 현재의 비즈니스의 차이를 디지털 서비스로 만들어 내는 기간으로 표시한 것이다.과거에는 하나의 디지털 비즈니스가 동작하기 위해서 데이터를 수집하고 분석, 기획, 구현, 실행하기까지 대부분 8.5개월에서 10개월 정도의 시간이 소요되었고, 이렇게 만들어진 서비스들은 실제 고객과 단절되어 있는, 내부 시스템에 가까웠다는 것을 표현한다.그리고, 디지털 비즈니스의 세계에서는 모바일로 실 고객과 커넥티드 되어 있으며, 각 비즈니스가 실제 수집부터 실행까지 1주에 동작되는 세계를 표현한다.이 차이는 정말 개발 조직과 개발 리더십에 많은 차이를 주게 된다.Classic Business에서는 8개월 이상의 방향성이 흔들리지 않도록, 전체적인 방향성이 흐트러지지 않도록 개발 리더십을 발휘하는 것이 중요했다. 특히, 초기의 개발 조직을 세팅하고 예산과 비즈니스의 완성과 실 서비스 후의 이익과 같은 경영적인 판단이 더 중요하던 시기였기 때문에, 실제 소프트웨어를 만들어내는 관점은 디테일하고, 기능적인 것에 집중화된 상태로 개발 조직이 구성되고, 리더십도 그것을 최대한 끌어내는 것에 집중했다.또한, 내부적 조직의 문제로 일이 더디게 진행되거나, 품질이나 세부적인 문제를 쥐어짜거나, 어떻게든 일정을 맞추기 위해서 조정하는 조정자의 역할도 매우 큰 상태였다. 개발 리더십도 그런 관점에서 구성되었고, 기술적인 변화도 거의 없이 초기에 결정된 상태로 대부분 진행되었다.그런데, Digital Business의 세계로 넘어오면 이것은 완전 다른 구도를 가지게 된다.1주 단위의 개발 및 배포까지 매우 유연한 상태로 가동되고, 이 단위는 기술적 선택과 실패가 매우 빠르게 반복되는 것을 의미하게 되며, 개발 조직은 말 그대로 작게 세분화되고, 전체적인 방향성은 계속 유동적으로 변화하게 된다.24시간 내에 하나의 개념이 수립되고, 이를 배포까지 진행시키기 위한 매우 다양한 시도들을 선택할 수 있게 하며, 기획 조직과 개발 조직이 하나의 '지표'나 '시각화'된 장표를 보고 빠르게 판단하게 할 수 있다.매우 빠른 순간 판단이 중요하며, '몇 분'간격으로 회사의 운명을 결정할 수 있는 서비스의 론칭도 가능하게 한다.관리적인 방법은 DevOps의 자동화된 환경과, 세분화된 배포 권한, 기획자들과의 유기적인 환경들을 보다 효율적으로 운용할 수 있는 방법들에 대해서 개발 리더십은 고민하게 된다.어떻게 빠르게 일을 효과적으로 움직일 것이며, 빠른 판단을 할 수밖에 없다. 빠르게 변화하는 기술 스택을 더 잘 알고 있는 것은 개발 조직이기 때문에, 아키텍트나 개발 리더의 권한은 계속 실무자에 가깝게 내려가게 되는 것이 순리에 가깝다.현재 DevOps를 지향하고 있는 개발 조직에서 아키텍트가 지향하는 것은 크게 개념적으로 변화한 것은 없다. '고객과 비즈니스를 이해하는 개발'임에는 틀림없으나, 기존의 아키텍팅과 많이 달라진 것은 실시간 서비스에 대한 분석과 기획의 변화, 데이터 중심의 개발 구조의 시각화를 통해서 개발 조직을 통제한다기보다는, 개발 조직을 숨 쉬게 만드는 '심장'과 같은 역할을 하게 된다.마치, 비즈니스가 빨라지면, 심장도 빨리 뛰고, 비즈니스가 좀 수월해지면 호흡을 고를 수 있는 형태...현재의 아키텍트는 개발 조직의 '심장'과도 같아.속도와 박자, 전체적인 흐름을 중시하는 것이 현재의 아키텍트의 역할이다.건축가인 아키텍트들에게는 엄청난 규칙과 법칙, 책임의 범위가 상당하다. 하지만, 소프트웨어 아키텍트들에게는 그런 책임이 법적으로 제시되고 있지 못하고 있다. 보통 소프트웨어 아키텍트라고 한다면, 부정적인 환경에서 제대로된 소프트웨어를 만들 수 없기 때문에 부당한 개발환경을 담당할 가능성이 없다는...그래서, SI현장에서 아키텍트는 거의 나오지 않는다고 봐야 한다. 슬프지만. 그리고, 마지막으로... 아키텍트는 '직위'나 '권위'가 아니다. '롤'일뿐이다. 그뿐...
조회수 1173

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

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

레진 기술 블로그 - 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처럼 골치아픈 동작 사례도 있는만큼 선택하지 않았습니다.물론 이 방법이 최선은 절대 아니며(심지어 배포할때마다 돈이 들어갑니다!), 현재는 자원의 활용 등 다른 측면에서의 고민 때문에 새로운 구성을 고민하고 있습니다. 이건 언젠가 나중에 다시 공유하겠습니다. :)

기업문화 엿볼 때, 더팀스

로그인

/