스토리 홈

인터뷰

피드

뉴스

조회수 1508

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

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

나는 이쁜 데일리룩을 보고 싶은걸? \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를 적용하면 비슷한 모습의 데일리룩들만 모아볼 수도 있고 스타일쉐어 유저들이 자주 찍는 자세 라던가 유저 별 자세 선호도 등등 재밌는 것들을 할 수 있을 것 같습니다.날 따라 해 봐요 같은 것도 해볼 수 있겠네요 :)같이 해보지 않을래요?아직도 재밌는 것들이 많이 남은 스타일쉐어 에서는 더 많은 것을 하기 위해 개발자분들을 모시고 있습니다 :)백엔드 개발자라고 해서 백엔드 개발에만 국한되지 않고 하고 싶은 것들을 해도 된다, 할 수 있다고 이야기해 주는 회사라고 생각합니다.스타일쉐어를 좀 더 알고 싶으시다면 여기를 눌러 주세요 :)#스타일쉐어 #개발팀 #개발자 #백엔드개발 #개발인사이트 #경험공유 #후기
조회수 1798

스켈티인터뷰 / 스켈터랩스의 스테로이드 서종훈 님을 만나보세요:)

Editor. 스켈터랩스에서는 배경이 모두 다른 다양한 멤버들이 함께 모여 최고의 머신 인텔리전스 개발을 향해 힘껏 나아가고 있습니다. 스켈터랩스의 식구들, Skeltie를 소개하는 시간을 통해 우리의 일상과 혁신을 만들어가는 과정을 들어보세요! 스켈터랩스의 스테로이드 서종훈 님을 만나보세요:)사진1. 스켈터랩스 스테로이드 서종훈 님Q. 진부한 첫 번째 질문, 자기소개를 부탁한다.A. 스켈터랩스에서 소프트웨어 엔지니어로 일하고 있는 서종훈이다. 연세대학교 컴퓨터과학과에서 HCI(Human-computer interaction)와 컴퓨터 비전(Vision)쪽 연구로 박사 학위를 받았다. 그리고 L모 기업의 AI연구소에서 일을 하다가 최근 스켈터랩스에 입사했다.Q. 어떻게 스켈터랩스에 입사하게 되었는지 궁금하다.A. 지인을 통해서 스켈터랩스의 여러가지 프로젝트에 대해 듣게 되었다. 스켈터랩스의 Inno Lab에서 진행 중인 프로젝트가 HCI와 가장 연관성이 깊고, 재미있는 디바이스를 구현하고 있어서 눈여겨 보다가 입사를 지원했다. 물론 프로젝트의 방향성이 나의 관심 분야와 일치하는지 뿐만 아니라, 함께 일하는 사람이 어떤 사람인지 알아보는 과정도 필요했다. 다행히 스켈터랩스에 지인이 있었고, 그의 소개로 하드웨어 엔지니어팀을 이끌고 있는 재경 님을 비롯하여 다른 팀원들을 미리 만날 수 있었다. 긴 대화 끝에 회사의 조직문화나 방향성의 결이 나와 맞는다는 생각을 했다. 뛰어난 개발자가 많기 때문에 내가 계속 성장해나갈 수 있는 환경이라는 점도 입사 결심을 굳히게 된 큰 요소 중 하나다.Q. 스켈터랩스에서는 어떤 업무를 맡고 있는가. A. 스마트 거울 샘(Samm)의 제스처 인식을 담당하고 있다. 이미지 인식을 기반으로 하는 작업이기 때문에 카메라로 구현을 하는게 맞을 지, 혹은 센서를 사용하는 것이 좋을지를 테스트하며 최적의 답을 찾아내려 하고 있다. 또한 엔도어 솔루션(Endor Solution, 공정 과정에서 부품의 결함을 자동으로 검출하는 솔루션)이 더욱 뛰어난 성능을 발휘할 수 있도록 개발에 참여하고 있다. 기존의 팀원들 모두가 딥러닝 경험이 풍부하다. 반면 전통적인 비전(Vision) 쪽 경험은 상대적으로 내가 더 풍부하기 때문에, 데이터처리나 고전적인 방법을 적용한 개발을 통해 엔도어 솔루션을 탄탄하게 보완하려고 한다. 텐서플로우(Tensorflow) 기반으로 기존의 팀이 일해왔다면, 나는 OpenCV를 통해 선행 데이터를 처리한다.사진2. 영화 <마이너리티 리포트>에 등장하는 G-SpeakQ. 비전 기술에 관심을 갖게 된 특별한 계기가 있었는지 궁금하다.A. 글쎄, 계기라고 말하기는 힘들다. 그냥 자연스럽게 HCI쪽에 관심을 가지게 되었고, 그러다보니 다양한 인터페이스를 구현하는 일을 맡아왔다. 당시 HCI가 붐이었고, 아이폰이 이제 막 세상에 등장한 시기이기도 했다. 그런데 HCI 분야의 개발을 지속할수록, 사람들에게 편리한 방식으로 원하는 것을 제공할 수 있는 분야에서 비전 기술은 필수라는 생각이 들더라. 웨어러블 디바이스를 사용하지 않고서도, 개개인의 행동을 관찰하고 그에 맞게 적절한 가이드를 제시하는 것은 모두 비전을 바탕으로 한다. 스티븐 스필버그 감독의 톰 크루즈 주연 영화 <마이너리티 리포트>를 보면, 톰 크루즈가 특수장갑을 착용한 채 스크린을 제어하는 장면이 등장한다. 양손을 사용하여 자유자재로 허공에 활성화시킨 스크린을 제어하는데, 이 장면은 단지 영화 연출이 아닌 실제로 개발된 기술에서 영감을 얻은 장면이다. 기술의 명칭은 ‘G-스피크(G-Speak)’. 이 혁신적인 기술을 개발한 존 언더코플러(John Underkoffler)는 영화 자문 이후, ‘오블롱 인더스트리즈(Oblong Industries)’라는 회사를 설립했다. 사실 ‘G-스피크'를 구현하기 위한 개별 기술들은 당시에도 굉장히 많았다. 오블롱의 차별점은 이 다양한 개별 기술을 하나로 통합한다는 점이다. 오블롱의 행보를 관찰하며, 비전 기술의 활용도에 대해 일종의 확신을 강하게 품게 되었다. NUI(Natural User Interface) 기술이 보편화되면, 기존 오퍼레이션 시스템 환경은 크게 변화할 것이다. 그때 일반 소비자에게 편하게 와닿을 수 있는 새로운 인터페이스를 선도하는 회사가 시장의 선도자가 될 것이고, 비전 기술은 시장 선도자의 핵심일 것이라고 생각하고 있다.Q. 여러 프로젝트에 동시에 참여하고 있기 때문에, 각 팀마다 업무 방식이 어떻게 다른지를 경험했을 것 같다. 그 이야기를 듣고싶다.A. 기본적으로 분위기가 굉장히 다르다. 엔도어 솔루션은 기업의 사설연구소의 느낌이랄까, 굉장히 학구적인 느낌이 강하다. 딥러닝과 관련된 많은 논문을 읽고 깊이 있게 연구하고자 한다. 많은 실험도 필수적으로 병행되는데, 내부적으로는 각 논문과 실험을 통해 얻은 인사이트를 정리하고 공유하고자 노력하고 있다. 이러한 과정을 통해 기존의 다양한 모델을 조합하고 자체적인 모델 개발을 통해 최적의 결과물을 구축하려고 한다. 반면 Inno Lab의 다양한 프로젝트는 오히려 내가 기대했던 스타트업스러운 느낌이 있다. 기존에 없던 디바이스를 만들어 내기 위해 다같이 아이디에이션 과정을 진행했다. 그리고 빨리 구현하고 피드백을 취합한 후, 다시 개발에 들어가는 과정이 꽤 다이나믹하게 이뤄진다. 현재 개발 중인 샘 덕분에 주변의 신기하고 재미있는 디바이스를 검색해보고, 직접 써보고 있는데 덕분에 굉장히 얼리어답터가 된 듯한 느낌이다.사진3. 종훈 님의 일하는 모습을 몰래 촬영해보았다Q. 동시에 결이 다른 두 개의 프로젝트를 진행하기가 어려울 것 같다.A. 어렵다. 그래서 나는 아예 프로젝트마다 기한일을 설정한다. 한 분야에 몰입해서 쭉 끌고 나가는 것이 내게는 더 맞는 느낌이라, 각 프로젝트의 PM과 상의하여 샘 개발에 15일까지 참여한다면, 월 말까지는 엔도어 솔루션에 참여하는 식으로 조정한다.Q. 이전 직장과 스켈터랩스의 업무가 어떻게 다른지도 궁금하다.A. 이제 스켈터랩스에 합류한지 3개월이 좀 지났는데, 크게는 두 가지가 가장 다른 점이자 만족스러운 점인 것 같다. 첫 번째는 일단 개발 환경이다. 스켈터랩스는 개발 환경이 굉장히 빠르고 선진적이다. 개발을 워낙 잘 하시는 분들이 많기 때문에 협업하면서 배울 점도 많고 협업을 통한 시너지도 강하다. 여러가지 툴을 똑똑하고 빠르게 잘 활용하는 것도 업무 효율을 크게 향상시키는 부분이다. 구글 드라이브, 깃허브(GitHub) 뿐만 아니라, 유트랙(Youtrack)과 같은 이슈트래커(Issue Tracker)도 적극 활용한다. 클라우드 환경, 빌드 환경 등도 모두 유연하게 잘 갖춰져있다. 이전 회사가 폐쇄적으로 운영되었던 부분이 있어서 상대적으로 이런 부분을 더 만족스럽게 생각한다. 스타트업인 만큼, 신기술에 대해서 팔로우하고 적용시켜 보려는 과정이 빠르게 일어나고 있는 점도 좋다. 두 번째는 ‘함께 하고 있다'라 느낌이 강하다는 것이다. 이전에는 워낙 프로젝트의 규모도 컸기 때문에, 각자 맡은 업무의 경계선이 분명하게 그어져있었다. 그러나 스켈터랩스는 잦은 미팅을 통해 함께 기획부터 참여하기 때문에 ‘우리의 것'을 만들어낸다는 느낌을 준다.Q. 스켈터랩스에서 가장 애정하는 조직문화가 있다면?A. 맥주를 먹으면서 일할 수 있다는 것(스켈터랩스에는 맥주 디스펜서가 구비되어 있다)! 다이어트를 하고는 있지만 워낙 맥주를 좋아하는 나로서는, 개발이 잘 안풀릴 때 맥주를 먹으면서 일을 할 수 있다는 것 자체가 만족스럽다. 매주 금요일마다 함께 모여서 회사의 여러 프로젝트 진행 상황을 듣고, 구성원에 대해서 알아보는 시간인 올핸즈(All-hands)도 좋아한다. 보통 다른 회사의 경우 정보가 총체적으로 전달되지 않고, 쪼개진 정보만이 내려오는 경우가 많다. 하지만 올핸즈 덕분에 회사의 정보들이 모두에게 공유될 수 있고, 또한 참여할 수 있다고 생각한다.Q. 비슷한 질문이지만 회사 자랑을 위해 하나 더 묻고싶다. 스켈터랩스에서 가장 자랑하고 싶은 점을 꼽는다면 무엇일까.A. 두 가지를 꼽고 싶다. 먼저 자유로운 문화라는 점. 한국에서 정말 몇 안되는 실리콘밸리의 분위기를 풍기는 곳이라고 생각한다(단순히 나만의 의견이 아니라, 실제 실리콘밸리에서 근무하는 친구가 사무실에 놀러왔을 때 ‘실리콘밸리 같다'라고 표현했다). 겉으로는 허름한 창고같은 사무실이지만, 문만 열리면 다른 세계가 펼쳐지는 듯한 느낌을 받을 수 있다. 자유롭게 의견을 내고 토론을 하는 문화도 이 사무실의 분위기와 일맥상통한다. 두 번째는 개개인의 실력이 높아서 정말 배울 것이 많다는 점이다. 그게 한편으로는 스트레스기도 하다. ‘내 밑천이 바닥나면 안될텐데'라는 생각에 책과 다양한 소스를 통해 끊임없이 자발적으로 공부하게 만든다. 실제 개발자 중 몇 분은 구글에서 개발자 레벨의 최고 등급을 받은 것으로 알고있다. 개발 실력은 당연히 코드에 묻어나온다. 다른 개발자의 코드를 보면서도 많은 영감을 얻을 수 있고, 코드 리뷰에 참여하는 것 만으로도 개발 실력이 향상될 수 있다.Q. 자유로운 출퇴근 문화지만, 종훈 님은 꽤 일찍 출근하는 편으로 알고 있다. 하루 일과가 궁금하다.A. 집에서 아침 시간을 여유롭게 즐기는 편이다. 여섯시에 일어나 아침 밥을 집에서 챙겨먹고 출근하고 있다. 일찍 출근할수록, 그 날 내가 목표로 한 업무를 빨리 마치고 퇴근할 수 있기 때문에 너무 늦게는 출근하지 않으려 한다. 덕분에 규칙적으로 일곱시 쯤에는 퇴근을 마치고 운동을 한다. 주말에도 주로 운동을 즐기는 편인데, 요즘에는 토요일마다 꼬박 꼬박 딥러닝 스터디를 하고있다. 나는 전통적인 비전(Vision) 연구를 해왔기 때문에, 딥러닝 쪽은 바탕 지식이 얕은 편이다. 업무를 진행하는데 큰 어려움은 없지만, 회사 프로젝트의 좋은 결과물을 내기 위해서는 딥러닝을 썼을 때 효율적인 부분이 크다. 때문에 많은 시간을 공부에 할애하는 것 같다.Q. 스켈터랩스 헬스동호회 스켈터 스테로이드의 수장으로 알고있다. 동아리를 소개한다면?A. 동호회를 만들게 된 계기는 단순하다. 새 회사에 왔으니, 새로운 몸을 만들겠다는 마음이었다. 사실 헬스는 누군가랑 같이 하는 운동은 아니지않나. 그래도 동호회원들 덕분에 ‘오늘은 그냥 좀 운동을 쉴까’ 싶다가도 누군가가 먼저 나서면 ‘그래도 빠지지 말아야지'란 생각에 꼬박꼬박 운동을 가게된다. 일주일에 두 번이니, 부담스럽지 않은 양이기도 하다. 내가 수장인 만큼 본보기로 열심히 나가야한다는 일종의 책임감도 꾸준히 운동을 이어나가는 원동력이 되었다. 날씨가 추워지면서, 다들 몸을 만들겠다는 의지가 약해져서인지 최근에는 참여률이 떨어지고 있다. 실내에서 할 수 있는 다양한 운동 종목을 더해, 참여를 높이는 방법을 고민 중이다. Q. 운동을 꾸준히 해오고 있는데, 헬스 동호회를 통해 목표했던 성취는 이루었는지 궁금하다.A. 동호회 소개를 하며 ‘이틀 밤을 새도 지지않는 체력을 얻어갈 수 있습니다'라고 공표했는데, 변명이지만 목표가 너무 거창했던 것 같다. 이틀 밤을 새도 지지않는 체력이 갖기 위해 갈 길이 멀다. Q. 이제 인터뷰를 마무리할 단계다. 스켈터랩스가 어떤 회사가 되면 좋겠는가.A. 앞서 말했던 오블롱 인더스트리즈나 센스타임(Sensetime)처럼, 확고한 기술력으로 시장의 선두주자가 되었으면 한다. 이를 위해서는 논문도 많이 내야할 것이고, 더욱 많은 개발자와 함께 기술을 더 깊게 파고드는 과정이 지속되어야 한다. 또한 스타트업으로서 시장의 성패와 상관 없이 가치있고 재미있는 개발을 많이 하면 좋겠다. 현재로서는 Inno Lab이 이러한 성격을 띠고 있다. 그래서 일단은 프로젝트 중 하나인 샘을 성공적으로 런칭하는 것이 나의 목표다.Q. 진짜 마지막 질문. 앞선 질문과 비슷하지만, 개인적인 꿈이 있다면?A. 오래 일하고 싶다. 나이가 들어서도 시장의 흐름을 읽고, 새로운 기술에 대한 충분한 이해와 개발력을 갖춘 사람으로 오래오래 일하고 싶다. 사실 일반적으로 개발자의 수명은 길지 않다. 그래서 창업에 대한 욕심도 품고 있다. 스켈터랩스의 CEO인 테드 님을 보면서 한편으로는 기업 운영 노하우를 배워나간다는 생각도 있다. 향후에는 스켈터랩스의 경쟁사를 내가 세울 수도 있지 않을까(테드 님이 이걸 보면 뭐라하실지 걱정이긴 하다).#스켈터랩스 #사무실풍경 #업무환경 #사내복지 #기업문화 #팀원인터뷰 #팀원소개 #팀원자랑
조회수 9980

Next.js 튜토리얼 7편: 데이터 가져오기

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기 2편: 페이지 이동 3편: 공유 컴포넌트4편: 동적 페이지 5편: 라우트 마스킹6편: 서버 사이드 7편: 데이터 가져오기 - 현재 글8편: 컴포넌트 스타일링9편: 배포하기개요꽤 그럴듯한 Next.js 애플리케이션을 만드는 방법과 Next.js 라우팅 API의 모든 장점을 배웠습니다.대부분의 경우 데이터 소스에서  원격으로 데이터를 가져와야 합니다. Next.js는 페이지에 데이터를 가져오기 위한 표준 API를 제공합니다. getInitialProps라 불리는 비동기 함수를 사용하여 구현할 것입니다.주어진 페이지에 원격 데이터 소스를 통해 데이터를 가져오고 원하는 페이지에 props을 통해 전달할 수 있습니다. 서버와 클라이언트 둘 다 동작하도록 getInitialProps를 작성할 수 있습니다. 그래서 Next.js는 클라이언트와 서버에서 모두 사용할 수 있습니다. 이번 편에서는 getInitialProps를 사용하여 공개된 TVmaze API에서 가져온 데이터로 배트맨 TV 쇼에 대한 정보를 보여주는 애플리케이션을 구현할 예정입니다.설치이번 장에서는 간단한 Next.js 애플리케이션이 필요합니다. 다음의 샘플 애플리케이션을 다운받아주세요:아래의 명령어로 실행시킬 수 있습니다:이제 http://localhost:3000로 이동하여 애플리케이션에 접근할 수 있습니다.배트맨 쇼 데이터 가져오기데모 애플리케이션 내의 home 페이지에 블로그 포스트 목록이 있습니다. 배트맨 TV 쇼 목록을 표시할 것입니다.쇼의 데이터들을 하드코딩하는 대신에 원격 서버에서 그 정보를 가져옵시다.여기서는 TV 쇼를 가져오기 위해 TVMaze API를 사용합니다.TV 쇼 정보를 검색하는 API 입니다.먼저 isomorphic-unfetch를 설치해야 합니다. 데이터를 가져올 때 사용할 라이브러리입니다. 브라우저 fetch API 구현을 간단히 할 수 있도록 만들어진 것이지만 클라이언트와 서버 환경에서 모두 동작합니다.npm install --save isomorphic-unfetchpages/index.js를 다음과 같이 변경해주세요:위의 페이지에 있는 모든 내용은 아래에 표시된 Index.getInitialProps를 제외하고는 익숙할 것입니다:애플리케이션의 어떤 페이지에든 추가할 수 있는 정적 비동기 함수입니다. 이것을 사용하여 데이터를 가져오고 가져온 데이터를 props를 통해 페이지로 보낼 수 있습니다.보다시피 배트맨 TV 쇼 데이터를 가져오고 'shows' props를 통해 페이지로 전달합니다.위에서 보았던 getInitialProps 함수에서 가져온 데이터 숫자를 콘솔에 출력합니다.이제 브라우저 콘솔과 서버 콘솔을 살펴봅시다. 그리고 페이지를 새로고침 해주세요.페이지를 새로고침 한 후 출력되는 메시지는 어디에서 보였나요?- 서버 콘솔- 브라우저 콘솔- 둘 다- 어떤 콘솔에도 출력되지 않았다서버에서만 출력됩니다이 경우 메시지는 서버에서만 출력됩니다.이는 서버에서 페이지가 랜더링되기 때문입니다.이미 데이터를 가지고 있어 클라이언트에서 다시 정보를 가져올 필요가 없습니다.post 페이지 구현하기TV 쇼에 대한 자세한 정보를 보여주는 "/post" 페이지를 구현해봅시다.먼저 server.js를 열고 /p/:id 라우트를 다음과 같이 바꿔주세요.위처럼 바꾼 코드를 적용하기 위해 애플리케이션을 재실행시켜주세요.이전에는 title 쿼리 파라미터를 페이지에 매핑했습니다. 이제 id로 이름을 바꿔야합니다.다음과 같은 내용으로 pages/post.js를 변경해주세요.페이지의 getInitialProps을 살펴봅시다:여기에서 함수의 첫 번째 파라미터는 context 객체입니다. 정보를 가져올 때 사용할 수 있는 쿼리 필드를 가지고 있습니다.예제에서 쿼리 파라미터로부터 보여지는 ID를 선택하고 TVMaze API로부터 데이터를 가져옵니다.이 getInitialProps 함수에서 표시할 제목을 출력하는 console.log를 추가했습니다. 이제 어디에서 출력되는지 볼 수 있습니다.서버와 클라이언트의 콘솔를 둘 다 열어주세요.그 다음 홈페이지 http://localhost:3000로 이동하여 배트맨 쇼 제목을 클릭하세요.위에서 애기했던 console.log 메시지가 보여지는 장소는 어디인가요?- 서버 콘솔- 브라우저 콘솔- 콘솔 둘 다- 아무 콘솔에서도 출력되지 않는다클라이언트 사이드에서 데이터 가져오기브라우저 콘솔에서 메시지를 볼 수 있습니다.클라이언트 사이드를 통해 포스트 페이지에 이동했기 때문입니다. 그런 다음 클라이언트 사이드로부터 데이터를 가져오는 것은 가장 좋은 방법입니다.예를 들어 http://localhost:3000/p/975에 직접 이동한다면 클라이언트가 아닌 서버에서 메시지가 출력되는 것을 볼 수 있습니다.마무리데이터를 가져오고 서버 사이드에서 렌더링하도록 만드는 Next.js의 가장 중요한 기능 중 하나를 배웠습니다.대부분의 유스 케이스에서 충분히 사용할 수 있는 getInitialProps의 기본을 배웠습니다. 더 많은 것을 배우고 싶다면 Next.js의 문서 중 data fetching 문서를 참고할 수 있습니다.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 1729

AWS Rekognition + PHP를 이용한 이미지 분석 예제 (2/2)

이전 글 보기: AWS Rekognition + PHP를 이용한 이미지 분석 예제 (1/2)Overview지난 글에서는 AWS Rekognition을 이용해 S3 Bucket에 업로드한 이미지로 이미지 분석 결과를 확인했습니다. 이번엔 더 나아가 Collection(얼굴 모음)을 생성해보고, 얼굴 검색을 해보겠습니다.1. Collection 만들기Collection은 AWS Rekognition의 기본 리소스입니다., 생성되는 각각의 컬렉션에는 고유의 Amazon 리소스 이름(ARN)이 있습니다. 컬렉션이 있어야 얼굴들을 저장할 수 있습니다. 저는 ‘BrandiLabs’라는 이름의 Collection을 생성했습니다.1-1. createRekognition 메소드를 이용해 손쉽게 Collection 을 생성합니다.# 클라이언트 생성 $sdk = new \\Aws\\Sdk($sharedConfig); $rekognitionClient = $sdk->createRekognition(); # 모음(Collection) 이름 설정 $collection = array('CollectionId' => 'BrandiLabs'); $response = $rekognitionClient->createCollection($collection); 1-2. Collection이 정상적으로 생성되었다면 아래와 같은 응답을 받습니다.[ { "StatusCode" : 200 "CollectionArn" : "aws:rekognition:region:account-id:collection/BrandiLabs" /*...*/ } ] 2. Collection에 얼굴 추가IndexFaces 작업을 사용해 이미지에서 얼굴을 감지하고 모음에 추가할 수 있습니다. (JPEG 또는 PNG) 모음에 추가할 이미지에 대해서는 몇 가지의 권장사항[1]이 있습니다.두 눈이 잘 보이는 얼굴 이미지를 사용합니다.머리띠, 마스크 등 얼굴을 가리는 아이템을 피합니다.밝고 선명한 이미지를 사용합니다.권장사항에 최적화된 사진은 S3 Bucket 에 업로드되어 있어야 합니다. 미리 ‘kimwk-rekognition’ 이라는 이름으로 버킷을 생성 후 제 사진과 곽정섭 과장님의 사진을 업로드해두었습니다.2-1. IndexFaces 메소드를 이용해 얼굴을 추가합니다. 예시에서는 제 얼굴과 곽 과장님의 얼굴을 인덱싱했습니다.$imageInfo = array(); $imageInfo['S3Object']['Bucket'] = 'kimwk-rekognition'; $imageInfo['S3Object']['Name'] = 'kwakjs.jpg'; $parameter = array(); $parameter['Image'] = $imageInfo; $parameter['CollectionId'] = 'BrandiLabs'; $parameter['ExternalImageId'] = 'kwakjs'; $parameter['MaxFaces'] = 1; $parameter['QualityFilter'] = 'AUTO'; $parameter['DetectionAttributes'] = array('ALL'); $response = $rekognitionClient->indexFaces($parameter); 각각의 요청 항목에 대한 상세 설명은 아래와 같습니다.Image : 인덱싱 처리할 사진의 정보입니다.CollectionId : 사진을 인덱싱할 CollectionId 입니다.ExternalImageId : 추후 인식할 이미지와 인덱싱된 이미지를 연결할 ID 입니다.MaxFaces : 인덱싱되는 최대 얼굴 수 입니다. 작은 얼굴(ex. 배경에 서 있는 사람들의 얼굴)은 인덱싱하지 않고 싶을 때 유용합니다.QualityFilter : 화질을 기반으로 얼굴을 필터링하는 옵션입니다. 기본적으로 인덱싱은 저화질로 감지된 얼굴을 필터링합니다. AUTO를 지정하면 이러한 기본 설정을 명시적으로 선택할 수 있습니다. (AUTO | NONE)DetectionAttributes : 반환되는 얼굴 정보를 다 가져올 것인지 아닌지에 대한 옵션입니다. ALL 로 하면 모든 얼굴 정보를 받을 수 있지만 작업을 완료하는데 시간이 더 걸립니다. (DEFAULT | ALL)2-2. Collection에 정상적으로 얼굴이 추가되었다면 아래와 같은 응답을 받습니다. 사진 속 인물의 성별, 감정, 추정 나이 등의 정보를 확인할 수 있습니다.[ { "Face":{ "FaceId":"face-id", "BoundingBox":{ "Width":0.28771552443504333, "Height":0.3611610233783722, "Left":0.39002931118011475, "Top":0.21431422233581543 }, "ImageId":"image-id", "ExternalImageId":"kimwk", "Confidence":99.99978637695312 }, "FaceDetail":{ "BoundingBox":{ "Width":0.28771552443504333, "Height":0.3611610233783722, "Left":0.39002931118011475, "Top":0.21431422233581543 }, "AgeRange":{ "Low":20, "High":38 }, "Smile":{ "Value":false, "Confidence":85.35209655761719 }, "Eyeglasses":{ "Value":false, "Confidence":99.99824523925781 }, "Sunglasses":{ "Value":false, "Confidence":99.99994659423828 }, "Gender":{ "Value":"Male", "Confidence":99.35176849365234 }, "Beard":{ "Value":false, "Confidence":94.80714416503906 }, "Mustache":{ "Value":false, "Confidence":99.92304229736328 }, "EyesOpen":{ "Value":true, "Confidence":99.64280700683594 }, "MouthOpen":{ "Value":false, "Confidence":99.4529037475586 }, "Emotions":[ { "Type":"HAPPY", "Confidence":2.123939275741577 }, { "Type":"ANGRY", "Confidence":6.1253342628479 }, { "Type":"DISGUSTED", "Confidence":19.37765121459961 }, { "Type":"SURPRISED", "Confidence":7.136983394622803 }, { "Type":"CONFUSED", "Confidence":30.74079132080078 }, { "Type":"SAD", "Confidence":9.113149642944336 }, { "Type":"CALM", "Confidence":25.382152557373047 } ], "Landmarks":[ { "Type":"eyeLeft", "X":0.45368772745132446, "Y":0.31557807326316833 }, … ], "Pose":{ "Roll":5.615509986877441, "Yaw":-5.510941982269287, "Pitch":-17.47319793701172 }, "Quality":{ "Brightness":93.13915252685547, "Sharpness":78.64350128173828 }, "Confidence":99.99978637695312 } } ] 3. 얼굴 검색드디어 얼굴 검색의 시간이 왔습니다. searchFacesByImage 메소드를 이용하면 지금까지 그래왔던 것처럼 쉽게 얼굴 검색을 할 수 있습니다. 저는 ‘kimwk2.jpg’ 라는 또 다른 제 얼굴 사진을 S3 Bucket에 업로드해뒀습니다. 얼굴 검색이 제대로 이루어졌다면 응답으로 제 ExternalImageId (kimwk) 가 내려올 것입니다. 한 번 해볼까요?3-1. searchFacesByImage 메소드를 이용해 얼굴 검색을 합니다.$imageInfo = array(); $imageInfo['S3Object']['Bucket'] = 'kimwk-rekognition'; $imageInfo['S3Object']['Name'] = 'kimwk2.jpg'; $parameter = array(); $parameter['CollectionId'] = 'BrandiLabs'; $parameter['Image'] = $imageInfo; $parameter['FaceMatchThreshold'] = 70; $parameter['MaxFaces'] = 1; $response = $rekognitionClient->searchFacesByImage($parameter); 3-2. 정상적으로 검색이 되었다면 아래와 같은 응답을 받습니다.[ { "Similarity":99.04029083251953, "Face":{ "FaceId":"FaceId", "BoundingBox":{ "Width":0.23038800060749054, "Height":0.2689349949359894, "Left":0.2399519979953766, "Top":0.08848369866609573 }, "ImageId":"ImageId", "ExternalImageId":"kimwk", "Confidence":100 } } ] SearchFacesByImage는 기본적으로 알고리즘이 80% 이상의 유사성을 감지하는 얼굴을 반환합니다. 유사성은 얼굴이 검색하는 얼굴과 얼마나 일치하는지를 나타냅니다. FaceMatchThreshold 값을 조정하면 어느 정도까지 유사해야 같은 얼굴이라고 허용할지를 정할 수 있습니다.Conclusion이미지 분석 알고리즘과 얼굴 검색 기능을 직접 구현하려 했다면 시간이 많이 걸렸겠지만 AWS 서비스를 이용하면 이미지 분석을 금방 할 수 있습니다. 이 기능을 잘 활용하면 미아 찾기나 범죄 예방과 같은 공공 안전 및 법 진행 시나리오에도 응용할 수도 있겠죠. 다음엔 보다 재밌는 주제로 찾아오겠습니다.참고[1] 얼굴 인식 입력 이미지에 대한 권장 사항[2] Amazon Rekonition 개발자 안내서[3] 모든 예제는 AmazonRekognition, AmazonS3에 대한 권한이 있어야 함글김우경 대리 | R&D 개발1팀kimwk@brandi.co.kr브랜디, 오직 예쁜 옷만
조회수 444

컴공생의 AI 스쿨 필기 노트 ⑦합성곱 신경망

안녕하세요! 이번 주 수업에서는 합성곱 신경망에 대해서 배웠어요. 제가 읽은 한 기사에 의하면 대장 내시경 검사에도 딥러닝을 이용하면 종양 식별 능력을 더 높일 수 있다고 해요. 딥러닝을 이용한 검사는 전문가 분석을 통한 대장 내시경 검사보다 종양을 9개 더 많이 발견했고 진단 정확도는 96%인 것으로 나타났어요. (원문 링크) 이 대장 내시경에 우리가 배운 CNN(Convolutional Neural Network), 이미지 기반 딥러닝 모델을 사용했다고 하는데요. 이 대장 내시경에 사용된 CNN에 대해 알아볼까요? (Cover image : Photo by Paul Carmona on Unsplash)CNN(Convolutional Neural Network, 합성곱 신경망) CNN(합성곱 신경망)은 Convolution(합성곱)연산을 사용하는 인공신경망의 한 종류에요. 합성곱 신경망은 주로 이미지 데이터를 다루는 문제에서 사용돼요. 쉽게 말해 합성곱 신경망은 이미지의 특징을 추출하고 잘 조합하여 문제를 해결하는데요.  예를 들어 왼쪽 이미지가 고양이인지 컴퓨터가 알아맞히기 위해서 합성곱 신경망은 고양이가 가져야 할 특징을 한 번에 파악하는 것이 아니라 부분부분 판단하여 종합적으로 결론을 내요. 합성곱 신경망은 사진에 고양이의 특징이 기묘하게 분포되어 있어도 정확하게 고양이의 특징을 찾아내는 높은 적응도를 갖고 있어요.이제 합성곱 신경망의 구조에 대해 알아볼까요?CNN의 네트워크 구조1. 합성곱 층 (Convolutional Layer)합성곱은 두 함수 중 하나를 반전하고 이동시켜가며 다른 하나의 함수와 곱한 결과를 적분해나간다는 아주 어려운 뜻을 가지고 있어요. 다음 예시를 보도록 할게요.여기에 2차원 배열 픽셀을 넣으면 X 인지 O 인지 알아내는 합성곱 신경망이 있다고 해봐요.이 합성곱 신경망은 똑바로 된 X와 O를 넣으면 X 인지 O 인지 정확하게 구분하는데,이렇게 크기가 바뀌고 회전되어 모양이 변형된 이미지를 보고도 X 인지 O 인지 정확히 구분할 수 있을까요?합성곱 신경망은 합성곱 신경을 이용하여 이미지의 특징을 매칭 시킨 결과가 같으면 같은 이미지라고 인식해요.‘X’ 이미지의 특징을 추출하면 위와 같은 매트릭스, 합성곱 필터(Convolution filter(=커널))가 나와요. (세 특징을 잘 조합하면 X 형태가 나오죠?)이제 제일 왼쪽의 합성곱 필터를 가지고서 이미지가 X 인지 알아볼게요. 합성곱 필터와 원본 이미지를 비교할 때는 곱셈을 이용해요. 합성곱 필터의 크기만큼 원본 이미지와 차례차례 곱해서 값을 채워나가요.위의 합성곱 정의에서 두 함수를 하나는 이미지, 또 하나는 필터라고 생각하면, 필터를 이동시켜가며 이미지와 곱한 결과를 적분 즉, 덧셈해 나간다는 뜻이 돼요.합성곱 필터의 크기만큼 값을 다 계산한 후, 계산한 원소를 다 더해서 합성곱 필터의 크기만큼 나눈 평균값을 또 다른 새 매트릭스에 채우게 되는데 이를 특징 맵(Feature map)이라고 불러요. 즉, 특징 맵은 기존의 이미지에 필터를 곱한 결과로 각 픽셀에 쓰여있는 값이 클수록 그 특징을 가지고 있다는 뜻이에요.이렇게 원본 이미지와 합성곱 필터의 곱한 결과로 특징 맵이 나왔어요.나머지 두 개의 합성곱 필터와 곱한 결과로 두 개의 특징 맵을 가질 수 있어요.한 개의 합성곱 층(Convolutional layer)에는 여러 개의 합성곱 필터가 있어요. 합성곱 층에서 기존의 이미지와 필터들을 합성곱한 결과, 처음 이미지는 필터 된 이미지(특징 맵)로 쌓이게 돼요.2. 풀링 층(Pooling Layer)풀링은 가로/세로 방향의 공간을 줄이는 연산으로 합성곱 층의 특징을 압축한 특징 맵을 형성해요. 풀링에는 최대 풀링(Max pooling)과 평균 풀링(Average pooling)이 있는데 이미지 인식 분야에서 주로 사용하는 것은 최대 풀링이에요. 그래서 보통 풀링이라고 하면 최대 풀링이라는 의미로 사용한다고 보시면 돼요.위의 예시는 2x2 최대 풀링을 적용한 예시에요. 아까 구한 특징 맵에서 2x2 픽셀에서 가장 큰 원소 값을 새로운 맵을 채워나가는데 이를 활성화 맵(Activation map)이라고 불러요. 최대 풀링을 사용하면 노이즈가 감소하고 속도도 빨라지며 영상의 분별력이 좋아진다고 해요. 마지막 출력 층은 최대 풀링의 모든 뉴런과 연결되어 출력값이 어떤 클래스에 해당하는지 파악되는데 사용돼요.이렇게 CNN을 이용하면 변형된 이미지라고 하더라도 원래 이미지의 특징을 가지고 있다면 정확히 구분할 수 있어요.코드로 연습해보기아래는 간단한 인공신경망 코드예요.Layer 1 - input:1x28x28 , output : 64x28x28 + Activation function - reluLayer 2 - input: 64x28x28 output:1x28x28Layer 3 - input: 1x28x28=784 output:10class MNIST_Net(nn.Module):    def __init__(self):        super(MNIST_Net, self).__init__() # nn.Module 생성자 호출         # an affine operation: y = Wx + b        layers = []        layers.append(nn.Conv2d(1,64,3,1,1))         layers.append(nn.ReLU())         layers.append(nn.Conv2d(64,1,3,1,1))         layers.append(nn.ReLU())         self.main = nn.Sequential(*layers)        self.fc = nn.Linear(28*28, 10)    def forward(self, x):        # x.view함수는 주어진 인자의 크기로 해당 데이터의 크기를 반환합니다. 즉, (Batch_size,1,28,28) --> (Batch_size,28*28)로 변환합니다.        x = self.main(x)        x = x.squeeze().view(-1, 28*28)        x = self.fc(x)  # 10 으로 10개의 Class에 대한 logit 값을 호출합니다.         return x합성곱 인공 신경망의 내용은 정말 배울 것이 많아서 수업 시간 내에 다 배우기가 조금 벅찼지만 다른 인공 신경망에 비해 재밌어서 집중할 수 있었어요. 이제 앞으로 1번의 이론수업만을 남겨두고 있어서 아쉽기도 하고 또 뿌듯하기도 해요. 앞으로 조원들과 함께 프로젝트를 진행하면서 지금까지 배운 이론을 적용해보게 되는데요. 프로젝트 주제를 정하는 것부터 벌써 쉽지가 않아요. 하지만 열심히 프로젝트를 해서 리쿠르팅 데이 때 실력을 뽐낼 수 있다면 좋겠네요!* 이 글은 AI스쿨 - 인공지능 R&D 실무자 양성과정 7회차 수업에 대해 수강생 최유진님이 작성하신 수업 후기입니다.
조회수 3941

100일 간의 챗봇 디자인 실패기-1편

디자인 학도로서 4년 넘게 학교에서 UI/UX를 공부했다. 또래에 비해 학교를 오래 다녔으며 해당 분야에 대한 관심도 남달랐거니와, 심지어는 UI 디자인 소프트웨어를 만드는 회사에 다닌 경험이 있는 만큼 실무적으로는 아직 많이 부족할 지라도 이론만큼은 이제 어느 정도 자신이 있다고 생각했다.그런데 대체 이 녀석은 또 뭐지. 챗봇이라니.   지난 1월, 새로운 사업을 결심한 팀원들과 사업구상을 하며 챗봇이라는 아이템을 마주하게 되었다. 우리가 챗봇에 대한 무한 신뢰를 했던 이유는 한 가지였다. '일상적 편리함에 있어 메신저만 한 것은 없다'는 것.한때 SNS에 화제가 되었던 '엄마의 메모장'챗봇은 이미 한 차례 미국 본토를 강타하고 조금씩 국내 시장에 진입하고 있던 상황이었고, 새로운 기술에 호기심을 가진 우리 팀은 챗봇에 희망을 품고 해당 분야에 대한 학습을 진행하기 시작했다.  자연어 처리, 형태소 분석 등 기술적인 부분들을 개발팀원들이 검토하고 있는 동안 디자이너로서 챗봇에 대한 리서치를 시작하려는 찰나, 아무리 검색을 해도 평소에 비해 아무것도 나오지 않는 매우 당황스러운 시추에이션이 발생했다.  일반적인 웹이나 어플리케이션 기획의 경우 이미 레퍼런스 삼을 만한 사례가 충분히 있었고, 설령 국내 자료 중에 없다고 한들 영어로 조금만 검색해보면 해외 자료들을 금세 찾을 수 있었다. 그러나 챗봇은 상황이 달랐다. 영어권 챗봇 또한 이제 막 성장하는 단계인 만큼 해외 챗봇 사례 중에서도 이렇다 할 벤치마킹 대상을 찾는 것이 쉽지 않았다.우선 우리가 만들고자 한 챗봇은 '일정' 관련 봇이었다. '자연스러운 대화를 이해하여 사용자의 일정 입력을 돕는 챗봇이 있다면 어떨까'라는 것이 우리의 가설이었다.괜찮지 않을까?지난 4년 간 학교에서 배운 과정대로라면 브레인스토밍, AEIOU, 컨셉맵핑, 유저 인터뷰, 포커스그룹 인터뷰 등에 걸친 여러 기법들을 통해 디자인을 시작해야 했다. 하지만 현 상황은 우리가 대체 정확히 무엇을 만드는 것인지에 대한 정의조차 내려지지 않은 상태였다.이 챗봇의 기능은 무엇이며, 타겟은 누구이고, 어떻게 구현될 수 있는 걸까. 너무나 생소한 분야였던 만큼 우선 첫 한 달 동안은 챗봇 관련 국내외 글을 꾸준히 읽기 시작했다. 4차 산업혁명, 완전자동화 등 챗봇에 대한 여러 이론적인(쓸데없는) 내용들이 있었지만 그중에서도 유독 눈에 띄는 글이 하나 있었다.https://chatbotsmagazine.com/bots-hype-or-glory-656f4d614efb#.g6s68jvkgI was an undercover-bot for 2 months. Here is what I learned.Bots: hype or glory?chatbotsmagazine.com 해당 글의 주요 내용을 번역 및 요약하자면 이러하다.- UX 매니아로서, 그 수많은 챗봇 중에 쓸만한 게 없더라.- 그래서 챗봇을 개발하기 전 직접 실험을 해보기로 했다.- 약 2달간 직접 서비스 내에 사용자를 돕는 봇인'척' 했다(틈틈이 사람이라고 힌트는 줬다).- 우리 서비스를 사용하는 사용자들은 컴퓨터나 기술을 좋아하는 사람들이 아닌, 일반인이었다.- 봇이 아닌 사람이 실시간으로 응대한다고 인지는 시켜주었지만 사실 신경 쓰는 사람은 없었다.본문은 '아직 챗봇은 기술적으로도, 시대적으로도 준비가 되지 않았다'로 최종 결론을 지으며 마무리되는데, 이미 챗봇에 콩깍지가 씌여 있던 나에게는 그저 앞부분의 내용이 중요할 뿐이었다."사람이 챗봇인 척 테스트를 한다고?"서비스 기획 및 디자인에 갈피를 못 잡고 있었던 우리 팀은 긴말할 것 없이 곧바로 실행에 들어갔다. 대학교 게시판에 피실험자 알바 구인 글을 올리고 약 30명의 캘린더 유저를 확보했다. 실험에 대한 대략적인 안내사항은 이러했다.1. 우리는 현재 일정 관련 챗봇을 만들기 위해 수동으로 실험 중이며, 주 기능은 '일정등록' 이다.2. 구글 또는 네이버 캘린더 작성 권한을 사용자로부터 공유받아 일정을 입력한다(캘린더 공유 기능 활용).3. 사용자는 최소 주 1회 이상 카톡을 통해 캘린더에 일정을 입력하여야 한다(페이 지급 조건).4. 사용자는 챗봇에게 일정 등록뿐만이 아닌 일정 관련 어떠한 요청도 할 수 있다.5. 이에 대한 예시로 문자/메일 분석, 공개 캘린더 추가, 키워드 일정 추천 등을 제시한다.6. 대화의 형태는 정해져 있지 않으며 원하는 어떠한 형태(말투, 축약어, 신조어)로든 가능하다.응대에 사용한 옐로아이디 관리자 툴지금은 플러스친구로 업데이트된 카카오톡 옐로아이디 관리자 툴을 활용하여 사용자들과 대화(채팅)를 진행했다. 데스크탑용 웹 인터페이스를 통해 대화를 입력할 수 있었기에 입력 속도는 빨랐지만 사용자가 언제 무슨 말을 걸어올지 도저히 예측이 불가능했다. 팀 내 개발자들이 자연어 처리에 대한 공부를 지속하는 동안 운영을 맡은 팀원과 함께 2명이서 상시 대기하며 사용자들의 요청에 응대했다.운영 초기 우리가 기대했던 이상적인 요청들은 이러했다.하지만 현실은 아래와 같았다.목적어 및 각각의 형태소가 매우 명료하고 명확한, 챗봇 개발 시 자동화가 가능한 텍스트들을 기대하고 있었지만 실상 대부분의 요청은 실제 사람이 개입하지 않는 이상 과연 처리가 가능할까 싶은 내용들이 태반이었다.텍스트 입력 시간도 사용자마다 다 제각각이었다. 아침 일과를 시작할 때 일정을 입력하는 사용자들이 있는 반면 하루를 정리하며 다음날 일정을 계획하는 사용자들도 있었다. 밥을 먹다가도, 샤워를 하다가도 옐로아이디 알람이 울리면 컴퓨터로 달려가 응답을 했다. 아무리 상시 대기를 한다 해도 잠은 자야 했기에 결국 자정부터 다음날 아침 8시까지는 옐로 아이디의 자동 응답기능을 활용하여 '잠시만 기다려주세요'를 출력하였다.(물론 잠시는 아니었지만)여러 시행착오를 거쳐 약 한 달 간의 기나긴 응대 끝에 실험이 종료되었고, 우리는 사용자들을 대상으로 설문 및 인터뷰를 진행하였다.우선 가장 중요하게 생각한 전체 캘린더 일정 입력률(데스크탑/모바일 캘린더를 포함한 모든 입력) 대비 카톡을 통한 일정 입력률은 약 절반 정도로 확인되었다.카톡을 통한 일정 입력률 / 전체 일정 입력률  = 51%이와 더불어 '카톡을 통해 캘린더에 일정을 등록하는 방식에 대해 불편한 점'을 질문한 결과1. 즉각적이지 않은, 늦은 응답 - 40%2. 개인 일정 정보 유출에 대한 불안 - 20%3. 익숙하지 않은 카톡 입력의 불편함 - 13.3%순으로 응답함을 확인하였다.생각보다 나쁘지 않은 결과였다.비록 입력 된 내용들을 정형화 하기가 쉽지는 않았지만, 기대했던 것에 비해 카톡을 통한 입력률이 높은 편이었고 가장 큰 문제점으로 지적된 '늦은 응답'과 '개인 정보 유출'은 챗봇 개발을 통해 개선할 수 있을 것으로 기대했다. 자동화를 통해 즉각적으로 응답할 수 있을뿐더러 사람의 개입을 없애 개인 일정 정보 유출을 방지할 수 있을 것이라는 판단 하에 챗봇 개발을 진행하였다.그렇게 한달 간 입력받은 텍스트 데이터를 활용, 약 2주 간의 개발 끝에 간단한 일정 등록 기능을 갖춘 일정 관리 챗봇, 린더봇이 탄생하게 되었다.https://www.youtube.com/watch?v=zSRYRYfzTFo2편에서 계속...#히든트랙 #챗봇 #기술기업 #개발자 #개발팀 #인사이트 #경험공유
조회수 2637

iOS 개발을 위한 11가지 노하우

Overview기고 제안을 받자마자 iOS 개발을 시작했을 때가 떠올랐습니다. 신대륙을 마주한 것 같았던 그때의 기분을 아직도 잊지 못하기 때문입니다. 당시까지만 해도 Android 개발만 했기 때문에 iOS는 그야말로 미지의 영역이었습니다. 게다가 개발을 시작하려고 조심스럽게 첫 발을 내딛은 순간, 입이 떡 벌어질 수밖에 없었죠.“이렇게 느린 IDE가 있다니…““개발자 프로그램이 뭐 이렇게 비싸?”XCodeXCode는 그동안 접했던 IDE 중에서도 가장 최악이었고, 개발자 프로그램 등록은 13만 원 상당의 비용을 지불해야 했습니다. 가장 중요한 건 맥 컴퓨터(Macintosh)를 보유해야만 했죠. 처음 개발을 시작하려니 넘어야 할 산이 매우 많았습니다. 맞습니다. 팜므파탈의 대명사 마타하리(Mata Hari)처럼 iOS 개발은 밀당과도 같습니다. 분명 매력적인 일이지만 XCode와 개발자 프로그램 등록은 빙산의 일각이기 때문입니다. iOS는 곳곳에 구덩이를 파고 초보 개발자들을 집어삼킬 준비를 하고있습니다. (예를 들면 리소스를 묶어놓은 R.java 파일 같은 레퍼런스가 없습니다. 흑.)그래서 준비했습니다. 수많은 초보 개발자들이 iOS의 구덩이를 피해갈 수 있는 팁을 말이죠.iOS의 구덩이를 피하는 11가지 방법1.비싼 맥(Macintosh)을 사세요.iOS 개발자에게 MacOS는 필수입니다. XCode가 MacOS만 지원하기 때문입니다. 오픈 소스로 공개된 Swift에는 제약이 없지만 XCode는 MacOS에서만 동작하는 제약이 있습니다. 따라서 맥은 iOS 개발자에게 가장 필요한 준비물입니다. 게다가 하드웨어 리소스를 많이 사용하는 XCode 탓에 더 크고, 더 비싸고, 더 아름다운 맥을 구매하셔야 합니다. Macbook이나 Macbook Air 모두 추천하지 않습니다. 15형 Macbook Pro 모델을 비롯해, Mac Pro나 iMac Pro 등의 고급 모델을 사용하면.. 개발이 잘 됩니다.2.돈을 내세요.iOS를 개발하려면 가장 먼저 Apple Developer Portal에서 연 129,000원의 개발자 프로그램에 등록해야 합니다. XCode를 사용해서 코드만 볼 것이라면 문제가 되지 않지만, 디바이스에 앱을 설치하고, 테스트하며, AppStore에 배포할 거라면 반드시 구매해야 합니다. 이 계정은 앞서 말한 것처럼 1년이 지나면 다시 구매해야 합니다. 만약 기업 개발자로 등록하려면 Enterprise Program이 따로 준비되어 있습니다. 기업을 위해 특화된 In-House 배포 등의 이점이 있습니다. 구매해야할 것이 꽤 많죠? 이제 익숙해져야 합니다.3.XCode를 설치하세요.XCode는 Mac App Store에서 설치할 수 있습니다. 용량이 크기 때문에 설치하기 전에 하드디스크 저장공간부터 확인하는 것이 좋습니다. 처음 실행하면 추가 컴포넌트를 다운로드하는 과정이 실행되고, 그 이후에 XCode를 사용할 수 있습니다. XCode와 관련된 자세한 내용은 아래에서 자세하게 다루겠습니다.4.어려운 것에 대비하세요.1)인증서‘들’XCode 설치 이후에도 몇 가지를 발급 받고, 셋업해야 합니다. 방 탈출 게임처럼 한 단계 한 단계 거치는 과정이 필요합니다. 첫 번째로 인증서‘들’을 발급받아야 합니다. 애플을 대신해 앱을 설치하고, 배포할 수 있는 권한을 위임 받는 과정입니다. 이 인증서들은 Apple Developer Portal의 ‘Certificates, IDS & Profiles’ 항목에서 발급 받을 수 있으며, MacOS의 키체인 앱을 이용해 개인 키를 생성하는 방식으로도 방식으로 발급 받을 수 있습니다. 2)디바이스 등록디바이스-iOS-에 개발한 앱을 설치하려면 애플 개발자 계정에 개발용 디바이스를 등록해야 합니다. 이 과정은 XCode에 신규 디바이스를 연결하고, 빌드 및 배포를 할 때 XCode가 알아서 합니다. 만약 디바이스를 보유하고 있지 않은 상황이라면 해당 디바이스의 UUID를 받아서 개발자 포털에 직접 등록할 수도 있습니다. 3)Bundle IDBundle ID는, 앱의 고유한 ID입니다. iOS가 앱을 식별할 때 사용하는 식별자이며, 보통 ‘com.companyname.appname’ 의 형식으로 회사나 개인의 도메인을 거꾸로 쓰는 것이 보편적입니다. 하지만 Bundle ID는 어디까지나 개발자가 결정하는 영역이므로 인스턴스 이름 지정하듯이 자신만의 고유한 방법을 사용해서 Bundle ID를 지정해도 문제가 없습니다. 4)Provisioning ProfileProvisioning Profile은 디바이스 정보와 앱 정보, 인증서 정보를 매핑해주는 Profile입니다. 최신 XCode에서는 이 Provisioning Profile을 자동으로 관리해주기 때문에 따로 신경쓰지 않아도 좋습니다.5.개발자 포럼에 질문하거나, StackOverflow에 질문하거나!질문하는 사람은 아름답습니다. 궁금하거나, 잘 안 풀리는 코드는 개발자 포럼에서 질문할 수 있습니다. 대신 영어 실력이 좋아야 합니다.크게 기대는 하지 않는 것이 좋습니다. 등록된 discussion에 대한 답글들이 ‘나도 같은 상황이다’, ‘나도 궁금한 점이다’ 등의 다른 개발자들의 답변 정도가 일반적이기 때문이죠.그들의 답변...저는 개발자 포럼보다 StackOverflow를 더 선호합니다. 참여하는 개발자 규모가 다르기 때문에 보다 양질의 정보를 빠르게 찾을 수 있습니다. (하지만 허위 정보도 존재합니다.) Vote 시스템으로 신뢰 높은 정보를 필터링할 수 있으나, 어떤 정보를 선택할지는 당신의 몫입니다.6. iTunesConnect와 친하게 지내세요.앱을 개발했다면, iTunesConnect를 통해 앱을 전 세계의 사용자들에게 배포할 수 있습니다. iTunesConnect는 iOS용으로 개발된 바이너리를 배포하는 등 앱 배포/테스트와 관련된 전반적인 사항들을 관리할 수 있는 포털입니다. AppStore에서 앱을 판매하려면 이 iTunesConnect를 통해 애플과 계약을 해야만 가능합니다. 출시할 앱을 등록하기도 하고, 앱의 사용자들이 어떤 경향을 보이는지 Trend Analysis를 확인할 수도 있습니다.iTunesConnectiTunesConnect에는 다양한 메뉴들이 있고, 앱을 배포하고 관리하는데 필요한 여러 툴이 있으므로 개발 중에 시선을 환기하고자 한다면 iTunesConnect를 한 바퀴 둘러보는 것도 좋습니다. 언젠가는 다 사용하게 될 테니까요.7.앱 개발을 마쳐도 XCode를 사용하세요.앱을 개발하고 iTunesConnect에 업로드하려면, XCode를 통해 간접적으로 바이너리를 업로드하게 됩니다. 서드파티 앱을 사용할 수도 있지만, 제가 주로 많이 사용하는 방식은 XCode입니다. 소스코드가 준비되었다면, XCode 메뉴의 Product > Archive 메뉴를 선택해 XCode가 배포용 앱을 빌드합니다. 빌드가 완료되면, 자동으로 Organizer 창이 열리면서 앱을 업로드할 수 있게 되죠. 이 때 미리 구매한 개발자 계정의 인증서가 준비되어 있어야 합니다. 모든 준비가 완료되고 아카이빙이 완료되면, Organizer의 Archives 탭에서 우측단의 ‘Upload to App Store…’ 버튼으로 바이너리 업로드를 진행할 수 있습니다.8.배포 전에 시험비행을 해봅시다.앱을 개발했다면, 테스트플라이트(TestFlight)를 통해 실제로 앱을 배포하기 전 ‘시험비행’을 할 수 있습니다. iTunesConnect에 관련 테스터들을 등록하고, 등록된 사용자들을 대상으로 미리 앱을 테스트할 수 있도록 요청하는 것이죠. 이 테스트플라이트에 배포된 바이너리를 그대로 AppStore에 배포하게 되므로, 테스트용으로 유용합니다.TestFlight테스트플라이트는 원래 iOS 배포 관리 솔루션을 제공하는 업체였지만 지금은 애플이 인수해 iTunesConnect에서 관리하도록 제공하고 있습니다.9.앱이 죽는다면 Organizer로 확인하세요.iOS는 충돌보고 Crash Report를 Organizer를 통해 오류를 확인합니다. 앱을 설치한 사용자가 동의하면 XCode는 iOS가 앱을 실행하면서 발생한 Crash Report를 애플에 자동으로 업로드합니다. 업로드된 Crash Report들은 XCode의 Organizer를 통해 다운로드하고, 확인할 수 있습니다. Organizer는 XCode > Window > Organizer 항목에서 실행하세요.Organizer와 Crash ReportCrash Report는 Organizer의 상단 Crashes 탭에서 확인이 가능합니다. 또 현재 보고 있는 Crash Report의 어느 부분에서 오류가 발생했는지 알고 싶다면 우측단의 ‘Open in Porject…’ 버튼을 눌러보면 됩니다.10.내 친구 XCode최근 XCode는 메이저 업데이트를 통해 사용성과 퍼포먼스를 향상시켰습니다. 하지만 이만큼 무겁고 느린 통합개발툴 IDE는 이클립스(Eclipse) 이후에 처음입니다. 안드로이드의 경우 IntelliJ 기반의 Android Studio로 쾌적한 개발환경을 제공하고 있는 반면, XCode의 업데이트는 퍼포먼스나 사용성 개선보다는 Swift의 메이저 버전 반영에 더 급급한 느낌입니다. (업데이트 때마다 속지만 ‘혹시 이번에는..’하고 또 속아 넘어갑니다.) XCode 사용을 위한 네 가지 팁을 소개합니다.1)XCode는 모노로그입니다.XCode는 로그를 따로 ‘예쁘게’ 볼 수 없습니다. 검은 화면에 흰 로그가 정리되지 않은 상태로 마구마구 출력됩니다. 개발자들에게는 쥐약같은 상황이죠. 이런 불편한 로그 출력 방식 때문에 저는 별도의 GlobalLogger 모듈을 작성해서 다음과 같은 스타일로 로그를 출력하도록 하고 있습니다.$$ BrandiLogger Error Log ##MESSAGE: Initial Parameter is not exist. ##LOCATION: BRLogPringer.swift @Line: 122 2)iOS개발자를 위한 휴식시간, 빌드 타임XCode의 빌드 타임은 개발자에겐 기나긴 휴식 시간입니다. 소스가 비대해질수록 퍼포먼스는 떨어지며, 담배 한 대를 태우고, 화장실에서 손을 씻고 들어와도 빌드가 절반도 안 된 상황을 마주할 겁니다. 빌드 타임을 줄이고자 구글링을 하면 몇 가지 팁을 발견할 수 있는데, 특히 빌드 타임을 가장 많이 단축할 수 있는 방법이 있습니다.짜잔! 공개합니다!먼저, 프로젝트 셋팅의 ‘Build Settings’ 항목에서 ‘Optimization Level’을 검색합니다. ‘Swift Compiler - Code Generation’ 항목을 찾을 수 있는데요. 여기서 Optimization Level의 Debug 항목을 ‘None’으로 설정하면, 빌드시간이 엄청나게 줄어든 것을 확인할 수 있습니다. Brandi iOS 버전의 소스코드는 원래 컴파일에 7분 이상이 소요되었지만, Optimization Level을 변경한 후 1분 내외로 단축되었습니다. Optimization Setting을 변경할 때는 꼭 Debug 항목만 변경하고, Release 버전은 기존 설정을 유지하는 것이 좋습니다. 그래야 빌드 과정에서의 버그를 막을 수 있기 때문이죠. 만약 이 설정으로 개발하던 도중 소스가 충돌되면 Command+Shift+K 단축키를 눌러 소스를 한 번 클린하고, 재빌드하세요. 충돌이 사라지는 경우가 많습니다. 빠른 빌드를 위해 종종 감수해야 하는 부분이기도 합니다. 3)Derived Data빌드가 자꾸 안되고 꼬일 때는 Derived Data 폴더를 삭제 해 보세요. Derived Data 폴더는 XCode > File > Project Settings(Workspace Settings) 항목에서 ‘Derived Data’ 항목 아래의 폴더 경로에서 접근할 수 있습니다. Derived Data 접근 경로Derived Data 폴더를 삭제하면 거짓말처럼 빌드 오류가 사라지는 기적을 만날 수 있습니다. 4)CocoaPods‘바퀴를 두 번 발명할 필요는 없다’는 격언이 있습니다. 이것을 개발에 적용하면 ‘잘 만들어진 라이브러리를 사용하라’ 정도가 되겠습니다. 개발자의 개발 시간을 현저하게 단축시키는 오픈소스 라이브러리. 이것들을 간편하게 사용하는 방식이 iOS에도 존재하는데, 바로 CocoaPods입니다. 프로젝트 Root 폴더에 Podfile을 생성하고, 원하는 오픈소스 라이브러리들을 명시한 후에 ‘pod install’ 명령어를 입력해주면….CocoaPods오픈소스 라이브러리가 설치되었습니다. 귀찮은 소스 다운로드와 임포트 과정을 거치지 않아도 됩니다. CocoaPods 설치와 사용에 관한 글은 구글링으로 쉽게 찾을 수 있습니다. 꼭 사용하길 권합니다.Mac App Store에서의 XCode 평점XCode는 느리고 불편합니다. 숨겨진 편의기능도 많지만 고질적인 빌드 문제와 사용성 문제를 마주하면 높은 평점을 줄 수가 없습니다. 그런데, 저만 그렇게 생각하진 않더라고요.(위 스크린샷 참조) XCode의 사용법은 기회가 되면 따로 정리하겠습니다.11.어떤 경우에도 대응할 수 있는 화면 구성을 원한다면, AutoLayoutiOS를 사용하면서, 금융권이나 쇼핑 앱들을 사용하다 보면 이런 상황이 발생합니다. 금융권 앱. 화면에 꽉 차지 않는 레이아웃 혹은 비정상적으로 커진 글씨본래 iOS는 단일 디바이스를 지향하는 플랫폼이었습니다. 아이폰 시리즈도 해상도가 변하지 않았기 때문에, 디바이스 종류가 많은 안드로이드처럼 다양한 스크린 사이즈를 지원할 필요가 없었습니다. 하지만 이제는 iPhone SE, iPhone 8, iPhone 8 Plus의 해상도에 iPhone X의 해상도까지 더해지면서 그야말로 ‘해상도 춘추전국시대’가 되었습니다.이런 다양한 해상도를 모두 지원하는 레이아웃을 구성하려면, iOS에서는 AutoLayout을 사용해야 합니다. AutoLayout은 Xib Editor에서 AutoLayout을 활성화하는 방식으로 사용할 수 있습니다. 거기에 한 가지 덧붙이면 Layout Constraints라는 개념도 있습니다. 레이아웃에 조건을 주는 방식입니다. 예를 들어 ‘어떤 해상도에서든 이 컴포넌트는 왼쪽 끝으로부터 10Point의 여백을 가지도록 한다’ 라는 식이죠. AutoLayout, Layout Constraint이 Layout Constraint를 이용하면 짧은 시간 안에 다양한 해상도를 지원하는 레이아웃을 쉽게 만들 수 있습니다. 가히 AutoLayout의 꽃입니다.ConclusionXCode/iOS 개발과 관련된 팁은 대부분 구글링으로 찾을 수 있습니다. 다룰 내용이 많지만 초보 iOS개발자들이 당황할 수 있는 내용을 중심으로 글을 썼습니다. 소소한 이야기지만, 분명 도움을 받을 수 있을 겁니다.글이정환 과장 | R&D 개발1팀leejh@brandi.co.kr브랜디, 오직 예쁜 옷만#브랜디 #iOS #개발기 #업무환경 #인사이트 #경험공유 
조회수 1615

성장하는 PHP와 환대받지 못하는 개발자

https://kinsta.com/blog/php-7-2/ PHP v7.2 릴리즈최근(2017년 11월 30일)에 PHP  7.2 버전이 릴리즈 되었습니다.(다운로드 바로가기) PHP는 1995년에 만들어진 오래된 언어지만 여전히 많은 웹사이트들이 PHP로 만들어지고 있습니다. 특히 버전7로 넘어오면서 퍼포먼스가 비약적으로 좋아졌다는 평을 듣고 있습니다. 이번 7.2 버전에서는 아래와 같이 보안성강화와 프로그래밍 기능 향상을 제공하고 있습니다. (개선목록 바로가기)PHP 7.2.0 comes with numerous improvements and new features such as  Convert numeric keys in object/array castsCounting of non-countable objectsObject typehintHashContext as ObjectArgon2 in password hashImprove TLS constants to sane valuesMcrypt extension removedNew sodium extensionPHP로 만들어진 많은 사이트2017년 GitHub 통계를 보면 PHP는 GitHub에서 사용되는 337개의 언어들중에서 Top 5에 들어가는 매우 대중적인 언어입니다.https://octoverse.github.com/ WordPress, Drupal, Zoomla 와 같은 웹 기반의 오픈소스 컨텐츠 관리 시스템은 모두 PHP로 만들어 졌습니다. 그리고테크크런치(TechCrunch), 펩시 리프레시(Pepsi Refresh), 코메디닷컴(Comedy.com) 같은 기업들은 WordPress로 만들어진 사이트를 적극 활용하고 있기도 합니다. 다만 아쉬운 점은 아직도 5버전을 사용하여 개발한 사이트들이 많이 있다는 점입니다.https://kinsta.com/blog/php-7-2/환대받지 못하는 PHP 개발자PHP는 탁월한 접근성으로 인해 생각지도 못한 문제가 발생합니다. PHP가 누구나 사용할 수 있을 정도로 쉬운 구조이다보니 우리나라의 갑-을-병-정 으로 내려가는 SI 구조에서 저렴한 인력으로 구분되기 시작합니다. PHP 고급 개발자가 고급 대우를 못받게 되는 상황이 발생하는 것입니다. 또한 엔터프라이즈 개발에서 제외되다 보니 PHP 개발자는 점점 대규모 시스템 설계 경험이 적어지고 결국 중소규모의 서비스 개발에만 참여하게 되었습니다. 하지만 PHP도 충분히 대규모 서비스 개발이 가능한 언어이며 PHP The Right Way 와 같이 PHP를 잘 사용할 수 있는 방법들을 정리한 사이트를 보면 PHP의 저력을 확인할 수 있습니다.PHP 개발자를 위한 서비스 관리 도구PHP 개발에 있어서 아쉬운 부분이 있다면 개발 이후 운영에 관련된 부분입니다. 많은 국내 PHP 사이트들이 개발 이후 성능 분석이 되지 않은 상태에서 운영되고 있습니다. Java로 만들어진 엔터프라이즈 서비스들은 오픈 시점과 운영 과정에서많은 노력을 들여서 서비스 최적화 작업을 진행하는데 반해서, PHP로 개발된 서비스들은 사용자가 많아지더라도 튜닝 작업을 진행하는 경우가 거의 없습니다. 아쉬운 점은 이로 인해 PHP의 성능이 떨어진다는 오해가 발생하기도 한다는 것입니다.일반적으로 평균 응답시간을 계산하여 서비스의 상태를 파악하기도 하지만 하루 1만명이 들어오는 사이트에 100명이 10초 이상의 응답시간을 경험하더라도 나머지 인원이 0.1초의 응답시간을 갖는다면 서비스의 평균 응답시간은 0.2초 이내로 나오게 됩니다. 이런 고객의 장애를 해결하기 위해서는 사용하는 성능 분석 서비스가 이전까지는 솔루션으로만 제공되었기 때문에 고가이며 설치도 어려웠지만 최근에 서비스로 제공되기 시작하면서 비용도 저렴해지고 설치도 매우 쉬워졌습니다. 해외에서는 몇 년전부터 많은 PHP 개발자들이 모니터링 서비스인 뉴렐릭(https://newrellic.com)이나 앱다이나믹스(https://appdynamics.com)의 서비스를 통해 PHP 분석/모니터링 서비스를 사용하고 있습니다. 이런 서비스들은 당연히 한국에서도 사용이 가능합니다.https://newrelic.com/php국내 모니터링 서비스 중에서는 와탭(https://whatap.io)이 최근 PHP를 지원하고 있습니다. 어플리케이션의 성능을 분석하고 튜닝한 사이트와 안한 사이트의 성능 차이가 날수 있기 때문에 PHP로 만들어진 서비스의 운영 및 업데이트 작업을 진행하는 개발자 분들은 뉴렐릭이나 앱다이나믹스 또는 와탭을 사용하여 운영중인 서비스의 성능을 확인해 보시길 권하고 싶습니다. 대부분의 PHP 성능 모니터링 서비스는 트라이얼 기간을 제공해 주기 때문에 일정기간 무료로 서비스 사용이 가능합니다. 몇일간 성능을 분석하고 모니터링 한다면 서비스 운영 방식에 대한 인사이트도 얻을 수 있습니다. https://coderseye.com/best-php-frameworks-for-web-developers/PHP 성능 모니터링 서비스로 할수 있는 것들PHP 성능 모니터링 서비스는 정확히 표현하면 고객의 트랜잭션을 추적하는 서비스입니다. 서비스를 사용하는 모든 고객의 트랜잭션을 추적하여 서비스의 성능을 알아내는 방식입니다. 이런 어플리케이션 성능 모니터링 서비스는 대규모 서비스를 체계적으로 운영하는 위한 필수 도구입니다. 최근 서비스 형태로 제공되는 성능 모니터링 서비스들은 기존 운영자 위주의 기능에서 벗어나서 개발자와 운영자가 함께 참여하는 DevOps 환경에 맞는 기능을 제공하고 있습니다. 서비스를 운영하는 과정에서 응답시간의 상황을 실시간으로 확인할 수 있으며 문제가 발생한 쿼리를 빠르게 찾을 수 있도록 도와줍니다. 트랜젝션의 에러도 당연히 알수 있으며 문제가 발생한 메소드도 알수 있습니다. 코드상의 서비스 구조뿐만 아니라 실제 트랜잭션의 흐름을 알수 있기 때문에 서비스의 동작 구조도 함께 공유해가며 서비스를 발전시킬 수 있도록 도와줍니다. 결론PHP는 정말 빠르게 발전하고 있는 언어중에 하나입니다. 우리가 정보를 주고 받는 많은 서비스들이 PHP로 만들어 지고 있으며 언어의 구조도 모던하게 변화하고 있습니다. 특히 빠르게 변화하는 스타트업에서 사랑받는 언어이며 세계적으로도 많은 이들의 사랑을 받고 있는 언어입니다. 한편 PHP는 소규모에서만 적용한다는 인식과 함께 PHP로 시작했음에도 규모가 커지면서 서비스를 Java로 변경하는 경우에는 아쉬움이 남습니다. 하지만 PHP가 지속적으로 발전하고 있고 더 좋은 방향으로 나아가는 과정에서 더 좋은 PHP 개발자들이 나오기 시작할 거라 생각합니다. 그리고 뉴렐릭(https://newrelic.com)이나 앱다이나믹스(https://appdynamics.com) 아니면 와탭(https://whatap.io)과 같은 성능 분석 도구를 사용하여 PHP로 만든 서비스의 효율을 높이고 운영 관리를 체계화해 나간다면 국내에서도 페이스북과 같이 PHP로 개발하여 대규모로 서비스볼수 있을거라 생각합니다. http://php.net/archive/2017.php#와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지 #PHP
조회수 1409

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 #리액트 #리액트가이드 #한글 #번역 #문서번역
조회수 3136

리디북스 서비스 장애 복구 후기

지난 8월 26일에는 약 21분간 리디북스 서비스 전체가 중단되는 장애가 있었습니다.사실 서버 스택 일부에만 영향을 주는 장애는 눈에 잘 띄지 않지만 꽤 흔하게 발생하는 일입니다. 기기 1대당 외부적인 요인으로 인한 장애가 평균 2년에 1번 발생한다고 가정하면, 서버가 100대 있을 때는 대략 1주일에 1번꼴로 장애가 발생하는 셈입니다.이런 형태의 장애는 서버 스택의 한 곳에서만 발생하므로, 이중화 혹은 클러스터링을 통해서 극복하곤 합니다. 또한 원인이 명확하므로 해당 기술에 대한 이해도가 높다면 비교적 빠른 시간 내에 복구가 가능합니다.그러나 이번에 리디북스가 경험한 장애는 달랐습니다. 현재 리디북스는 2개의 데이터센터와 클라우드에 인프라가 분산되어 있는데, 이 중에서 1차 데이터센터의 전원 공급에 문제가 생겨 특정 서버 랙에 있는 서버 17대가 동시에 내려간 것입니다. 즉, 소프트웨어나 머신의 물리적인 장애가 아닌, 데이터센터의 장애였습니다. AWS로 비유를 하자면 가용 영역(Availability Zone)의 장애라고 할 수 있겠습니다.원인에 대해이번 장애의 근본적인 원인은 데이터센터가 전원을 정상적으로 공급해주지 못한 것입니다. 물론 데이터센터 혹은 클라우드 서비스(IaaS)는 고객사에게 전원과 네트워크를 안정적으로 제공해주어야 하는 의무가 있습니다.하지만 이들 역시 천재지변이나 사람의 실수에 대한 대비가 100% 완벽할 수는 없습니다. 따라서 이러한 점을 사전에 고려하고 인프라를 설계하지 못한 것이 2차적인 원인입니다.이번 계기를 통해 데이터센터 이중화를 계획하게 되었고, 사용 중인 클라우드 역시 지역(Region) 전체에 장애가 생길 경우에 대한 대비가 되어있지 않아, 이번 계기로 복제 계획(Geo-Replication)을 세우게 되었습니다.구체적인 상황당시 전원이 차단되어 강제 종료된 서버들은 아래와 같습니다.데이터베이스 프록시 x 2메인 리버스 프록시 x 1읽기 분산용 MySQL 슬레이브 x 1서점용 웹 서버 x 3추천 알고리즘 API 서버 x 1알림센터 API 서버 x 2메인 스토리지 서버 x 2출판 플랫폼용 데이터베이스 x 2테스트 및 배치 작업용 서버 x 3그림으로 표현해 보자면, 대략 아래와 같은 상황에서… 아래와 같은 상황이 된 셈입니다.서버 스택의 여러곳에 순간적으로 장애가 발생한 상황공인 IP가 할당된 메인 프록시 서버 중 1대가 내려갔지만, 실제로는 아래와 같이 가상 IP로 구성을 한 상태였기 때문에 대기 중인(stand-by) 프록시가 동작하여 곧 서점에 장애 공지를 띄울 수 있었습니다.[이미지 출처: DigitalOcean™]공지 이후의 움직임우리는 데이터센터의 복구 시점을 명확히 알 수 없어서 신규 구축(provisioning)을 시작함과 동시에, 서버들의 물리적인 위치 이동을 고려하고 있었습니다. 그러나 다행히 10분이 지난 시점에서 전원 문제는 해결되었고, 서버들은 순차적으로 부팅이 완료되었습니다.일부 서버들은 부팅 과정에서 예상치 못한 지연이 발생하기도 하였지만, 모든 서버의 부팅이 완료된 이후에도 서비스는 완전히 정상으로 돌아오지 않았습니다. 당시 우리가 겪었던 문제와 해결책은 아래와 같습니다.A. 읽기 분산용 MariaDB 슬레이브의 복제 지연(replication lag) 문제슬레이브 서버의 부팅이 완료되자 데이터베이스 프록시(HAProxy)는 해당 서버를 정상으로 간주하여 라우팅 대상에 포함하게 되었고, 애플리케이션 서버들은 정상적으로 커넥션을 맺기 시작하였습니다. 하지만 해당 슬레이브는 수십 분간 마스터를 따라잡지 못한 상태였기 때문에 최신 데이터가 보여지지 않는 문제(stale data)가 있었습니다. 우리는 즉시 해당 슬레이브를 제거하였고 지연이 사라진 이후에 다시 서비스에 투입하였습니다.B. 읽기 분산용 슬레이브의 웜업(warm-up) 문제복제 지연은 사라졌지만 서버의 CPU 사용량이 크게 높은 상태가 한동안 유지되었고, 응답속도는 정상적인 슬레이브에 비해서 많이 느렸습니다. 왜냐하면 캐시가 비워진 상태에서 바로 서비스에 투입되어, 캐시 미스가 휘몰아치는 현상(cache stampede)이 발생하였기 때문입니다. 따라서 간단한 쿼리도 평소보다 오래 걸렸고, 그대로 둔다면 커넥션풀이 꽉 차는 현상이 발생할 것으로 예상되었습니다.곧 우리는 HAProxy로 해당 서버의 가중치를 10%로 낮추어 인입되는 쿼리의 양을 조절하였으며 응답속도는 정상 수치로 돌아오게 되었습니다. 이후 스크립트를 작성하여 수동으로 캐시를 채워나감과 동시에 점차 가중치를 높여 처리량을 정상화하였습니다.프로덕션에서 사용하는 서버는 innodb_buffer_pool 이 100G 이상으로 매우 크게 설정되어 있으며, 재시작 시 캐시가 날아가는 현상을 해결하기 위해 innodb_blocking_buffer_pool_restore 옵션을 적용하고 있습니다. 하지만 지금처럼 메모리를 덤프하지 못하고 비정상 종료가 된 상황에서는 해당되지 않았습니다.C. 인메모리 데이터의 보존 문제알림센터는 다양한 프로모션과 개인화된 정보를 전달해주는 공간입니다. 알림센터의 특징은 데이터의 영구 보존(persistency)이 필요하지 않고, 매일 수백만 건의 개인화된 메시지가 기록된다는 것입니다. 이러한 특징은 인-메모리 데이터베이스에 적합하므로 우리는 Redis를 마스터/슬레이브로 구성하여 저장소로 사용하고 있었습니다.어떠한 이유로든 Redis를 재시작해야 할 경우가 생기면, 메모리 상의 데이터가 날아가는 것을 방지하기 위해 주기적으로 스냅샷을 남기고 있습니다만, 이번에는 로그가 마지막까지 기록되지 못한 상태에서 메모리의 데이터가 날아가 버렸습니다.다행히 알림 발송과 관련된 메타정보는 모두 MariaDB에 기록하고 있으므로, 우리는 이를 기반으로 소실된 시점부터의 알림을 순차적으로 재발송할 수 있었습니다. 물론 모든 알림이 신규 상태로 간주되어 아이콘이 잘못 노출되는 문제가 있었지만, 고객님들은 너그럽게 이해해 주신 것 같습니다. 😅그래서 앞으로는?리디북스 DevOps 멤버들은 이번 데이터센터 장애를 통해 현재 인프라의 한계점을 실감하였고, 앞으로의 개선 방향에 대해 고민하게 되었습니다.몇 가지를 정리하면 다음과 같습니다.랙 단위로 장애가 발생할 수 있음을 인지하고 대비하자.같은 기능을 하는 서버를 하나의 랙이나 같은 가용 영역에 두지 말자.2차 데이터센터는 더 이상 옵션이 아닌 필수다.낙뢰나 지진으로 인해 데이터센터에 문제가 생길 수도 있다.긴급하게 프로비저닝이 필요한 상황에 대비하자.문서화가 되어 있더라도 경험이 없다면 동일한 구성에 많은 시간이 소요된다.모든 구성요소들에 대한 Ansible 스크립트를 작성하여두자.캐시 웜업 스크립트도 작성하여 두자.백엔드 구성요소들 간의 불필요한 의존 관계를 끊자.단 한 줄의 코드라도 참조하고 있다면 이는 독립적인 것이 아니다.언제나 서비스 지향적인 설계를 추구하자.Uptime을 관리하자.최대 180일을 기점으로 무조건 리부팅을 하자.재시작 과정에서 다양한 문제와 개선점이 발견될 것이다.커널 패치, 보안 패치를 할 수 있는 것은 덤이다.아래와 같은 긍정적인 면도 발견하였습니다.장애 상황이 실시간으로 Slack 채널을 통해 전파되었음진행 상황에 대해 모두가 동일한 수준으로 이해할 수 있었다.모니터링 연동(integration) 기능 때문에라도, Slack은 유료로 구매할만한 값어치가 충분하다.같은 기능을 하는 서버들이 다른 랙에 많이 분산되어 있었다.인프라가 확장될 때마다 빈 공간에 필요한 서버를 추가했을 뿐이지만, 자연스럽게 물리적인 위치가 분산되는 효과가 있었다.이 외에도 특정 클러스터를 구성하는 노드들을 분산하여 배치시키자.서버별로 오너쉽이 부여되어 있어서 빠르게 복구가 된 점여러 명의 백엔드 개발자들이 병렬적으로 복구를 진행할 수 있었다.마지막으로넷플릭스의 엔지니어들은 무질서한 원숭이(Chaos Monkey)라는 프로그램을 만들어서 운영한다고 합니다. 이 원숭이는 서비스 인스턴스들을 무작위로 중단시키는 역할을 합니다. 다소 황당하게 들리지만, 넷플릭스에는 일부 서비스에 장애가 발생하더라도 나머지 부분은 문제없이 운영되어야 한다는 원칙이 있으므로, 이를 수시로 시뮬레이션하는 과정을 통해 복구 능력을 높여둔다는 것입니다.실제로 이렇게 급진적인 아이디어를 실천할 수 있는 회사는 매우 드물 것입니다. 하지만, 우리는 이번 계기를 통해 무질서한 원숭이의 필요성을 절감하였고, 이로 인해 서버를 주기적으로 리셋하는 정책을 만들게 되었으며 모든 단일 장애점(SPoF)에 대한 대비를 시작하게 되었습니다.장애를 단순히 피해라고만 생각한다면, 서로를 비난하고 책임을 전가하는 상황이 펼쳐질 것입니다. 하지만 고객의 불편함과 맞바꾼 매우 비싼 경험이라고 생각한다면, 보다 튼튼하고 회복탄력적인 시스템을 갖추기 위해 노력하게 될 것입니다. 그러다 보면 언젠가는 데이터센터 전체에 문제가 생겨도 버틸 수 있는 모습으로 진화할 것이라고 생각합니다.#리디북스 #장애복구 #역경돌파 #개발 #개발후기 #개발자 #서버개발 #서버
조회수 1188

AWS Rekognition + PHP를 이용한 이미지 분석 예제 (1/2)

OverviewAWS Rekognition은 딥 러닝 기반의 이미지, 동영상 분석 서비스입니다. Rekognition API를 사용하면 서비스에서 객체, 사람, 텍스트, 장면 및 동작을 식별하고 부적절한 콘텐츠를 탐지할 수 있습니다. Rekognition은 딥 러닝 기술을 기반으로 하고 있기 때문에 지금 이 순간에도 새로운 데이터를 통해 끊임없이 학습하고 있고, AWS에서도 새로운 레이블과 얼굴 인식 기능을 추가하고 관리합니다. 이번에는 AWS S3 Bucket에 업로드한 이미지로 이미지 분석 결과를 볼 수 있는 예제 사이트를 통해, Rekognition과 친해지는 시간을 갖도록 하겠습니다. 저는 예제 사이트를 개발하기 위해 PHP 프레임워크인 CodeIgniter 3, MAMP, Bootstrap을 사용했습니다.1. AWS Rekognition SDK 설치하기1-1) AWS Rekognition 사이트에 접속해 Download SDKs 를 클릭합니다.1-2) AWS 에서 제공하는 다양한 언어의 SDK를 확인할 수 있습니다. 저는 PHP를 사용할 것이므로 PHP 의 Install을 클릭하겠습니다.1-3) AWS SDK 를 설치할 수 있는 방법은 여러가지가 있습니다. 이 중에서 저는 Composer를 이용해 설치했습니다.curl -sS https://getcomposer.org/installer | php php -d memory_limit=-1 composer.phar require aws/aws-sdk-php 1-4) 짠! 짧은 명령어 2줄로 SDK 설치가 완료되었습니다 :)2. AWS S3 Bucket 에 업로드된 이미지를 분석하기2-1) 여기에 임의로 만든 예제 사이트가 있습니다. [이미지 선택] 과 [S3에 이미지 업로드하기] 를 통해 이미지 파일을 등록하면, 백단(Back-end) 에서는 해당 파일을 특정 S3 Bucket 에 업로드 한 후 Rekognition 에게 이미지 분석을 요청하도록 짜여있습니다. 관련 코드는 아래와 같습니다.{     "Image": {         "S3Object": {             "Bucket": "bucket",             "Name": "input.jpg"         }     },     "MaxLabels": 10,     "MinConfidence": 80 } 위의 코드 블록은 AWS Rekognition 개발자 안내서에 나와있는 예제 포맷이고, 아래의 코드는 예제 포맷을 PHP 에서 요청할 수 있는 방식으로 코딩한 것입니다.detectLabels 메소드 를 이용해 분석할 이미지가 저장되어 있는 S3 Bucket 과 이미지의 Name 을 전달해줍니다. 1) MaxLabels : 응답 받을 최대 Label 수 2) MinConfidence : Label 에 대한 최소 신뢰성 여기서 Label 이란 ‘이미지에서 발견되는 객체, 장면 또는 개념’ 이라고 생각하면 됩니다. 예를 들어 해변에 있는 사람들을 촬영한 사진에는 ‘사람’, ‘물’, ‘모래’ (객체) 및 ‘해변’ (장면) 그리고 ‘야외’ (개념) 등이 Label 이 될 수 있습니다. 자, 우주 사진을 한 번 분석해볼까요? array(3) {     ["Labels"]=>     array(8) {       [0]=>       array(2) {         ["Name"]=>         string(9) "Astronomy"         ["Confidence"]=>         float(96.8987350464)       }       [1]=>       array(2) {         ["Name"]=>         string(5) "Earth"         ["Confidence"]=>         float(96.8987350464)       }       [2]=>       array(2) {         ["Name"]=>         string(5) "Globe"         ["Confidence"]=>         float(96.8987350464)       }       [3]=>       array(2) {         ["Name"]=>         string(11) "Outer Space"         ["Confidence"]=>         float(96.8987350464)       } ...     } Rekognition이 업로드한 우주 사진을 분석하여 정확히 연관된 Label들만 반환한 것을 확인할 수 있습니다. 이 Label을 가지고 이미지 태그를 간단하게 구현했습니다.참 쉽죠 ?Conclusion이번 시간에는 AWS Rekognition 을 이용하여 기본적인 이미지 분석을 해보는 시간을 가져봤습니다. 다음 시간에는 ‘얼굴 감지 및 분석’ 기능을 응용하여 Collection 을 생성해보고, 얼굴 검색을 해보는 시간을 갖겠습니다. 참고놀라운 무료 이미지 · Pixabay핀터레스트 스타일 레이아웃 만들기 (masonry) - 생활코딩이미지에서 레이블 감지 - Amazon Rekognition글김우경 대리 | R&D 개발1팀kimwk@brandi.co.kr#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유

기업문화 엿볼 때, 더팀스

로그인

/