스토리 홈

인터뷰

피드

뉴스

조회수 1600

IT 서비스 모니터링 제대로 잘하기

모니터링은 IT 운영의 핵심입니다. 장비의 활성화 상태에서 애플리케이션의 변화와 성능 이슈까지 언제나 실시간으로 인지와 대응이 가능해야 합니다. 서비스를 운영에 장애를 없앨 수는 없지만 좋은 모니터링 전략을 가지고 있다면 빠른 예방과 대응을 통해 고객이 불편함을 느끼지 못하게 할 수는 있습니다.  IT 운영에서의 비지니스 목표IT 서비스 모니터링 전략을 만들기 전에 우리는 우선 목표를 선정해야 합니다. 빠른 예방과 대응은 좋은 모니터링 전략의 기본 목표일 뿐입니다. 우리는 모니터링을 통해 아래와 같은 비지니스 목표를 이루어야 합니다. 브랜드 이미지 향상매출증대비지니스 개선비지니스 목표를 위한 모니터링그리고 이런 비지니스 목표를 위해서는 아래와 같은 일들을 모니터링을 통해 수행할 수 있어야 합니다. 안정적인 서비스 운영 (브랜드 이미지 향상, 매출증대)빠른 장애 대응 (브랜드 이미지 향상, 매출증대)장애 예방 (브랜드 이미지 향상, 매출증대)사용자 분석 (비지니스 개선)사용성 분석 (비니지스 개선)서비스 성능 개선 (브랜드 이미지 향상, 매출증대)현대 IT 서비스는 물리서버와 클라우드가 혼재되어 있는 인프라스트럭처 환경과 다양한 플랫폼에서 개발된 애플리케이션들이 작게 구성되어 있는 복잡한 구성을 가지고 있습니다. 뿐만아니라 서비스의 구성 또한 전 세계에 분산되어 있는 상황에서 우리는 효율적인 모니터링 전략을 만들어서 IT 서비스를 운영해야 합니다.비지니스 목표를 위한 모니터링 전략이런 체계적이고 효율적인 IT 서비스 모니터링 전략을 만들기 위해서는 아래와 같은 것들을 고려해야 합니다.1. 통합 모니터링 체계를 구축하세요.  인프라스트럭처와 애플리케이션을 모두 모니터링하여 전체 그림을 얻어야 합니다. 전체적인 그림을 모든 운영자들이 알수 있어야 체계적인 IT 서비스 운영이 가능합니다.2. 기준을 넘어서는 성능 변화가 생기면 알수 있도록 경고를 설정해야 합니다. CPU 부하율, 메모리 사용률, 누적 트랜잭션 등 다양한 상황에 대한 기준 값을 선정하고 이에 대한 알림을 받을 수 있어야 합니다. 초기 이슈 확인은 고객이 영향을 받기 저너에 문제를 해결할 수 있게 해 줍니다. 3. 사용자 관점에서 모니터링 해야 합니다. 예를 들어 TPS의 평균값만으로 서비스의 안정성을 판단해서는 안됩니다. 사용자 개개별 현황을 파악 할 수 있어야 합니다. 기업의 브랜드는 서비스 사용에 불편을 겪는 1%의 고객을 통해 내려갈 수 있습니다.4. 메트릭을 비지니스 목표와 맞출 수 있어야 합니다. 현재 서비스에 접속한 사용자 현황을 알 수 있어야 합니다. 예를 들면 동시 접속자 수를 기반으로 현재 서비스의 성능을 설명할 수 있어야 합니다. 5. 애플리케이션에서 특히 데이터베이스의 성능을 평가할 수 있어야 합니다. 많은 이슈들이 데이터베이스에서 발생합니다. 6. 애플리케이션의 코드 성능을 분석할 수 있어야 합니다. 많은 프로젝트에서 오픈소스 또는 서드파티 솔루션들이 사용되고 있습니다. 여기서 발생하는 문제들은 심각한 장애 상황을 유발할 수 있습니다.7. 모든 서비스를 분석 할 수 있어야 합니다. 몇몇 페이지가 아니라 전체 페이지를 분석 할 수 있어야 합니다. 우리는 항상 효율적인 IT 모니터링 전략을 재평가하고 새로 구축해야 합니다. 모니터링 전략을 만드는 것은 쉬운 일이 아닙니다. 하지만 모니터링 전략을 만드는 데 시간을 투자하는 것은 안정적으로 서비스를 운영하는데 있어서 매우 가치있는 일입니다. #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 2525

KT 채용 필수 정보! 채용담당자가 알려주는 KT 신입사원 공채 Q&A

 대기업 하반기 신입사원 공채가 속속들이 올라오고 있는 요즘, 취업준비생 여러분은 자소서 쓰랴 면접 준비하랴 무척 바쁜 나날을 보내고 계실 것 같은데요. 대한민국 대표 통신기업 KT 역시 2017 신입사원 공개채용 마감을 일주일 앞두고 있습니다. 올 하반기 KT는 신입/석박사 260여명을 채용할 예정이라고 하는데요. ‘KT는 어떤 인재를 찾고 있을까?’, ‘KT는 정말로 자소서를 다 읽어볼까?’, ‘학점이나 전공이 합격에 미치는 영향은 얼마나 될까?’ 등등… 저 역시도 궁금한 것들이 참 많답니다. 저처럼 KT 채용에 대해 궁금증이 많은 분들을 위해 KT ‘모바일퓨처리스트(MF)’ 대학생들이 인재채용팀 문을 두드렸는데요. 173명의 대학생들이 질문하고, 채용담당자가 직접 답변해 준 KT 신입사원 공채 Q&A! 지금 함께 보시죠~  KT 공채, 이것이 궁금합니다!  Q. 신입사원 채용 시 지원 조건이나 연령 제한이 있나요?A. 2017년 KT 하반기 공개채용의 경우, ‘4년제 대학 졸업 및 2018년 2월 졸업 예정자’의 조건만 충족하면 지원이 가능하며 학점 커트라인 및 연령 제한은 없습니다. Q. 전공과 무관한 직무로 지원해도 괜찮은가요?A. 네, 괜찮습니다. 국악과를 전공하고 유통채널관리 직무에 입사한 사례가 있고, 전자전기공학과를 전공하고 Biz 영업 직무를 맡고 있는 분도 있습니다. 직무와 직접적인 경험이 아니더라도 지원 직무에 대한 뛰어난 역량이 있다면 전공은 무관합니다. Q. 학점이나 어학점수, 대외활동은 어느 정도의 비중으로 보나요?A. KT는 자기소개서를 꼼꼼하게 읽는 기업으로 잘 알려져 있습니다. 그만큼 채용 전형을 진행할 때 학점이나 어학 능력 등 소위 말하는 ‘스펙’보다 자기소개서에 중점을 두고 서류 검토를 하고 있습니다. Q. 뻔하고 진부한 스펙이나 경험을 꼽는다면?A. KT는 대학생활 동안 지원자들이 경험하고 학습한 활동 하나하나가 뻔하고 진부하다고 생각하지 않습니다. 오히려 자신이 겪은 경험들을 KT에 입사해 어떻게 활용할 수 있는지 잘 풀어낸다면 좋은 평가를 받을 것이라고 생각합니다.  KT는 어떤 인재를 선호하나요?  Q. KT가 찾고 있는 인재상이 궁금합니다.A. KT는 끊임 없이 도전하는 인재, 고객을 존중하고 벽 없이 소통하는 인재, 기본과 원칙을 지키는 인재를 찾고 있는데요. 더불어 열정과 끈기까지 갖춘다면 더할 나위 없이 완벽한 KT의 인재상이 아닐까 생각합니다. (웃음) Q. 가장 기억에 남는 지원자가 궁금해요.A. KT ‘스타오디션’ 전형은 입사지원서만으로 표현하기 어려운 직무에 대한 열정, 본인의 역량과 경험 등을 5분의 시간 동안 형식에 구애 받지 않고 발표하는 전형인데요. 올해 상반기 ‘스타오디션’ 전형을 통해 입사한 한 지원자는 면접장에서 “생과일 주스는 왜 다 비쌀까요?” 라는 질문을 던지며 오디션을 시작해 이목을 집중시켰습니다. 전국 4개 광역시를 돌며 푸드트럭을 운영했던 자신만의 일화를 소개한 지원자는 ‘생과일 주스가 비쌀 수 밖에 없는 이유’를 설명하고, 어떻게 해야 합리적인 가격으로 소비자들을 사로잡을 수 있을지 본인만의 해답을 제시했는데요. 그 일련의 과정들이 무척 인상 깊었습니다. Q. KT 입사를 희망하는 지원자들에게 추천하는 활동이 있다면?A. 지피지기면 백전백승! ‘묻지마 지원자’가 되지 않기 위해서는 우선 KT의 가치관과 인재상, 그리고 KT가 하고 있는 사업에 대한 공부와 이해가 필요하다고 생각합니다. 그러다 보면 자연스럽게 본인에게 맞는 직무가 무엇인지 알게 되고 면접 때도 형식적인 대답이 아닌 본인만의 이야기를 풀어낼 수 있겠죠?  KT 면접, 팁을 알려주세요!  Q. 면접에서 가장 중요하게 생각하는 부분은 무엇인가요?A. 우선 지원자가 지원 직무에 대해 얼만큼 이해하고 있는지를 평가합니다. KT는 스펙으로 지원자를 평가하기 보다는 직무 전문성 검증 면접을 통해 지원자가 해당 직무에 얼마나 적합한지를 평가합니다. Q. 면접에서 ‘이것만은 하지 않았으면’ 하는 것이 있다면?A. 단순히 경험을 나열하거나, 암기한 듯한 형식적인 답변은 지양하는 것이 좋습니다. 면접장에서 긴장을 하게 되면 외워둔 것도 모두 잊어버리는 경우가 많은데요. 본인의 경험이 어떻게 지원 직무와 연관이 있는가를 연결 지어 말하는 것이 중요합니다. 또한 같이 면접을 보는 지원자들이 경쟁자이긴 하지만 너무 배려 없는 행동은 오히려 면접관들의 반감을 사게 된다는 점을 기억하셔야 합니다. Q. KT의 블라인드 채용은 어떻게 진행되나요?A. KT는 지속적으로 블라인드 채용을 실시하고 있습니다. 현재 진행되고 있는 2017 하반기 공채의 경우 입사지원서에 사진을 첨부하지 않아도 됩니다. 또한 면접과정에서도 면접위원들에게 지원자의 학교나 전공 등의 정보를 제공하지 않고 자기소개서만 제공하기 때문에 오롯이 지원자의 열정과 직무 역량을 평가하고 있습니다. Q. 마지막으로 2017 KT 하반기 공개채용에 대해 설명해주세요!A. 오는 18일 마감되는 2017 KT 하반기 공개채용의 경우 경영/전략, 영업마케팅, 네트워크 보안 등 총 17개 직무에서 작년보다 46% 증가한 260명을 채용하는데요. 실무형 인재 채용, 지역 쿼터제, 블라인드 채용 등을 실시하여 더욱 다양한 분야의 지원자들을 모집하고 있으니 많은 지원 부탁 드립니다!  <iframe width="560" height="315" src="https://www.youtube.com/embed/ZxCkMKBVvXk" frameborder="0" allowfullscreen="">2017 KT 하반기 신입사원 채용 지원하러 가기 채용담당자에게 직접 들어 본 KT 신입사원 공채 Q&A! 궁금했던 부분이 조금이라도 풀리셨나요? 국민기업 KT는 글로벌 1등 통신기업을 함께 만들어 나갈 열정 넘치는 인재를 찾고 있습니다. 블라인드 채용으로 스펙을 초월해 본인만의 이야기가 더욱 중요해진 이번 공채에서 지원자 여러분 모두의 건승을 기원합니다. 파이팅![늘 곁에 kt, KT그룹 블로그] #KT #2017KT하반기공채 #KT공채 #KT서류 #KT신입사원공채 #KT인재상 #KT인적성 #KT입사 #KT자소서 #KT채용Q&A #KT취업 #KT하반기공채 #KT합격 #KT그룹 #KT채용 #블라인드채용 #스타오디션 #스펙 #인사담당자 #인재채용팀 #채용Q&A #채용인터뷰 #취업준비 #하반기공채 #학점커트라인 #합격팁
조회수 1115

2017년 상반기 결산!

잔디 유저에게 사랑받은 기능 7선잔디 메신저▲ 늘 많은 관심 주셔서 감사합니다!안녕하세요 잔디 CX팀의 Jessica입니다. 블로그를 통해 오랜만에 인사를 드리네요. 그동안 잘 지내셨나요? 유저분들의 관심에 보답하기 위해 늘 노력하는 잔디 팀. 2017년 상반기에만 총 19개의 신규 기능을 선보였는데요. 그 중 잔디 사용자 분들에게 가장 많이 사랑받은 기능 Top 7을 공개합니다.1. 게스트 초대 (준회원)준회원멤버 권한 관리가 조금 아쉽다는 평을 받던 잔디. 올 상반기 게스트 초대(a.k.a 준회원 기능) 기능이 추가되며 멤버 권한 설정이 가능해졌는데요. 관리자/정회원/준회원 등 3단계로 나눠 업무에 적절히 활용할 수 있게 되었습니다.준회원 기능을 활용 사례 또한 다양하게 나왔는데요.1)  팀 내 특정 프로젝트/업무 토픽에 협력사, 벤더, 대행사를 초대해 협업2) 인턴, 아르바이트생 등 한시적으로 근무하는 인원에 대한 준회원 설정-관리이 외에도 전국 프랜차이즈 점포 관리, 단기 프로젝트 진행을 위한 프리랜서 초대 등에 활용할 수 있습니다.> 관련 포스트 – 잔디의 준회원 기능, 이렇게 활용해보면 어떨까요?2. 잔디 접속 상태 on/off잔디업무용 메신저에 꼭 필요했던 기능이었죠? 팀 멤버의 잔디 접속 여부를 알려주는 기능도 큰 관심을 받았는데요. PC, 모바일 앱을 통해 잔디에 접속한 유저는 하늘색 점이 멤버 프로필 옆에 표시됩니다. 이 기능은 1:1 메시지를 전송할 때 상대방의 답변 속도를 예측할 수 있어 유용하다는 평이 많았습니다.3. 방해 금지 시간대 설정 (퇴근 후 알림 방지)공사 분리개인용 메신저의 업무 사용은 여러 가지 병폐를 낳았는데요. 퇴근 후에도 계속되는 업무 메시지로 많은 직장인들이 고통받고 있죠? 상반기에 출시된 기능 중 언론의 주목을 받기도 했던 방해 금지 시간대 설정(a.k.a 퇴근 후 알림 방지)는 연결받지 않을 권리, 공과 사를 분리하는데 효과적인 기능입니다.잔디 메신저메시지 알림 수신 요일, 시간을 설정해 원하는 일정 내에서만 알림을 받을 수 있습니다. 휴가, 개인적인 사정으로 업무 메시지를 받기 어려운 경우엔 부재 중 설정을 이용할 수 있는데요. 팀원에게 부재 중 상태가 표시되도록 설정할 수 있어 유용합니다.4. 영상 통화 (비디오 컨퍼런싱)영상 통화잔디 엔터프라이즈 플랜(유료 모델 중 하나)를 사용하는 기업들이 쓸 수 있는 영상 통화(a.k.a 비디오 컨퍼런싱) 기능! 1:1, 1:N 미팅에 끊김 없이 사용할 수 있는 잔디의 영상 통화 기능도 상반기 업데이트된 기능 중 많은 관심을 받았는데요. PC, 모바일 모두 활용할 수 있어 원격 근무, 출장 등이 잦은 팀원들에게 편리하다는 의견이 있었습니다.5. 읽기 전용 대화방잔디그동안 업무 공지사항 혹은 관리자가 메시지를 전달해야 하는 토픽도 필요하다는 의견이 많았는데요. 상반기에 업데이트된 읽기 전용 대화방은 관리자만 메시지를 남길 수 있는 일방향 소통(One-way communication) 채널 역할을 합니다. 회사/팀 공지사항 전달 등 내부 소통 용도로도 활용할 수 있지만, 전국 점포 점주 대상 공지사항 전파 등 외부 소통을 위해서도 사용되는 케이스도 볼 수 있었습니다.6. 화면 캡쳐-편집 툴화면 캡처상반기 업데이트된 기능 중 가장 많은 사랑을 받은 기능이죠? 빠른 업무 커뮤니케이션을 위해 자주 사용되는 화면 캡처-편집 툴은 소통의 레벨이 달라졌다는 평을 사용자 분들로부터 들었습니다. 단순히 화면 캡처뿐만 아니라, 필요에 따라 글씨나 도형, 색상을 넣어 전달하고자 하는 내용을 효과적으로 상대방에게 전달할 수 있어 활용도가 높은 기능 중 하나입니다.* 현재는 PC 설치형 메신저에서만 사용하실 수 있습니다.7. 스탬프 이모티콘이모티콘마지막 기능은 스탬프 이모티콘입니다. 업무 커뮤니케이션에 새로운 에너지를 불어넣는 잔디 이모티콘! 많은 잔디 유저들이 사용하고 있는 기능인데요. 상반기에 추가된 스탬프 이모티콘은 다른 이모티콘들과 달리 업무 관련 텍스트로 디자인된 형태인데요. 업무 답변 시 유용하게 쓸 수 있다는 피드백을 받았습니다.이상 상반기에 업데이트된 내용 중 잔디 유저들의 사랑을 가장 많이 받은 기능 7개를 소개해드렸는데요. 여러분도 이 기능들 잘 쓰고 계신가요? 보다 즐겁고, 편리하게, 효율적인 업무 커뮤니케이션 환경을 제공하기 위해 잔디 팀은 남은 하반기에도 여러분이 원하는 기능을 다수 선보일 예정입니다. 앞으로도 많은 관심과 응원 부탁드립니다!#토스랩 #잔디 #JANDI #팀문화 #결산 #2017년 #상반기 #돌아보기 #원격근무 #디지털노마드 #재택근무 #리모트
조회수 77

업무의 스트레스를 모두 날려버리는 바로고 "스파클링데이"

바로고스파클링데이바로고직원들의 단합을 위한톡톡 튀는 스파클링데이업무의 스트레스를 모두 날려버리는바로고의 스파클링데이바로고에서만 만날 수 있는특별한 바로고복지 입니다.업무의 스트레스를 모두 날려버리는 바로고 "스파클링데이"거창한 것은 아니지만소소하게 준비한 스파클링데이한 번 먹으면 계속 먹게 된다는 젤리한 통 다 먹을 때까지 멈출 수 없는 프링글스그 외 입을 심심하지 않게 해줄 각종 스낵류들~출출함이 살짝 밀려오는 시간오후의 졸음이 살짝 밀려오는 시간바로고의 스파클링데이가 시작되었습니다.배고픔을 참지 못한 손 하나가스르륵~ 먼저 시식을 해보기로 했어요.삼삼오오 팀원들이 모이면서업무에서 잠시 벗어나이런저런 이야기를 나누기 시작합니다.업무할 때 중요한 것은팀워크!사람과 사람으로 하는 관계업무처리의 신속한 처리모두 모두 팀워크가 강하면힘든 일도 함께 나아갈 수 있습니다.바로고는 끈끈한 팀워크를 바탕으로함께 노력하고 응원하며더 나은 내일의 바로고를 위해파이팅 하고 있어요!스파클링의 종류는취향에 따라!각자의 취향을 존중하는바로고의 문화~한 사람의 의견이 존중되고팀원들이 의기투합하며바로고만의 사내문화를 만들어 가고 있습니다.살짝 부족한 부분은햄버거로 든든하게 채웠습니다.스파클링과 함께 하는 버거세트더욱 맛있게 먹을 수 있었어요.날씨가 점점 더 좋아지고배달이 증가함에 따라바쁜 바로고의 일상에서기분까지 좋아진 스파클링데이 였습니다.앞으로도 바로고는직원들의 편의를 위해세심한 배려를 잊지 않을 것입니다.바로고를 만들어가는바로고의 모든 임직원 여러분오늘도 파이팅입니다!바로고바로고는대한민국을 대표하는배달대행 전문 업체입니다.배달대행바로고"배달에 대한 고민""배달의 어려움"바로고만의체계적인 배달시스템과바로고프로그램 을 통해배달에 대한 고민을 덜어드리겠습니다.바로고배달대행 문의02-550-9938www.barogo.com
조회수 8674

내가 창업을 선택한 3가지 진짜 이유 (동기)

내가 창업을 선택한 3가지 진짜 이유"왜 창업을 하셨나요?"스타트업 대표들이 가장 많이 듣는 질문 중에 하나이다. "남들 밑에서 일하기 싫었다."라는 대표부터, "세상을 바꾸고 싶다"라는 거창한 이야기를 하는 대표까지 각각의 창업자마다 창업이란 길을 선택한 수많은 동기와 이유들이 있다.  나 역시도 같은 질문을 받았을 때마다 상황에 맞는 그저 평범한 수많은 모법 답안을 대답했지만 말하지 않았던 내가 창업을 선택한 3가지 진짜 이유들에 대한 이야기이다.1. 돈으로 살 수 있는 것들 (여태껏 살아온 세상)마이클 센델의 책들을 개인적으로 정말 좋아한다. 하지만 작년 베스트셀러인 "돈으로 살 수 없는 것들"은 내가 경험한 현실과는 거리가 있어 보인다. 교수라는 사회적 위치와 어느 정도의 경제 수준과 상황에 올라갔기 때문에 나올 수 있는 책이 아닐까? 세상 대부분의 사람들은 돈으로 살 수 없는 것보다 돈으로 살 수 있는 것, 즉 현실에 매달려 살아가고 있다.돈이 인생의 전부는 아니라는데 동의한다. 하지만 돈이라는 것이 행복을 위한 여러 조건 중에 가장 기본적이고 도 많은 부분을 차지한다는 것을 나는 경험했다.우리 집은 어렸을 때부터 가난했다. 옥탑 단칸방에서 네 식구가 살기도 했었고, 이사를 정말 많이도 다녔던 기억이 있다. 경제적으로 부족한 아버지 때문에 평범한 가정주부 대신 어머니는 일터를 선택해야만 했고 정말 많은 고생을 하시는 모습을 지켜보며 자라왔다. 흔히 드라마에 나오는 것처럼 여유롭게 가족들이 여행을 다녀왔던가 하는 행복한 추억들은 그리 많지 않다.시간이 흘러 성인이 되었고 사회로 나와 열심히 노력하며, 좋은 직장을 들어가기 위해 치열하게 경쟁하고 살아남았다. 하지만 한창 젊었던 시절 사랑하던 여자 친구와 경제적인 문제로 헤어지는 상처를 받기도 했다. 유복한 가정의 여자 친구 부모님에게는 직장생활 급여라는 경제력만을 가지고 있던 나는 턱없이 부족했고 결국 우리는 이별이라는 현실을 선택해야만 했다. 어머니가 살아오신 삶 때문 일까? 어머니는 항상  "네가 능력이 안된다면 귀한 집 딸을 데리고 와서 고생시키지 말아라."라고 하셨었다. 나도 동의한다. 사랑하는 여자를 어머니처럼 고생시키고 싶지 않기 때문이다. 그런데 뭐 결혼 안 한다고 큰일 나는 거 아니지 않은가? 그리고 내 있는 그대로의 모습을 인정하고 받아들이는 여자를 만나면 되지 않는가?그래! 가난했던 집안 환경, 사랑 그리고 결혼 이런 것들은 이겨냈고 앞으로도 없이 살 수 있다고 생각한다. 나도 좋은 집에 살고 비싼 차를 몰고 싶지만 주어진 현실에 만족하면서 열심히 일하고 실력을 쌓고 나 스스로의 삶을 개척하고 살아가고 있다고 자부하며 지난 30대 초반을 지내왔다.어머니는 나와 동생의 대학교 등록금을 벌기 위해 10년 넘게 식당을 하셨었다. 두 아들이 대학교도 무사히 마치고 자리를 잡아가기 시작했지만 자식들한테 나중에 신세 지고 손 벌리기 싫다고 하시면서 계속 열심히 일만 하셨다.2012년 가을하늘도 정말 무심하시지. 그렇게 평생 고생만 하고 사셨던 어머니에게 정말 큰 아픔이 다가왔다.식당일로 인해 보건증을 끊으러 병원을 가셨다가 암 4기 말 판정을 받으셨다. 가족들에게 알리지도 않고 몇 주를 고민하시다가 털어놓으셨고 온 가족이 그때 받은 충격은 이루 말할 수 없었다. 큰 대학병원으로 옮겨 검사가 시작되었고 의사로부터 길어야 6개월이라는 시한부 선고를 받게 된다. 그렇게 힘든 인생을 사시고 자식들을 위해 고생만 하신 어머니가... 힘들게 살면서도 더 여러운 이웃에게 항상 베풀기만 하셨던 분이 도대체 왜.수술을 하면 어머니가 조금이라도 더 사실 수 있다는 의사의 의견에 우리 가족은 수술을 하기고 결정했다. 시한부 판정을 받았더라도 수술 날짜는 바로 잡을 수가 없었다. 수많은 암환자들이 있는 대학병원에서 가능한 수술 날짜와 입원날짜는 아직도 하늘에 별따기이다. 서울의 유명하다는 모든 대학병원을 수소문해서 수술과 입원이 가능한 곳을 알아봤지만 짧게는 한 달에서 길게는 세 달의 시간을 기다리라는 답변만을 받았다. 주변의 지인들을 통해 아는 의사나 교수님 모든 인맥을 동원해서도 노력해봤지만 대답은 같았다. 보호자가 힘든 내색을 하면 안 좋기 때문에 속으로 정말이지 수백 번을 울면서도 어머니 앞에서는 괜찮다고 웃어 보이며 금방 찾을 수 있다고 안심을 시켜 드렸었다. 하지만 현실에서 하루가 다르게 상태가 악화되는 어머니를 보면서 내가 할 수 있는 것은 아무것도 없었다. 어머니 본인 스스로의 충격과 현실은 그보다 더 잔인했다. 그렇게 온 가족이 모든 수단을 동원했지만 유명한 의사가 있고 가장 믿을만한 병원에서 두 달 정도 뒤에 가능하다는 최종 통보를 받았다. 아니 남아있는 6개월 중에 2달을 그럼 그냥 시간을 보내고 기다리라는 말인가!시간은 계속 빠르게 흘러갔고 살려야만 했고 방법을 찾아야만 했다. 그러던 중 어머니 친구분이 떠올랐다. 그 어머니 친구분은 재산이 몇백,몇천억이 아닌 "조"단위 정도 되는 큰 부자이신데 그분이라면 병원의 높은 사람을 아시지 않을까? 지푸라기라도 잡는 심정으로 전화를 걸었다."아이고 혁재야 그런 일 있으면 말하지 아줌마가 전화 한 통 넣을게"돌아오는 대답은 그냥 평범했고 형식적이었다. 하지만 정확히 하루 만에 두 달을 기다리라고 했던 바로 그 대학병원의 원무과장에게 직접 전화가 왔다."OOO님 되시죠. 내일 바로 입원하시고요. 최대한 빠르게 수술 진행하도록 돕겠습니다."정말 그렇게 말도 안 되는 일이 벌어졌고, 어머니는 다음날 바로 입원을 할 수 있었다. 흔히 말하는 각과의 과장들이 직접 내려와서 어머니를 캐어하기 시작했고 그것도 모자라 다른과의 과장들과 협진을 통해 수술 준비를 이어갔다. 병원에서의 모든 직원들의 역시 대우가 틀려졌다. 입원을 하자마자 정말 빠르게 모든 검사가 완료됐고 바로 수술 날짜가 잡혔다. 그분의 전화 한 통 후 그렇게 대한민국 최고 권위의 의사들과 좋은 시설의 병원에서 며칠 만에 모든 검사와 수술이 이루어졌다. 수술 이후에도 지속적으로 수많은 치료가 이루어졌다.수술은커녕 입원조차 안된다고 하던 바로 그 병원에서...그렇게 6개월 시한부 판정을 받으신 어머니는 병원에서 기적같이 살아나셨다. 나중에 모든 사실을 알게 되었다. 부탁을 하신 그 부자라는 어머니 친구와 남편분의 가족은 병원에서 흔히 말하는 VIP, 아니 VVIP 중에서도 최고의 VVIP였다. 가장 높은 사람인 병원장과 형, 동생 하면서 골프를 치는 사이이고 병원에 기부도 수십억 하고 그런 부류의 사람들. 병원장부터 내려온 오더는 어머니의 수술과 치료에 총동원이 되었던 것이다.수술이 끝나고 어머니 친구분께 고맙다는 인사를 드리고 나서 혼자 병원 벤치에 앉아 엉엉 울었다. 정말이지 고맙고 서럽고 만감이 교차했다. 사랑하는 사람조차 살리지 못하는 부족한 내 모습이 싫었고 돈이 사람 생명을 결정하는 현실이 너무나도 싫었다. 하지만 한편으로는 그래도 이렇게 어머니를 살린데 어디인가 스스로를 위안했다. 병원은 사람을 치료한다고 살려야 한다고 하지만 이익을 내야만 하는 영리 조직이고, 그 조직은 돈의 의해서 움직인다라는 것을. 사랑하는 사람을 경제적인 이유로 제대로 된 치료를 못 받게 하고 잃을 수 있는 현실을 머리로는 받아들였지만 가슴속으로는 받아 들 일 수 없었다.< 드라마 낭만닥터에서 주인공의 아버지는 VIP 환자에게 수술순서에 밀려서 죽는다. >-진짜 복수 같은 걸 하고 싶다면 그들보다 나은 인간이 되거라. 분노 말고 실력으로 되갚아줘. 알았니? 네가 바뀌지 않으면 아무것도 바뀌지 않는다. <낭만 닥터 중에서>그렇게 난 피눈물을 흘리며 다짐했다. 꼭 성공할 거라고. 그래서 다시는 이런 일을 겪지 않을 거라고 무기력하고 능력 없는 오늘까지의 내가 앞으로의 나에게 다짐한 채찍질이자 복수였다. 그건 내 가족, 형제, 그리고 미래에 있을 내 아내와 아이들에게 부족한 아버지로 남기 싫음이었다. 내가 사랑하는 사람들이 적어도 돈이라는 이유 때문에 죽어가는 일은 다시는 만들지 않을 수 있는 실력(경제력)을 가질 거라 결정했다. 안 좋은 길(불법적인 프로그램을 개발하는 일)로 들어서는 것도 고민했지만 떳떳하지 못할 거라 생각해고 내가 생각한 최선을 방법은 직장생활이 아닌 정정당당하게 창업을 도전해서 성공하면 경제적으로도 풍족해질 수 있는 합법적인 테두리 안에서의 선택이었다.암환자의 보호자 역할을 하면서 알게 된 사실인데 통계적으로도 경제적으로 부족한 사람들이 더 많이 암에 걸린다고 한다. 힘든 삶에서 건강을 챙길 시간 와 돈이 없고 일만 하면서 살아가기 때문이다. 그 어머니의 부자 친구분은 온 가족이 정기적으로 수백, 수천만 원이 넘는 건강검진을 받고 있고, 아프다면 최고의 의료진과 의료시설로부터 혜택을 받고 있다.수술은 잘 되었고 어머니는 빠르게 회복하셨다. 어머니는 평소 "짝" 애청자이셨다. 두 달여간의 입원기간 동안 어머니는 병원에서도 그 프로를 보시면서 "너는 밖에서 여자도 못 만나지?", "빨리 장가가라" 잔소리를 하시곤 했다. 정말 재미있게도 그날 낮에 짝 섭외 요청이 왔었고 어머니의 깜짝 선물 겸 가족 특집이라는 이유로 출연을 결정했다. "니 주제에 무슨 짝을 나가?" 하고 웃으시면 어머니는 방송 출연 전날까지 날 믿지 않으셨다. 어머니는 병원에서 외출 허가를 받고 같이 방송 촬영지에 오셨고 온 가족이 출연하는 즐거운 추억도 그때 만들게 된다. 방송 이후 자식 자랑에 어머니는 병원에서 일약? 스타덤에 오르셨고 다시 미소를 찾으셨다.기쁨도 잠시 그 뒤로 신체의 다른 부위로 전이가 되어 치매나 다리 마비 같은 위기가 해마다 찾아왔다. 하지만 어머니는 버티고 또 버텨내셨다. 4기 말 암환자로 6개월 시한부 판정을 받았던 어머니는 그렇게 암과 싸우며 아직도 살아가고 계시다."내가 너 장가가는 건 보고 죽어야 하는데.""내가 너 성공하는 건 보고 죽어야 하는데."아직도 입버릇처럼 말씀하시는면 난 이렇게 대답하고 한다."장가도 갈 거고 성공도 할 거야. 근데 한다고는 하는데 조금 더 걸릴 거 같아."그리니 더 살아서 꼭 큰아들 장가가고 성공하는 거 보고 눈감으라고.이게 내가 항상 "입에 칼 물고 열심히 해야 한다."라고 말하는 진짜 이유이다.2. 일하고 싶은 회사? (경험한 현실)10년... 그러고 보면 직장생활을 꽤 한 것 같다. 벤처기업, 중소기업, 대기업까지 10년이란 시간은 나에게 정말 많은 경험들을 주었다. 하지만 일하고 싶은 회사, 매일 출근하고 싶은 회사와는 거리가 멀었다. 흔히들 말하는 꿈의 직장을 다니고 싶었고, 그렇기 위해서는 실력을 쌓아야만 한다는 것을 알게 되었다. 기사나 책에서 꿈의 직장을 하면 나오는 여러 회사들 말이다. 높은 연봉과 쾌적한 근무환경과 복지들을 부러워한다.<구글이 꿈의 직장이라고 들어가고 싶다는데 나는 꿈의 직장에 들어갈 실력이 되는가?>사회생활을 하면서 흔히 말하는 꿈의 직장들인  마이크로소프트 본사, 구글 본사와 같은 가 본 경험을 가질 수 있었다. 특히나 LG전자에서 첫 해외 출장지였던 시애틀에서 짬을 내서 혼자 마이스로 소프트 본사를 쳐들어 간 경험은 신선한 충격으로 다가왔다. 그들이 어떻게 일하는지, 세계 최고라는 친구들은 어떤 모습을 하고 있는지 궁금해서였다. 지도를 펼치고 방문객을 위한 센터를 쳐들어갔는데 그걸로 만족하지를 못했다. 사무실이 가보고 싶었다. 안 되는 영어로 미친척하고 XBOX를 하고 있는 MS 직원에게 사진을 찍어 달라고 부탁하고 사진을 찍어준 그 친구에게 말을 건넸다. (참고로 내 영어실력은 초등학생 수준이다.)나 한국에서 온 누구누구인고 어느 회사 다니는 최혁재라고 한다. 너 개발자냐? 나도 개발자인데 반갑다. 사무실 좀 구경시켜줄 수 있니? 그 친구는 시원하게 허락을 했고 MS의 역사와 문화, 자기 사무실 구경에 심지어 식당에서의 점심식사까지 대접해줬다. (자기가 개발자로서 MS를 선택한 이유와 백그라운드 설명하는데 MIT 박사였다. )<  XBOX 를 열심히 하고 있던 MS 직원 아니 MIT 박사님  >< XBOX><  MS의 창업멤버들 >그 뒤로도 시애틀로 출장 갔었고 내가 짬날 때마다 찾아간 곳은 마이크로소프트 본사, 스타벅스 본사, 보잉사 본사였다. (그러고 보면 나도 정상은 아니다.) 그러면서 많은 것들을 느끼고 간접적으로다가 좋은 회사라는 기준을 잡아가기 시작했다. 그 친구들의 이야기는 한결같았다. 정말 좋은 회사에서 내게 배움을 줄 수 있는 인재들과 함께 일한 하는 것이 나의  행복이라고. 그래서 스스로가 항상  실력을 쌓기 위해 노력을 게을리하지 않는다 였다.내 주변에서 역시도 실력 있는 친구들이 있었고, 이 친구들과 함께 선의의 경쟁을 하며 일한다면 정말 좋을 것 같다는 생각을 했었다. 한국의 회사들에 만연한 학연, 지연 그리고 흔히 말하는 정치질이 싫었고 실력으로 인정받고 정정당당하게 승부하고 결과를 인정하는 그런 합리적인 조직생활을 꿈에 그렸었다. 지금 내가 속한 조직에서는 내가 그럴 수 없다는 것을 알게 되는데 10년이라는 시간이 걸렸다. 꿈의 직장은 가보지 못했지만 나부터 시작해보면 어떨까? 였고 그렇게 창업을 결정했다. 그 시작은 힘들겠지만 개개인들이 실력을 쌓고 동료들과 함께 노력한다면 흔히들 말하는 꿈의 직장을 만들어 볼 수 있지 않을까?"꿈의 직장은 꿈에서나 나올 거 같은 훌륭한 실력이 있는 사람들한테만 주어지는 곳이다."3. 이끌던가 따르던가 비키던가 (미래)<  3가지 삶 중에 내가 마지막으로 선택한 삶은 이끄는 삶이였다. >나의 20대는 게임에 미쳐 PC방에서 2년간 숙식을 했었고, 스노보드에 빠져 12년 동안 매해 겨울마다 보드를 탔었다. 명품으로 꾸미며 된장질도 해봤고, 여자들도 만나볼 만큼 만났고, 강남 클럽, 나이트 죽돌이였던 나는 사회에서 바라는 삶이 아닌 허황되고, 한심하며, 노력하지 않는 비주류, 흔히들 말하는 바닥을 깔아주는 그런 비주류의 사람, 게다가 주어진 환경이나 사회에 불만을 가지기만 하고 노력하지 않는 사람. 당장의 쉬운 것, 좋은 것, 재미있는 것, 편한 것만을 추구하며 보낸 비키는 삶을 살았었다.직장 생활을 하면서 조금씩 철이 들어갔고 10년, 20년 뒤 내 미래에 대한 고민을 하기 시작했다. 조금은 나아졌지만 내 현실은 비키는 비주류의 삶에서 조금 나아진 따르는 삶이었다. 불합리한 부분에 수긍했으며, 아닌 것을 아니다 라고 말하지 못했다. 갑을병정이라는 우리 사회의 만연된 계급 아닌 계급을 맛보기도 했다. 노력하지 않고 평범하게 현실에만 안주하다가 시작된 나의 첫 사회생활은 흔히들 말하는 "을"로써 시작되었다. 첫회사가 LG전자의 외주 개발업무를 했었기 때문에 LG전자 사무실을 들어가서 개발 일을 하기도 했다. 그때 내 목에는 LG전자 사원증 대신 주황색의 외주업체 사원증이 걸려 있었다. 중요한 회의는 참석하지 못했고 그저 개발, 딱 개발일만 했다. 똑같이 일을 해도 나보다 높은 급여를 받고 좋은 대우를 받는 그 친구들을 보면서 나 스스로에 대한 현실에 불평, 불만을 늘여놓기만 했다. 내가 참여하고 고생한 프로젝트가 마무리되고 제품이 출시되어도 내가 다니는 회사의 제품은 아니었다. 그리고 다짐했다. 불평, 불만이 아니라 내가 그곳으로 올라가는 실력을 쌓아보자고.그렇게 시간이 흘러 중소기업을 거쳐 LG전자까지 들어갔다. 사람들은 내가 그냥? 운이 좋아서 이직을 잘했고 마지막에는 대기업에 들어간 줄 알지만 난 10년 동안 약 500번의 이력서를 썼고 100여 번의 면접을 봤다. 이력서를 고치고 다듬고, 경력을 쌓고 그만큼 노력했고 올라가고 싶었다. 대기업에 가고 많은 것들이 현실적으로 변했다. 높은 급여, 복리후생 사회적인 지위와 주변의 인정, 하물며 만나는 여자의 급 또한 달라졌다. 노력에 대한 보상은 그렇게 피부로 느껴졌다. 그렇게 대기업에서 4년이란 시간을 지내면서 많은 것들을 배웠다. 조직관리와 글로벌 프로젝트 그리고 훌륭한 동료들을 만나게 되었다. 들어오기 전에 불평불만을 했던 나 자신 스스로가 너무 초라했었구나 역시 그때 느꼈다. 내가 만난 동료들은 나와 비슷한 개발 실력만 가지고 있지 않았다. 개발도 잘하고 영어도 잘하고 조직관리 등 개발 외적인 부분인 조직에서 필요한 다른 부분의 실력 역시 뛰어났으며 내 회사 내제품이라는 애사심과 자부심까지 가지고 있었다.아직도 주변에 대기업 다니는 사람들을 보고 일도 못하는 게 연봉만 높다고 불평. 불만들 늘여 놓는 친구들이 많이 있다. 내가 경험한 그들은 실력 있고 그만한 대우를 받을만한 동료들이었다. 살인적인 업무강도와 성과를 내야만 하는 스트레스를 직접 겪어보지 않고 말하는 사람들을 보면 화가 나기까지 한다. 세상에 공짜는 없다. 그만큼 일을 하고 성과를 내기 때문에 그런 대우를 받는 것 아닐까?"나는 대기업은 체질에 안 맞아"라고 이야기를 하면서 다니는 회사의 연봉에 불만을 같은 건 합리적이지 않다. 높은 연봉을 받고 싶으면 대기업을 가면 되지 않는가. 그런데 대기업을 갈 노력과 실력이 되는지 정말 냉정하게 생각해보자. 그리고 창업을 준비한다면 바로 창업하지 말고 나는 꼭 대기업을 들어 가보라고 추천한다. 대기업을 들어가기 위해서 노력하는 모든 것들은 자산으로 남는다. 그리고 들어가서는 정말 많은 것들을 배우고 느낄 수 있고 훌륭한 동료들을 만날 수 있다. 나 역시도 창업 후 가장 도움이 되는 경험은 바로 대기업에서 경험했던 것들이었다.10년간의 직장 생활. 많은 것을 배운 경험은 소중한 자산이 되었지만 아직도 내 모습은 이끄는 삶이었다. 10년 뒤 20년 뒤 조직 내에서의 내 모습은 내가 원하는 모습이 아니었다. 누리고 있던 모든 것을 내려놓는 것은 쉽지 않았다. 하지만 내가 선택한 창업이란 길은 내가 원하는 이끄는 삶으로 가기 위한 가장 올바른 방법이라 생각했고 아직도 그때의 결정을 후회하지 않는다.#스푼 #Spoon #창업자 #스타트업 #스타트업창업 #초기창업 #고민 #스타트업문화 #인사이트
조회수 695

스타트업을 위한 AARRR(해적지표) 개념잡기

AARRR, 스타트업에 종사하거나 그로스해킹에 관심있으신 분이라면 한 번쯤은 들어본 용어일 것입니다.  그런데 이걸 뭐라 읽어야 할지, 정확히 무슨  용어인지 궁금해 하신 분들이 계실 것 같습니다.  오늘은 AARRR(해적지표)이 무엇인지 살펴보도록 하겠습니다.1. ‘AARRR’이란?이 용어는 미국의 스타트업 엑셀러레이터인 500 Startups를 이끌고 있는 데이브 맥클루어(Dave McClure)가 개발한 분석 프레임워크입니다. 스타트업은 아이디어를 바탕으로 서비스(상품)를 만드는 데 능숙하지만, 이 서비스를 어떻게 효과적으로 사람들에게 알리고 꾸준한 사용자를 확보하기 위해 무엇을 개선하면 좋을지 늘 고민인데요, AARRR은 시장 진입 단계에 맞춰 특정 지표를 기준으로 우리 서비스의 상태를 가늠할 수 있는 효율적인 지표입니다. 수많은 데이터 중 핵심 지표에 집중할 수 있게 함으로써, 분석할 리소스(인력, 시간)가 충분하지 않은 스타트업에게 매력적인 프레임워크라고 할 수 있습니다.2. AARRR 단계별 핵심 지표Acquisition : 어떻게 우리 서비스를 접하고 있는가Activation : 사용자가 처음 서비스를 이용할 때, 긍정적인 경험을 제공하는가Retention : 이후의 서비스 재사용률은 어떻게 되는가Referral : 사용자가 자발적 바이럴, 공유를 일으키고 있는가Revenue : 최종 목적(매출)으로 연결되고 있는가약어를 살펴보면 사용자가 서비스를 접하고 우리가 원하는 매출을 일으키기까지의 전체적인 과정을 크게 5단계로 분석한다고 볼 수 있는데요, 그럼 각 단계별로 실제 어떤 지표를 살펴봐야 하는지 보겠습니다. Acquisition → DAU, MAU, New UserAcquisition은 사용자를 획득하는 단계로, 서비스 안정화를 거친 후 시장 진입을 위해 공격적인 마케팅을 할 때 집중하는 지표입니다. 여러 채널을 통해 얼마나 많은 사용자가 유입되고 있는지, 신규 사용자는 얼마나 획득했는지 등을 파악합니다. 적은 비용으로 높은 볼륨을 일으키는 채널이 좋은 채널이라 할 수 있습니다.Activation →  Bounce Rate, Avg.PV, Avg,Duration, Signup사용자가 서비스를 이용하기도 전에 이탈하는 비율은 얼마나 되는지, 서비스 이용을 시작했다면 이후의 engagement는 어떻게 되는지 등을 파악합니다. Bounce Rate(반송률)은 첫 페이지에서 서비스를 종료한 비율로, 부정적 사용자 경험을 나타내는 지표입니다.Retention → Retention Rate사업 초기 단계에서 가장 중요한 지표 중 하나가 바로 Retention입니다. Retention Rate는 서비스의 만족도를 가장 잘 대변하는 지표로,서비스 만족도가 높다면 꾸준한 사용으로 높은 재사용률을 나타낼 것입니다. 반대로 재방문율이 낮으면 해당 서비스는 오랫동안 존속시키기 힘듭니다. 이럴 경우 낮은 Retention을 끌어올리기 위한 푸시, 메일링, 리뉴얼 등의 다양한 노력이 필요합니다.Referral → Channel, SNS Share Rate최근 신규 앱을 받아 설치한 적 있으신가요? 설치하는 데 영향을 끼친 것은 무엇이었나요? 아마 지인의 추천을 통해 설치하는 경우가 다수일 것입니다. 이미 사용자 디바이스에 설치된 앱은 포화상태이기 때문에, 지인의 추천이 아니면 새로운 앱을 설치할 이유도, 목적도 쉽게 제시하지 못하는 상황입니다. 그렇기 때문에 서비스가 안정화되고, 성장하는 단계에서 중요한 데이터가 바로 Referral입니다. 우리 서비스가 어디에, 얼마나 공유되고 있으며, 그 채널로 인해 얼만큼의 사용자를 다시 확보하는지 등을 살펴봐야 합니다. 결국 Referral 단계는 Acquisition과 엮어서 선순환을 돌게 됩니다.Revenue → Conversion서비스가 존재하기 위해선 명확한 수익모델이 있어야 합니다. 이를 판단하는 지표는 서비스마다 다르겠지만, 서비스를 이용하는 사용자의 Conversion Rate를 높이는 것이 목표인 것은 동일할 것입니다.*각 지표 별 자세한 분석 방법과 예시가 궁금하다면 아래 내용을 참조해주세요.http://www.wisetracker.co.kr/모바일-앱-데이터-분석으로-비즈니스-목표-달성하기/결론AARRR은 허무 지표(vanity metrics, 총 방문수 등)에 시간을 쏟기 보단 실행 지표(Actionable metrics)에 집중함으로 개선의 방향을 잡고 최적화할 수 있도록 돕는 분석 프레임워크라고 볼 수 있습니다. AARRR과 같이 Actionable한 데이터를 제공하는 와이즈트래커를 통해 보다 많은 기업이 통찰력 있는 데이터 기반의 실행으로 높은 퍼포먼스를 내길 기대합니다.( Dave McClure의 AARRR 문서 보기 : http://www.slideshare.net/dmc500hats/startup-metrics-for-pirates-long-version)  * WISETRACKER는 모바일 광고 성과 측정부터 In-app 이용자/컨텐츠 분석, 푸시메시지 최적화까지 지원하는 모바일 통합 분석/타겟팅 솔루션입니다. 와이즈트래커 솔루션의 무료체험을 원하실 경우 여기를 클릭해주세요.* WISETRACKER가 제공하는 무료 데이터 분석 컨설팅를 원하신다면 여기를 클릭해주세요.#와이즈트래커 #스타트업 #데이터분석 #AARRR #인사이트
조회수 2754

Radix? Redis!

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

혁신은 서비스만으로는 만들 수 없다.

처음에 핀다(Finda)를 창업하면서 주변 사람들이나 투자자들, 또는 금융업 종사자들에게 설명할 때 하나같이 다음과 같은 두 가지 반응이 있었다. 한 가지는 ‘꼭 필요한 서비스인데 왜 없지?’이고 다른 한 가지는 ‘예전에 많이들 시도했었는데 성공하지 못했던거 같은데.’였다. 공동창업자와 이런 반응에 대해서 우리가 판단하는 시장에 대한 상황을 설명하곤 했는데, 결론적으로 ‘아무래도 시장이 준비가 덜 되었다.’라는 이야기를 나누곤 했다.론 애드너의 <혁신은 천개의 가닥으로 이어져있다> (출처: 교보문고)그러나 핀다를 창업하고 이제 1년 남짓한 시간밖에 지나지 않았는데, 시장의 반응은 180도 바뀌었다. 창업 초기 온라인으로만 100% 대출이 가능했던 상품은 없었는데, 지금은 1금융권에서도 모바일, 온라인 상품들을 출시하고 있고, 오히려 금융권에서도 먼저 손을 내밀어주고 있다. 사용자들도 송금과 결제 외의 금융활동들을 모바일과 온라인에서 하고자 하는 니즈도 많아졌고, 나름 각 분야에서 경쟁 서비스들도 출시되기 시작했다. 그러면서 몇 년 전에 읽었던 론 애드너 (Ron Adner)의 ‘혁신은 천개의 가닥으로 이어져 있다 (The Wide Lens)’ 책의 내용을 다시 되짚어보게 되었다.내 실행이 의미를 지니려면 누구와 무엇을 해야하는가책에서는 혁신을 성공적으로 이루고 경쟁자를 물리치려면 무엇이 필요한지에 대해 세 가지 요소가 필요하다고 설명하고 있다. 첫째는, ‘실행 초점 (Execution focus)’, 둘째는 혁신이 의미가 있으려면 다른 누가 혁신을 일으켜야 하는지에 대한 ‘공동 혁신 (Co-innovation)’, 마지막으로 최종 소비자가 완전한 가치 제안을 평가하기 전에 다른 누가 내 혁신을 수용해야 하는지에 대한 ‘수용 사슬 (Adoption Chain)’을 이해하고 있어야 한다고 한다.  론애드너의 혁신 전략에 대한 와이드 렌즈 관점. 나도 잘하고, 시장도 성장하고, 이해관계자들도 모두 성장해야 한다.‘공동 혁신'과 ‘수용 사슬'에 대해 조금 더 살펴보자면, 이를 잘 만들어내기 위해서는 가치 창출을 위해 결합해야 하는 모든 요소의 청사진과 생태계 구조 구축 ‘순서'에 대한 명확한 계획을 바탕으로 단계적으로 시스템을 구축해야 한다고 설명한다. 즉, 나만 잘한다고 혁신이 되는 것은 아니라고 말이다.엠페사(M Pesa) 혁신 사례로 본 시장의 중요성 여러가지 좋은 사례가 나와있지만 그 중에서도 성공적인 핀테크 회사로 유명한 케냐의 엠페사(M Pesa)의 케이스가 와 닿았다. 영국의 보다폰 (Vodafone)과 케냐의 최대 이통사인 사파리콤 (Safaricom)의 합작사인 엠페사는 대리점 네트워크를 기반으로 문자 메세지를 통해 은행 계좌가 없어도 쉽게 송금할 수 있게 해주고 있다. 이제는 케냐 뿐만 아니라 남아공, 인도, 이집트 등 다양한 국가에서 총 2천 5백만 명이 사용하는 글로벌 금융 서비스로 성장했다. (2016년 3월말 기준)엠페사가 처음 설립되었을 때 목표는 케냐의 수많은 금융 소외 인구에게 기본적인 은행 업무를 제공하고 자본에 대한 접근성을 높이는 것이었다. 이는 케냐 전체 인구의 27%는 휴대전화를 가지고 있으며, 27%는 잠재적 고객으로 만들 수 있다고 판단했고, 무엇보다 이 수치가 빠르게 증가하고 있다는 시장 상황을 바탕으로 실행에 옮겼다고 한다.  케냐 엠페사의 대리점 모습. 상대적으로 허름한 모습이 오히려 고객 가치의 본질에 대해 다시 생각하게 해준다. (출처: Worldbank 홈페이지)엠페사의 이러한 성공에는 공동 혁신과 수용 사슬의 발전이 뒷받침 되었다. 문자메세지 기간망 그리고 휴대폰 보급이라는 ‘공동 혁신’은 이미 일어나 있었고, 사파리콤 대리점이라는 ‘수용 사슬’의 매개체 또한 갖추고 있었다. 하지만 수용 사슬에서 어려움에 봉착하게 되었는데, 도시에 사는 사람이 농촌에 사는 친지에게 송금하는 경우가 많아, 농촌 대리점에 현금이 부족해졌다. 이를 위해 대리점들이 협력하는 방식을 바꿨다.그 외에도 더 큰 과제는 복잡한 생태계 구조였다. 송금 이외에도 대출이나 입출금과 같은 포괄적인 금융 서비스를 제공하기 위해 케냐의 미소금융 기관인 파올루 케냐(Faulu Kenya)와 시범 사업을 개시했는데, 일일이 언급하기가 힘들만큼 많은 과제가 있었고 그 중에서도 특히 파올루 케냐의 기존 관행 및 시스템을 수용하기 위해 소비자 거래가 상당히 복잡해졌다고 했다. 이 문제를 해결하기 위해 엠페사는 기본적인 요소인 문자를 통한 송금 서비스에만 집중하였고, 결국 파올루 케냐를 제외시킨 송금 관련 ‘최소 실행 가능 범위’의 생태계를 구성하게 되면서 큰 성공을 거둘 수 있었다.2년 이후 엄청나게 단순화된 최소 실행 가능 범위. 집중할 수 있도록 시장을 구성하는 것이 매우 필요하다. 혼자만의 노력으로는 소비자에게 의미있는 서비스가 될 수 없다핀다가 만들고자 하는 혁신도 같은 맥락에서 바라볼 수 있다. 너무나 당연한 이야기겠지만 우리 혼자만의 노력으로는 소비자에게 의미있는 서비스가 될 수 없기 때문이다. 다른 O2O 서비스와는 다르게 더 나은 사용자경험을 모바일과 온라인에서 제공하는 것이 매우 중요하다. 금융상품에 대한 정보를 보고 선택하는 것은 온라인에서 일어나고, 실제로 가입하고 구매하는 것은 오프라인에서 일어나서는 소비자에게 제대로 된 온라인 경험을 제공하는 것이라고 할 수가 없기 때문이다. 은행에서 온라인에서 가입할 수 있는 상품을 만드는 ‘공동 혁신'이 이루어지고 또 그러한 상품을 은행 자체 채널이 아닌 객관적으로 비교하고 추천할 수 있는 채널에서 판매하는 ‘수용 사슬'이 만들어져야 핀다의 비즈니스가 성립할 수 있다.하지만  ‘공동 혁신'도 ‘수용 사슬'도 이루어지지 않았으니 핀다와 같은 서비스가 제대로 기능하기가 힘든 환경이었다. 베타 서비스를 런칭한 2016년 봄에도 온라인으로 신청 완료까지 가능한 금융 상품은 드물었고 다른 금융기관의 상품들과 같은 자리에서 비교하고 판매하는 것에 대한 거부감이 매우 높았다. 그러다보니 핀다가 갖고 가야할 생태계 구조에 대해서도 많은 고민을 할 수 밖에 없었다. 상품의 가입과 구매가 오프라인에서 일어나기 때문에 핀다가 대출 중개인과 계약을 통해서, 또는 직접 운영을 통해서 오프라인 가입과 관련된 문제를 해결해줘야 하나 하는 여러가지 고민이 있었다. 이러한 고민은 최종 소비자에게 제공할 가치 측면에서 뿐만 아니라 비즈니스 모델 측면에서도 중요한 문제였다.핀다의 사업가치 청사진을 도식화 해보았다. 제대로 가치를 제공하는 온라인/모바일에 집중하는 것이 중요하다고 생각한다.(중개인을 활용하게 되면 ‘수용 사슬'이 복잡해지고 최종 소비자에게 제공할 수 있는 가치도 불명확해진다. 그림을 그려보니 금융기관이 늘어날수록 복잡성이 역시 증대되는 것은 어찌보면 당연한 이야기인 것 같다.)그러나 보다 우리가 명확하게 가치를 제공할 수 있는 범위로 서비스를 제한하기로 결정했다. 온라인 및 모바일로 가입이 가능한 금융상품을 소개하고 연결하는 것에만 집중하기로 한 것이다. 온라인이 더 익숙한 밀레니얼 세대의 중요성 증대, 비대면 본인인증 등 기반기술 및 정책의 발전, 해외 사례, 조금씩 보이는 금융권의 변화의 움직임 등을 바탕으로 핀다와 같은 서비스가 필요해질 환경이 금방 올 것이라 생각 (또는 베팅을) 했다.  시장의 흐름을 잘 파악하는 노력창업을 하기 전뿐만 아니라 회사를 운영하면서도 필요하다.그 사이 시장 환경은 빠르게 변화해왔다. P2P 대출부터 올해 첫 번째 인터넷 은행인 K뱅크까지 오픈하였고, (카카오뱅크도 오늘 출시되었다.) 시중 은행들도 온라인, 모바일 전용 금융상품들을 앞다투어 출시하고 있다. 씨티은행의 경우에는 지점의 80%를 폐쇄하겠다고 발표하기도 했다. 핀다 자체의 혁신에 이러한 환경 변화가 더해지면서 핀다는 신한은행, 씨티은행을 포함, 30개 이상의 금융기관과 파트너십을 맺을 수 있었다고 생각한다. 공동 혁신 및 수용 사슬이 이루어졌음은 물론이고 갈수록 범위가 커지면서 자연스레 핀다가 최종 소비자에게 제공할 수 있는 가치도 커지고 있다.핀다의 목표는 사람들이 큰 노력없이 자신에게 잘 맞는 금융상품을 추천 받음으로써 큰 고민이나 걱정없이 더 나은 금융생활 (a better financial life)을 할 수 있게 하는 것이다. 그 일환으로 핀다 고객들이 핀다에서 상품들을 비교해보고 추천받아 가장 적합하다고 판단되는 대출 상품을 가입하면 제휴사와 함께 우대금리를 추가로 제공하는 핀다 전용 상품들을 출시했다.씨티은행과 제휴해서 직장인 신용대출을 받는 핀다고객들에게 0.5% 우대금리를 제공하고 있다. (출처: 핀다)창업 초기에는 시장에 대한 리서치, 인터뷰 등에 대한 노력을 많이 기울이는 편인데, 아무래도 스타트업을 하면서 수많은 업무와 이슈에 파묻혀 시장을 흐름과 사용자들의 변화하는 니즈를 놓치게 되는 경우가 종종 있다. 하지만 노력을 진짜 규모있는 혁신으로 만들기 위해서는 계속해서 변화하는 시장의 흐름과 이에 따라 사용자들에게 전달해 줄 수 있는 가치들이 어떻게 변할지 Wide Lens를 통해 시장을 보려는 노력을 끊임없이 기울여야 한다. 시장은 끊임없이 변화하고 있으니...#핀다 #스타트업창업 #창업가 #창업자 #철학 #마인드셋
조회수 1410

나는 부족한 사람입니다 -1

창업자 인터뷰 – 첫 창업설 연휴가 끝난 2월의 어느 날, 옐로모바일 사무실 내 까페인 '클럽옐로'의 한 미팅 룸에서 이상혁 대표를 마주했습니다. “나는 수줍은 사람입니다”라는 오프닝으로 시작된 옐로모바일의 공식 블로그. 그 첫 컨텐츠로 이 회사의 창업자인 이상혁 대표의 인터뷰를 싣기 위해서였습니다. 2시간여 동안 진행된 대담은 생각보다 흥미진진했습니다. 차분한 목소리로 이어진 대화였지만, 높고 낮은 굴곡이 있었고, 좌절과 희망이 보였습니다. 긴 대화를 마치고 나자 바로 떠오른 제목이 바로 “나는 부족한 사람입니다” 였습니다.완벽하기는커녕, 어찌 보면 지극히 평범한 이 대표의 실패와 시행착오로 가득 찬 인생 이야기를 지금 여러분께 전해드리고자 합니다.바쁜 여러분을 위한 Y의 다섯 문장 요약!!1. 창업은 상상도 못했던 대학생, 교수가 되고자 대학원에 갔으나 세미나 발표를 잘 못한다고 교수님이 세미나 중에 나가버리셨다?2. 석사 졸업하고 처음 시작한 직장 생활, 일을 못 해 첫 인사고과 'D'의 충격3. 우연한 만남으로 시작된 첫 창업, 처음엔 잘 나가는 듯 했으나 수년 뒤 회사 존폐 위기4. 7년 만의 피벗 (Pivot) 결정, 통장 잔고 200만원의 순간 수십억 원대 투자 유치5. 2년 후 마침내 이룬 흑자 전환, 그러나 근심 걱정은 이어지고대표님 안녕하세요, 사내기자 Y입니다.반갑습니다. Y라니, 뭔가 007 영화의 코드네임 같네요.하하 그런가요? 실은 옐로모바일 (Yello Mobile)의 앞 글자이기도 하지만, 계속해서 “왜(Why)”를 묻고 의미를 찾아보잔 뜻에서 지어본 이름입니다. 그런 의미에서 오늘은 인간 이상혁이 왜 지금 이 자리에 있게 되었는지를 파헤쳐보려고 합니다.파헤치실 것 까지야… 조금 긴장되네요ㅎ해치지 않습니다  그럼 과거로 돌아가서 시작을 해볼까 해요. 옐로모바일이 두 번째 창업으로 알고 있는데요, 학생 때부터 창업을 계획하셨나요?전혀요. 전 대학에서 경영학을 전공했습니다. 열심히 공부하며 나름 학점도 잘 받고 했지만, 내가 좋아하고 잘 할 수 있는 것이 무엇인지 감을 잡기 어려웠어요. 깊은 고민 끝에 내렸던 결론은, ‘어떤 것을 정리해서 남에게 설명하는 것에는 조금 자신이 있다, 하지만 큰 무리 앞에 나서는 것은 자신 없다, 그러니 교수가 되는 것에 도전해보자’ 였습니다.교수요? 묘하게 어울리는 것 같기도 한데요?그런가요?그 당시를 회고해보면, 인터넷이 처음 생기고 한창 홈페이지라는 것이 유행하던 때 였어요. 이 때 창업해서 인터넷과 게임 사업을 했던 동기들이 오늘날 대한민국 대표 IT 기업들을 이끌게 되었죠. 하지만 전 스스로가 창업과는 거리가 먼 사람이라고 생각했어요. 오죽했으면 오프닝 에서 보셨듯이 제 이모님께서 기사를 보시고 “이 상혁이가 우리 상혁이냐”는 말씀을 하셨겠어요ㅎㅎ 아무튼, 교수가 되기 위해선 학위가 필요했고, 그래서 대학원에 가 마케팅을 공부하기 시작했습니다. 매주마다 논문을 읽고, 교수님과 선배들 앞에서 세미나 발표를 하는 것이 진짜 고역이었어요. 스스로 발표를 못한다고 생각한 적이 없었는데, 매 번의 세미나는 제게 공포의 순간으로 다가왔죠. 심지어 제가 발표를 너무 못한다며 교수님께서 중간에 나가버리신 적도 있었어요. 그렇게 2년이 지나자 그래도 어딜 가서 발표 못한다는 얘기는 더 이상 듣지 않게 된 것 같아요.당시 교수님께서도 지금의 대표님을 보시면 꽤나 놀라시겠어요ㅎㅎ 계속해서 박사 공부는 안 하셨나요?당연히 박사 학위가 필요했고, 이왕 하는 것 미국 아이비리그에 도전해보고 싶단 생각이 있었어요. 하지만 미국 학교는 학비가 훨씬 비쌌고, 가정 형편이 넉넉하지 않아서 재정적으로 손을 벌릴 곳도 없었기 때문에, 학비 마련을 위해 직장 생활을 시작해야 했어요. 군 문제도 해결해야 했고요. 그래서 석사 졸업 후 삼성SDS 정보기술 연구소에서 3년간 근무하게 되었어요. 무려 개발 직군으로요.개발이요? 경영학과에 마케팅 석사셨는데요?그래서 하루하루가 너무 힘들었어요. 물론 기본적인 개발은 배운 적이 있었지만, 서울대나 카이스트 전산과 출신 친구들 틈바구니에서 IT 개발 업무를 할 때의 자괴감이란 이루 말할 수가 없었죠. 처음 몇 달을 떠올리면 네 글자가 떠올라요. 월.급.루.팡.월급루팡이라니... 웃프네요ㅜㅠ 그 위기를 어떻게 극복하셨나요?첫 인사평가에서 D를 받았어요. D를 두 번 받으면 나가라는 소리라고 하더라고요. 큰 충격을 받고 ‘살아남아야 한다’라는 일념 하에 선배, 동기들을 괴롭혀가며 밤새 개발 공부에 매달렸어요. 그렇게 6개월 정도 지났을 때, 여전히 동기들보다는 못 했지만 그래도 월급루팡 신세는 모면할 수 있었던 것 같아요. 다음 고과에서 B를 받았거든요. :)진땀 나는 6개월이었겠어요정말 그랬죠. 실은 살면서 학업 등에 있어 한 번도 실패를 맛보거나 뒤쳐진 적이 없었거든요. 그래서인지 제 부족함을 마주했을 때의 충격이 더 컸던 것 같아요. 그 충격 가운데서 얻은 중요한 깨달음이 몇 가지 있었어요. 하나는, ‘내가 남보다 못할 수 있다는 것이 당연하다’는 것. 내가 경험하지 못한 영역의 선배들, 능력자들과 경쟁하면 나는 아무것도 아닌 존재가 될 수 있다는 깨달음이요. 거기서 이어진 두 번째 교훈은 ’이 세상에 혼자 할 수 있는 것은 없다’는 것. 수많은 분들께 도움을 받으면서 그 동안 내 공부, 내 일만 신경 썼던 스스로가 많이 창피했어요. 세상은 서로 도우면서 성장하는 곳이라는 것을 체감하면서 크게 성장할 수 있었던 시기였죠.지금 이 자리를 빌어 그 당시 사수였던 류대선 선배님과 동기들에게 감사의 말을 전하고 싶네요ㅎㅎ영상메시지라도…?그런 건 부끄러워서 싫어요….ㅠㅠ네 알겠습니다ㅋㅋ 그럼 그 이후 박사 진학을 하셨나요?아니에요. IT 회사에서 팀원들과 함께 일을 하면서 새로운 재미를 느끼기도 했고, 당시 한메일, 네이버 같은 국민 서비스들을 보면서 새로운 도전에 눈을 뜨게 되었어요. 나도 창업을 해볼 수 있지 않을까란 생각으로 인터넷 경매 서비스 사업 계획서를 만들어 조언을 구하고자 KAIST 교수님을 찾아 뵈었다가 연구실 선배를 만났고, 그 때 함께 창업을 해보지 않겠냐는 제안을 받았어요. 믿고 신뢰하던 선배들과 창업을 할 수 있다는 사실에 들떴고, 1998년 9월, 5명의 창업멤버 중 막내로 시작했던 회사가 디엠에스랩이었죠.교수에서 창업가라, 뭔가 급선회한 느낌인데요, 사업 아이템이 무엇이었나요? 게임? 인터넷 서비스?동기들이 인터넷이나 게임 관련 사업을 했을 때, 저희가 택했던 것은 SI (System Integration) 컨설팅이었어요. CRM 전략 컨설팅 및 관련 시스템 구축업무가 핵심이었죠. 명백히 보이는 시장을 공략하고자 했던 생각이 컸던 것 같아요.컨설팅이라… 그럼 주로 어떤 업무를 하셨나요? 개발? 영업?작은 벤처에 제대로 된 업무 정의가 어디 있겠어요. 제안서를 쓰고, 선배들 따라다니며 제안 발표를 하고, 영업을 통해 프로젝트가 수주되면 프로젝트 관리를 하고, 산출물을 만들어 결과 발표도 하고… 필요한 모든 업무에 함께했죠.지금까지의 경험과는 또 다른 종류의 일들이었을 것 같은데요?그렇긴 했지만 잘 해낼 수 있을 것이란 자신감이 있었어요. 그 것이 착각이라는 것을 깨닫는데 그리 오랜 시간이 걸리지 않았다는 것이 함정이지만요. 막상 부딪혀 보니, 제대로 할 줄 아는 것이 하나도 없었어요.외람된 말씀이지만, 능력자 이미지와는 거리가 조금 멀었네요…하하하 맞아요. 선배들이 옆에 앉아 불러주는 것들을 파워포인트로 정리하며 제안서를 썼어요. 그리고 대기업 경영진 앞에서 발표하는 선배들의 모습을 보면서, 나도 발표는 조금 한다고 생각했던 스스로가 부끄러워졌죠. 수준 자체가 달랐어요. 그렇게 발표를 잘 했다고 수주가 되는 것은 또 아니었어요. 계약을 성사시키기까지 고객사 실무자, 팀장, 경영진이 원하는 것을 파악하고 해결책을 제시하며 확신을 주는 과정도 결코 만만치 않았죠. 프로젝트가 시작돼도 쉬운 것이 하나도 없었어요. 늘어가는 새치에 한숨도 많이 쉬었던 것 같아요ㅎㅎ 이 과정을 7년 동안 계속했어요.7년씩이나요?네. 실은 그렇게 오래 할 것이라고 아무도 생각하지 못했어요. 많이 힘들기도 했고요. 그래도 제게는 엄청난 배움의 시간들이었어요. 생각하는 것을 말로 잘 풀어내고, 이를 다시 글로 잘 정리하는 것을 배웠고, 사람의 마음을 사는 영업은 어떤 것인지, 그리고 프로젝트 관리를 하면서 발생하는 수많은 이슈들을 어떻게 하면 잘 해결할 수 있는지 등등.그 정도 시간이면 사업이 많이 성장했겠어요.처음 사업을 시작했을 때는 그렇게 될 줄 알았어요. 근데 실상은 그렇지 못 했죠. 초기에는 연간 몇 억 원의 흑자가 났지만, 몇 년 지나지 않아 경쟁이 치열해지고, 저가 수주 때문에 수익성이 떨어졌어요. 더 시간이 흐르니 고객사의 수요가 줄고, 심지어 우리 직원들이 고객사로 이직하면서 우리는 단순한 외주업체로 전락하게 되는 과정을 보았죠.엄청 심각한 상황으로 들리는데요?맞아요. 이 때 깨달은 것이, 명함과 회사 홈페이지를 만들고 힘차게 시작한 사업을 유지하는 것이 정말 힘들다는 것이었어요. 사업을 통해 흑자를 내는 것도 힘들지만, 그것을 유지하는 것은 더 힘들구나. 이래서 많은 비즈니스의 라이프사이클이 길지 않구나. 경쟁환경, 시장환경이 변하니 많은 회사들이 망하는구나…이 위기를 어떻게 극복하셨나요?답이 잘 보이지 않았어요. 그래서 피벗 (Pivot)을 해야겠다고 생각했죠. 다른 기업을 위해 컨설팅 하는 것은 그만하고, 우리 사업을 하자고 말이에요.7년 차에 피벗이요? 절대 쉽지 않은 결정이었을 것 같은데…정말이지 여간 어려운 일이 아니었어요. 낮에는 기존 사업의 프로젝트를 진행하며 돈을 벌고, 밤에는 신규 사업을 계획했어요. 하지만 신규 사업이라는 것이 밤에 짬을 내어 고민하고 준비한다고 쉽게 만들어지는 것이 아니잖아요. 결국 어느 날 기존 사업의 프로젝트 수주를 중단했어요. 회사 자금도 거의 바닥난 상태에서 말이죠. 당시 대표이사였던 현진석 대표님이 급여 만드느라고 백방으로 뛰어다니며 고생해주신 덕분에 저희는 신규 사업을 만들어가는데 집중할 수 있었어요.엄청난 결단이었네요. 그렇게 해서 신규 사업은 무사히 시작할 수 있었나요?결국 시작한 사업이 마이원카드라고, 지갑에 다수의 포인트카드를 가지고 다니지 않아도 손쉽게 포인트를 적립해 주는 서비스였어요. 지금의 시럽과 유사한. 그리고 너무나 감사하게도 수십억 원의 투자 유치를 받아 회사가 기사회생할 수 있었죠. 투자 유치 직전 통장 잔고가 200만원 정도였던 것으로 기억해요.드라마가 따로 없네요. 그래도 덕분에 새로운 도전의 장을 열 수 있었겠어요.그랬죠. 투자 유치 과정에서 대주주가 외부 주주로 바뀌었고, 어떻게 하다 보니 창업 멤버 막내였던 제가 대표이사가 되어 있었어요. 이 때 처음으로 ‘대표’라는 자리의 막중함을 깨달았던 것 같아요. 지분 3~4%의 대표이사였고, 중간 중간 좋은 이직 제안들도 있었지만 흔들리지 않고 더욱 열심히 할 수 밖에 없었죠. 제게는 젊음을 바친 사업이었고, 제 분신과도 같다고 생각했거든요.  2년 간의 적자가 이어지고 투자금을 거의 소진해갈 무렵, 마침내 흑자 전환에 성공할 수 있었어요.거의 10년 가까이 첫 사업을 하시면서 우여곡절이 많으셨을 텐데, 가장 크게 느낀 점이 있다면?하나를 꼽긴 어렵지만, 그래도 가장 크게 고생하고 깨달은 것이 있다면 바로 ‘사람’.이룬 것이 많지 않은 작은 회사가 직원을 뽑는 것이 쉬운 일이 아니었어요. 지금도 많은 중소 기업 대표님들께서 갖고 계신 고민이겠지만, 마치 제가 인터뷰를 하는 것이 아니라 인터뷰를 당하는 느낌이랄까? 그렇게 하나 하나 공들여 채용한 직원들이 어느 날 불쑥 찾아와 “우리 회사는 비전이 뭐에요?”라고 따지면서 묻거나, 회식 자리에서 불만을 토로하며 하소연할 때, 대표이사로서 대답이 참 궁색해서 정말 많이 미안했죠. 하지만 더 힘들었던 것은 정들었던 직원들이 하나 둘 대기업이나 다른 회사로 떠나가는 일이었어요. 축하할 일이었지만 한 편으로는 상처도 많이 받았던 것 같아요. 그리고 서운함 보다는 그 친구들을 붙잡을 수 없는 회사라는 자괴감이 더 컸어요. 결혼하고 가정이 생긴 친구들에게 월급도 많이 올려주지 못했고, 복리후생도 변변치 못했으니까요.이 때 배운 정말로 소중한 것은, 창업자는 멋진 비전을 제시할 수 있어야 한다는 것. 그리고 그 비전이 비전으로만 끝나서는 절대 안되고, 무조건 사업을 성공시켜야 한다는 것이었어요. 그렇게 해야 함께 해준 소중한 직원들에게 나누어 줄 것이 생기니까요. 시장 환경, 경쟁 환경을 탓할 수 있을 만큼 창업자의 책임은 가볍지 않더라고요.어수룩했던 창업의 준비기부터 치열했던 10년간의 첫 창업 속 좌절과 성공까지, 이상혁 대표의 이야기를 들으면서 저 Y 또한 많은 것을 생각하게 되었습니다. 계속해서 흥미진진한 이야기를 이어나가고 싶지만, 분량 조절을 위하여 이 이후 이어진 첫 사업의 매각, 인수 회사에서의 새로운 도전, 그리고 옐로모바일의 창업에 대한 이야기는 다음 편에서 전해드리도록 하겠습니다. 짧지 않은 첫 이야기, 재미있게 읽히셨기를 바라며, 저는 다음 이야기로 찾아 뵙겠습니다. Y였습니다.
조회수 1343

레진 기술 블로그 - 자바 기반의 백엔드와의 세션 공유를 위한 레일즈 세션 처리 분석

레일즈 기반의 프론트엔드(브라우저에서 서버 사이드 렌더링 계층까지)와 자바 기반의 백엔드(내부 API와 그 이후 계층)이 세션을 공유하기 위해 먼저 레일즈의 세션 처리 과정을 분석하고, 레일즈 세션 쿠키를 다루기 위한 자바 소스 코드를 공유합니다.여기저기 자랑하고 다녔으니 아시는 분은 아시다시피 레진은 구글앱엔진을 사용하고 있습니다. 지금이야 Java, Python, Node.js, Go 언어와 Flexible Environment 같은 다양한 선택지가 있지만, 레진이 입주할 당시만 해도 Java 7(subset), Python(subset)을 지원하는 Standard Environment라는 선택지 밖에 없었죠.최근 Saemaeul Undong 기술 부채 탕감의 일환으로 자바7, 스프링3.x, JSP(!) 기반의 백엔드에 포함되어 있던 프론트엔드를 레일즈 기반의 프론트엔드 서버(서버 사이드 렌더링을 담당하는 서버는 프론트일까요? 백엔드일까요?)로 분리하고 있습니다.서로 다른 세계의 존재들 - 자바와 레일즈의 세션을 공유해야하는 상황이 문제의 발단입니다.자바와 레일즈의 세션을 공유하는 여러가지 방법이 있겠지만, 가장 단순하고 효과적인 방법은 쿠키(cookie)라고 판단하고, 세션 encrypt/decrypt와 marshal/unmarshal을 동일한 방식으로 맞추기로 했습니다. (백엔드 API를 완전히 stateless하게 새로 만들면 좋겠지만, 코인은 벌어야 소는 키워야죠)이를 위해 레일즈의 세션 처리 과정을 분석하고 정리했습니다.레일즈의 actionpack의 action_dispatch/middleware/cookie.rb를 보면 EncryptedCookieJar 클래스의 초기화 과정은 다음과 같습니다(digest의 경우 따로 지정안하면 SHA1이 사용되는 듯):class EncryptedCookieJar < AbstractCookieJar # :nodoc: include SerializedCookieJars def initialize(parent_jar) super if ActiveSupport::LegacyKeyGenerator === key_generator raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " + "Read the upgrade documentation to learn more about this new config option." end secret = key_generator.generate_key(request.encrypted_cookie_salt || '') sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || '') @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end private def parse(name, encrypted_message) debugger deserialize name, @encryptor.decrypt_and_verify(encrypted_message) rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage nil end def commit(options) debugger options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value])) raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE end end key_generator는 EncryptedCookieJar에 포함된 SerializedCookieJars 모듈에 정의되어 있습니다:module SerializedCookieJars # ... def key_generator request.key_generator end end 흠… 좀 더 파보죠. request.key_genrator는 다음과 같습니다:class Request # ... def key_generator get_header Cookies::GENERATOR_KEY end #... end 흠… 좀 더 파야할 듯 ㅠㅠ.Cookies::GENERATOR_KEY는 다음과 같습니다:class Cookies #... GENERATOR_KEY = "action_dispatch.key_generator".freeze end action_dispatch.key_generator는 레일즈의 엔진 모듈에 해당하는 railties의 application.rb에 정의되어 있습니다:def key_generator # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 @caching_key_generator ||= if secrets.secret_key_base unless secrets.secret_key_base.kind_of?(String) raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String, change this value in `config/secrets.yml`" end key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000) ActiveSupport::CachingKeyGenerator.new(key_generator) else ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token) end end # ... def env_config @app_env_config ||= begin validate_secret_key_config! super.merge( # ... "action_dispatch.key_generator" => key_generator, "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt, "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt, "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt, "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer, "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest ) end end 너무 깊이 판 느낌적느낌(?)이 있지만, 여기까지 왔으니 좀 더 파보겠습니다.핵심 알고리즘은 activesupport의 key_generator.rb, message_encryptor.rb, message_verifier.rb에 정의되어 있습니다.먼저, key_generator.rb의 핵심은 다음과 같습니다:class KeyGenerator def initialize(secret, options = {}) @secret = secret # The default iterations are higher than required for our key derivation uses # on the off chance someone uses this for password storage @iterations = options[:iterations] || 2**16 end # Returns a derived key suitable for use. The default key_size is chosen # to be compatible with the default settings of ActiveSupport::MessageVerifier. # i.e. OpenSSL::Digest::SHA1#block_length def generate_key(salt, key_size=64) OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) end end 계속해서, message_encryptor.rb의 핵심은 다음과 같습니다:def initialize(secret, *signature_key_or_options) options = signature_key_or_options.extract_options! sign_secret = signature_key_or_options.first @secret = secret @sign_secret = sign_secret @cipher = options[:cipher] || 'aes-256-cbc' @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer) @serializer = options[:serializer] || Marshal end def _encrypt(value) cipher = new_cipher cipher.encrypt cipher.key = @secret # Rely on OpenSSL for the initialization vector iv = cipher.random_iv encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data << cipher.final "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" end def _decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} cipher.decrypt cipher.key = @secret cipher.iv = iv decrypted_data = cipher.update(encrypted_data) decrypted_data << cipher.final @serializer.load(decrypted_data) rescue OpenSSLCipherError, TypeError, ArgumentError raise InvalidMessage end def encrypt_and_sign(value) verifier.generate(_encrypt(value)) end def decrypt_and_verify(value) _decrypt(verifier.verify(value)) end (Hopefully)마지막으로, message_verifier.rb의 핵심은 다음과 같습니다:def initialize(secret, options = {}) raise ArgumentError, 'Secret should not be nil.' unless secret @secret = secret @digest = options[:digest] || 'SHA1' @serializer = options[:serializer] || Marshal end def valid_message?(signed_message) return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank? data, digest = signed_message.split("--".freeze) data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) end def verified(signed_message) if valid_message?(signed_message) begin data = signed_message.split("--".freeze)[0] @serializer.load(decode(data)) rescue ArgumentError => argument_error return if argument_error.message =~ %r{invalid base64} raise end end end def generate(value) data = encode(@serializer.dump(value)) "#{data}--#{generate_digest(data)}" end private def encode(data) ::Base64.strict_encode64(data) end def decode(data) ::Base64.strict_decode64(data) end def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) end # ... # encode, decode는 base64사용 이제 레일즈가 쿠키 기반의 세션을 어떻게 처리하는지 조금 눈에 들어옵니다. 그러나 우리의 최종 목표는 레일즈의 내부를 공부하는 것이 아니라, 자바에서 동일한 처리를 하는 것입니다. 모듈 의존성 따위는 가볍게 무시하고 무한복붙(?)을 시전해서, 레일즈의 세션 처리 과정을 눈으로 확인할 수 있도록 재구성했습니다:require 'openssl' require 'base64' require 'concurrent/map' class Object def blank? respond_to?(:empty?) ? !!empty? : !self end def present? !blank? end end class Hash # By default, only instances of Hash itself are extractable. # Subclasses of Hash may implement this method and return # true to declare themselves as extractable. If a Hash # is extractable, Array#extract_options! pops it from # the Array when it is the last element of the Array. def extractable_options? instance_of?(Hash) end end class Array def extract_options! if last.is_a?(Hash) && last.extractable_options? pop else {} end end end module SecurityUtils def secure_compare(a, b) return false unless a.bytesize == b.bytesize l = a.unpack "C#{a.bytesize}" res = 0 b.each_byte { |byte| res |= byte ^ l.shift } res == 0 end module_function :secure_compare end class KeyGenerator def initialize(secret, options = {}) @secret = secret # The default iterations are higher than required for our key derivation uses # on the off chance someone uses this for password storage @iterations = options[:iterations] || 2**16 end def generate_key(salt, key_size=64) OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) end end class CachingKeyGenerator def initialize(key_generator) @key_generator = key_generator @cache_keys = Concurrent::Map.new end # Returns a derived key suitable for use. def generate_key(*args) @cache_keys[args.join] ||= @key_generator.generate_key(*args) end end class MessageVerifier class InvalidSignature < StandardError; end def initialize(secret, options = {}) raise ArgumentError, 'Secret should not be nil.' unless secret @secret = secret @digest = options[:digest] || 'SHA1' @serializer = options[:serializer] || Marshal end def valid_message?(signed_message) return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank? data, digest = signed_message.split("--".freeze) data.present? && digest.present? && SecurityUtils.secure_compare(digest, generate_digest(data)) end def verified(signed_message) if valid_message?(signed_message) begin data = signed_message.split("--".freeze)[0] @serializer.load(decode(data)) rescue ArgumentError => argument_error return if argument_error.message =~ %r{invalid base64} raise end end end def verify(signed_message) verified(signed_message) || raise(InvalidSignature) end def generate(value) data = encode(@serializer.dump(value)) "#{data}--#{generate_digest(data)}" end private def encode(data) ::Base64.strict_encode64(data) end def decode(data) ::Base64.strict_decode64(data) end def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) end end class MessageEncryptor module NullSerializer #:nodoc: def self.load(value) value end def self.dump(value) value end end class InvalidMessage < StandardError; end OpenSSLCipherError = OpenSSL::Cipher::CipherError def initialize(secret, *signature_key_or_options) options = signature_key_or_options.extract_options! sign_secret = signature_key_or_options.first @secret = secret @sign_secret = sign_secret @cipher = options[:cipher] || 'aes-256-cbc' @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer) @serializer = options[:serializer] || Marshal end def encrypt_and_sign(value) verifier.generate(_encrypt(value)) end def decrypt_and_verify(value) _decrypt(verifier.verify(value)) end def _encrypt(value) cipher = new_cipher cipher.encrypt cipher.key = @secret # Rely on OpenSSL for the initialization vector iv = cipher.random_iv encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data << cipher.final "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" end def _decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} cipher.decrypt cipher.key = @secret cipher.iv = iv decrypted_data = cipher.update(encrypted_data) decrypted_data << cipher.final @serializer.load(decrypted_data) rescue OpenSSLCipherError, TypeError, ArgumentError raise InvalidMessage end def new_cipher OpenSSL::Cipher.new(@cipher) end def verifier @verifier end end #key generate encrypted_cookie_salt = 'encrypted cookie' encrypted_signed_cookie_salt = 'signed encrypted cookie' def key_generator secret_key_base = 'db1c366b854c235f98fc3dd356ad6be8dd388f82ad1ddf14dcad9397ddfdb759b4a9fb33385f695f2cc335041eed0fae74eb669c9fb0c40cafdb118d881215a9' key_generator = KeyGenerator.new(secret_key_base, iterations: 1000) CachingKeyGenerator.new(key_generator) end # encrypt secret = key_generator.generate_key(encrypted_cookie_salt || '') sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt || '') encryptor = MessageEncryptor.new(secret, sign_secret, digest: 'SHA1', serializer: MessageEncryptor::NullSerializer) value = "{\"session_id\":\"6022d05887d2ab9c1bad8a87cf8fb949\",\"_csrf_token\":\"OPv/LxbiA5dUjVsbG4EllSS9cca630WOHQcMtPxSQUE=\"}" encrypted_message = encryptor.encrypt_and_sign(value) #encrypted_message = encryptor._encrypt(value) p '-----------encrypted value-------------' p encrypted_message # decrypt encrypted_message = 'bDhIQncxc2k0Rm9QS0VBT0hWc3M4b2xoSnJDdkZNc1B0bGQ2YUhhRXl6SU1oa2c5cTNENWhmR0ZUWC9zN05mamhEYkFJREJLaDQ3SnM3NVNEbFF3ZVdiaFd5YXdlblM5SmZja0R4TE9JbDNmOVlENHhOVFlnamNVS2g1a05LY0FYV3BmUmRPRWtVNUdxYTJVbG5VVUlRPT0tLXd1akRqOU1lTTVneU9LTWszY0I5bFE9PQ==--b0a57266c00e76e0c7d9d855b25d24b242154070' p '-----------decypted value-------------' puts encryptor.decrypt_and_verify encrypted_message p '---------------------------------------' 이 과정을 자바로 구현한 소스는 생략 깃헙에 올려두었습니다. 이 코드를 이용해서 서블릿 세션과 연동하는 방법은 추후 사측(?)과 협의되는 대로 공유할 예정입니다. 물론, 그 전에 쿠키를 공유할 필요가 없어지면(or 공유할 쿠키가 없어지면) 더 좋겠죠 :D
조회수 1509

공유 숙박업, 어디까지 왔나?

말도 많고 탈도 많은데 계속 성장하는 공유 숙박업. 도시민박업으로 외국인들에게 오픈하고, 공유 숙박업으로 내국인에까지 오픈하게 되는데요 저희가 먼저, 개정법안 대표발의 의원 이완영의원실에 전화하여 물어보았습니다. "공유 숙박업, 어디까지 됐나요?" 현재 계류 중인 법안의 워딩을 그대로 가져와보았습니다. 국토의 계획 및 이용에 관한 법률” 제6조 제 1항에 따른 도시지역에서 숙박/숙식을 제공하는 업을 도시민박업으로 정의도시민박업의 영업일수는 연간 180일 이내   도시민박업은 문화체육관광 부령으로 정하는 안전/위생기준을 지켜야 함    관할 등록 기관장이 연간 영업일수 등 준수사항을 단속함   실제 법안을 그대로 옮겨놓으니 잘 와 닿지 않으시죠? 위의 법안 내용을 조금 쉽게 설명드려볼게요. 법에 도시지역이라고 명시된 곳에서는 도시민박업 가능! → 대도시는 물론이거니와 대부분의 관광지 인근 도시는 모두 도시지역입니다.  365일 중 180일은 도시민박업 가능! → 180일을 어떻게 활용해야 할지! 다양한 경우의 수에 대해 에어 위클리 다음호에서 자세하게 설명드리겠습니다.  화재 예방 / 전기 사용 / 가스 사용 / 대피 / 질서 유지 및 안전사고 예방 / 위생, 총 6가지 기준을 통과해야 함! → 대부분의 건축물은 위의 기준을 충족하기 때문에 불법적인 건축물만 아니라면 크게 걱정하실 부분은 없습니다  구청/시청에서 단속활동을 함! 이처럼 생각보다 까다롭지 않은 기준으로 공유 숙박업을 시행할 것이라고 발의문은 말하고 있습니다. 하지만, 법안이 통과되기까지는 아직 많은 과정이 남아있습니다. 위의 법안을 발의한 이완영 의원실에 문의한 결과 아직 해당 상임위의 법안심사소위원회(이하 소위)조차 통과하지 못했다고 합니다. 법안은 발의된 후, 소위를 거쳐 상임위 전체회의, 법사위의 심사를 받은 후에야 본회의에 상정됩니다. 또한 본회의 통과 후에도 6개월이 지나야 법안이 시행되니 공유 숙박업이 시행되기까지는 아직 시간이 걸릴 것으로 예상됩니다만.. 그러나!!! 현 정부에서도 도시민박업과 비슷한 종류의 공유 민박업을 정부입법 추진하겠다고 밝혔는데요. 이와 관련한 정보 또한 저희가 계속 수집하여 조만간 여러분께 알려드리도록 하겠습니다.#핸디즈 #인사이트 #에어비앤비 #업계정보
조회수 824

기록을 시작하다

많은 사람들이 그렇겠지만, 나도 글을 쓰면서 생각을 정리해보고 싶다는 욕구를 느꼈다.많은 사람들이 그렇겠지만, 욕구를 느낀지 꽤 오래, 그리고 자주 느꼈지만 실천을 못했다.2017년, 30대에 접어들었다.(만으로는 29세라는 레퍼토리는 주변 87년생 형들이 넘나 우려먹어버린 관계로, 써먹지 않는 걸로)30대 입성의 힘을 빌려, 기록을 시작해보려 한다.무슨 주제를 써볼까, 어떤 테마로 써볼까를 고민하기 전에,아직 안해봤으니 일단 저질러서 이것저것 써보고 방향은 잡아나가는걸로.여기서도 스타트업 정신 발동! 시작이 반이겠죠? 그렇죠?^^*물리적으로, 심적으로 바빠지다보니 어떤 것을 해야지라고 생각만 했을 때 하지 못하는 경우가 대부분이다.(물론 의무적으로 해야하는 것들에 대해선 어떻게든 해오고 있다.)그래서 최근에 항상 써먹는 방법이 모든 활동들을 시스템화시키는 것이다.예를 들어, 최근의 국정농단 사태를 보면서 나 자신에게 회의감이 들었다. 연일 떠들석한 뉴스거리와 문제들 속에서 '내가 그 문제들을 판단할 만한 지식을 가지고 있는가. 알량한 지식가지고 정치에 관심이 있는 척, 깨인척 진보는 이렇고 보수는 이렇고 대통령은 잘못했다라고 말하고 있지는 않은가.' 라는 반문에 대해 아직도 자유롭지 못하다. 그리고 이러한 것을 자각하고 있으면서도 일상이라는 핑계속에 더 적극적으로 임하지 못하는 것 역시 부끄럽다.그래서 이러한 시스템을 만들기로 결심했다.주말을 이른오후/저녁/밤 3타임으로 나눴을때 총 6타임이 생긴다.그중에 한타임만큼은 이러한 시국과 정치, 경제 등에 대한 공부를 하는 것으로. 다른 한타임은 오늘부터 시작한 '한주를 정리하며' 라는 허세 가득한 가칭의 글쓰기 프로젝트를 진행하려고 한다.브런치를 물들이는 수많은 글들은 아마 이 첫 글에 선전포고된 '6타임 프로젝트' 로 물들지 않을까 기대해본다.- 현재는 1월 막바지 일요일 저녁 10시, 아메리카노 과다 복용으로 인한, 약간은 하이텐션 상태 #학생독립만세 #교육기업 #기업문화 #조직문화

기업문화 엿볼 때, 더팀스

로그인

/