스토리 홈

인터뷰

피드

뉴스

조회수 903

조건문을 긍정적으로!

Overview“나 혼자 프로젝트를 하니 주석은 안 달아도 무방해요” 이렇게 말하는 개발자는 그 코드를 가장 많이 보는 것도 자신이라는 사실을 잊고 있을지도 모릅니다. 구린 코드를 보고 욕했는데 3개월 전 자신이 작성한 코드란 걸 알면 그제서야 얼굴이 붉어지기 일쑤죠. 작은 습관이지만 약간의 변화만 준다면 분명 즐겁고 생산적인 개발을 할 수 있을 겁니다. 얼굴 붉어질 일도 없고요. 오늘은 그 노하우를 전해드립니다. 혼돈을 피하는 여섯 가지 코드 작성법조건문은 긍정적으로 쓰자조건문의 성능은 생각하지 말자조건 검증을 깔끔하게 하자주석은 적절하게, 적당하게 하자상수를 활용하자복잡한 코드는 풀어서 쓰자과거의 나 자신아, 넌 나에게 똥을 줬어1.조건문은 긍정적으로 쓰자”쟤가 그 아이가 아니지 않지 않나?!” 프로그램 코드를 마지막으로 실행하는 건 컴퓨터지만 코드를 작성하고 관리하는 건 결국 사람입니다. 수많은 조건문이 존재하는 프로그램에서 조건이 부정적이라면 한 번 더 생각해야 합니다. 반대로 조건문을 긍정적으로 작성하면 보다 편리하게 개발을 진행할 수 있습니다. 가능하다면 긍정적인 마인드로 조건문을 적어봅시다.<?php // 예제는 PHP로 작성 되었습니다. $title = $_POST['title']; // 공지사항 제목 if (empty($title)) {     echo '제목을 입력해주세요';    return; } // 위의 경우보다 한번 더 생각해야한다. if (!isset($title)) {     echo '제목을 입력해주세요';    return; } cf)비슷한 사례 for 증감식을 i– 처럼 적는 경우 꼭 필요한 경우가 아니라면 삼가는 것이 좋다. for로 작성 가능한 반복을 while로 구현하는 경우 for는 끝이 명확하지만 while은 언제나 불안하다. 2.조건문의 성능을 생각하지 말자간혹 조건문에 성능을 고민해 줄여보려는 개발자가 있습니다. 10개의 and 조건을 2개로 줄인다면 얼마나 이득일까요? 하지만 이것은 티도 나지 않는 적은 양입니다.1) 조건문을 압축하지 마세요. 시간이 지나면 자신의 코드가 마치 보물지도처럼 보일 수도 있습니다. 조건문을 최적화하려고 하기보다는 보기 좋고 읽기 편하게 변경합시다. 3.조건 검증을 깔끔하게 하자만약 게시물에 글을 쓰는 프로그램을 제작한다면 요청된 값들이 정상인지 확인해야 합니다. ‘게시물 제목이 있고, 글 내용이 있고, 글 분류가 정상이고, 뭐뭐 하면 등록!’이라고 작성하면 논리적인 접근으로 보이지만 코드의 상태는 그렇지 않습니다.<?php $title = $_POST['title']; // 공지사항 제목 $content = $_POST['content']; // 공지사항 내용 $category = $_POST['category']; // 공지사항 분류 if (!empty($title)) {     if (!empty($content)) {         if (!empty($category)) {             // 게시글을 등록한다.!         } else {             echo '카테고리를 선택해주세요';         }     } else {         echo '내용을 입력해주세요';     } } else {     echo '제목을 입력해주세요'; } if문 블럭이 중첩되어 가로 스크롤 압박에 시달릴 것이기 때문입니다. 또한 나중에 수정하려면 많이 고생해야 합니다. 조건 검증을 하는 코드라면 아닌 경우를 체크하는 것이 더 좋습니다. 아래와 같은 형태로 작성하는 게 깔끔하고, 유지 보수에도 도움이 됩니다. 게시물 제목이 없으면 오류 출력글 내용이 없으면 오류 출력글 분류가 정상이 아니면 오류 출력그 외 등등…<?php // 예제는 PHP로 작성 되었습니다. $title = $_POST['title']; // 공지사항 제목 $content = $_POST['content']; // 공지사항 내용 $category = $_POST['category']; // 공지사항 분류 if (empty($title)) {     echo '제목을 입력해주세요';     return;  } if (empty($content)) {     echo '내용을 입력해주세요';     return;  } if (empty($category)) {     echo '카테고리를 입력해주세요';     return;  } // 게시글을 등록한다.! 4.주석은 적절하게, 적당하게 하자주석이 많아야 좋을까요, 아니면 적어야 좋을까요? 이 논제는 여전히 개발자 사이에서 뜨거운 감자입니다. 다양한 의견이 있지만 저는 ‘적당한게 좋다’고 생각합니다. 주석이 없어서 고생한 적도 있지만, 주석이 너무 많거나 쓸모없었던 적도 겪어봤기 때문입니다. 가끔 “코드 한 줄마다 주석을 달아”라는 미친 선임도 있었고 “주석이 필요 없게 깔끔하게 짜”라고 말하는 기괴한 선임도 있었습니다. 사고의 최종 결과물인 프로그램 코드가 아무런 설명 없이 다른 사람 혹은 미래의 자신을 이해시키는 건 불가능한 일이라고 생각합니다. 다양한 테크닉과 아름다운(?) 코딩으로 주석을 줄여나갈 수는 있겠지만 꼭 필요한 곳엔 적어야 한다고 생각합니다. 4-1) 주석이 꼭 필요하다고 생각할 때 깊은 사고의 결과를 코드로 작성하였고, 다음에 왜 그렇게 작성했는지 헷갈릴 것 같을 때함정 카드가 발동되어 헤맬 것 같은 코드일 때코드가 길어져 기능의 단위별로 나눠서 보는 게 좋을 때기술된 함수나 클래스가 이름과 다르게 동작하는 코드일 때한참 디버깅 후에 허무함을 안겨준 코드일 때함수 클래스 파일에 대한 주석일 때변수가 특이성을 가지고 있거나, 타입별로 세팅되는 값일 때플러그인이나 라이브러리 사용법을 공유할 때 4-2) 주석을 줄여 나가야 한다고 생각할 때 조건문의 내용을 한글로 다시 기술하고 있을 때프로그램과 관계 없는 내용일 때변수명으로 설명이 가능한 내용을 기술하고 있을 때4-3) 주석이 잘못 되었다고 생각할 때 나만 이해할 수 있는 단어나 문장으로 기술된 주석일 때주어가 없는 주석일 때5.상수를 활용하자코드값에 따라 분기를 작성 중이라면 상수를 활용하는 게 좋습니다. ‘F’보다는 FACEBOOK_SERVICE 가 더 직관적이기 때문입니다.<?php // 예제는 PHP로 작성 되었습니다. if ($userAccountType == 'F') {     // 페이스북 유저 처리 로직 } /** 유저 구분 값 페이스북 */ define('ACCOUNT_TYPE_FACEBOOK', 'F'); // 코드는 좀 더 길어보이지만 별다른 주석 없이도 어떤 코드인지 알 수 있다. if ($userAccountType == ACCOUNT_TYPE_FACEBOOK) {     // 페이스북 유저 처리 로직 } 상수는 프로그램마다 다양한 형태로 지원되기 때문에 선언 후 참조해서 쓴다면 주석을 줄이는 데에 많은 도움이 될 것입니다.6.복잡한 코드는 풀어서 쓰자여러 가지 사고의 결정이 다시금 엮여서 또 다른 결과를 만들어야 하는 복잡한 코드입니까? 우선 서술형 문장으로 먼저 정리하십시오. 그 다음 오류가 없다면 이어서 작성하는 것이 좋습니다. 2)// 기획전이 시작 되면 세팅한 값으로 할인을 하고 // 기획전이 끝나면 원래의 할인율로 돌아오게 하는 프로그램 이다. 1. 대상 기획전을 찾는다.     * 기획전 시작일이 오늘인가? 종료일이 오늘인가? 2. 트랜잭션을 연다. 3. 대상 기획전 건수 만큼 루프를 돌며     1. 조건 체크         case 1. 시작일이 오늘이면             1. 상품 상태를 기획전 데이터로 업데이트         case 2. 종료일이 오늘인면             1. 상품 상태를 시작일 이전 히스토리 데이터로 변경     2. 상품 히스토리를 남긴다. 4. 커밋한다. 저는 사고의 결과를 주석 형태로 작성하고, 순번을 달아서 진행을 정리합니다. 다음으로 정리된 내용을 검증하고, 주석을 중간 크기로 작성해 쪼갭니다. 그 밑에 코드를 작성하면 두 마리 토끼를 잡을 수 있습니다. 중간 크기의 주석은 프로그램의 진행 단위를 나눠서 보기 편하고, 단계별로 검증할 때에도 유용합니다.<?php /***************************************************** * 1. 대상 기획전을 찾는다. *     - 기획전 시작일이 오늘인가? 종료일이 오늘인가? **************************************************** */ // 세부 로직은 생략함 $list = getPlainedPromotionList(); /*****************************************************  * 2. 트랜잭션을 연다. **************************************************** */ beginTransaction(); /*****************************************************  * 3. 대상 기획전 건수 만큼 루프를 돌며 *****************************************************/ foreach ($list as $obj) { /*****************************************************  *        case 1. 시작일이 오늘이면  *            1. 상품 상태를 기획전 데이터로 업데이트 *****************************************************/     if ($obj['startDate'] == $today) {         updateProductDistRate($obj['productNo'], $obj['distRate']);    } /*****************************************************  *        case 2. 종료일이 오늘인면  *            1. 상품 상태를 시작일 이전 히스토리 데이터로 변경  *****************************************************/     if ($obj['endDate'] == $today) {        recoveryProductFromHistory($obj['productNo']);    } /*****************************************************  *    2. 상품 히스토리를 남긴다. *****************************************************/     addProductHistory($obj['productNo']); } /*****************************************************  * 4. 커밋한다. *****************************************************/ commit(); Conclusion영화 <인터스텔라(Interstellar, 2014)>의 주인공 쿠퍼(매튜 맥커너히)가 책장 너머 다른 차원에서 과거의 자신에게 신호를 보냈던 명장면이 생각납니다. “ STAY” 그의 메시지는 분명 후회의 몸부림이었을 겁니다. 마찬가지로 당신이 조건문을 부정적으로 만들고 있다면 잠시 키보드에서 손을 떼는 게 좋습니다. 다른 차원의 자신이 어딘가에서 메시지를 보내고 있을지도 모르니까요. “STOP….” 참고 1) 1초에 수백억 번 이상 연산이 가능한 컴퓨터에선 10회와 2회의 차이가 거의 없다. 2) 동료에게 정리한 문장을 이해시킬 수 있다면 정리가 잘 되었을 확률이 높다. 글천보성 팀장 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 1565

VCNC가 Hadoop대신 Spark를 선택한 이유

요즘은 데이터 분석이 스타트업, 대기업 가릴 것 없이 유행입니다. VCNC도 비트윈 출시 때부터 지금까지 데이터 분석을 해오고 있고, 데이터 기반의 의사결정을 내리고 있습니다.데이터 분석을 하는데 처음부터 복잡한 기술이 필요한 것은 아닙니다. Flurry, Google Analytics 등의 훌륭한 무료 툴들이 있습니다. 하지만 이러한 범용 툴에서 제공하는 것 이상의 특수하고 자세한 분석을 하고 싶을 때 직접 많은 데이터를 다루는 빅데이터 분석을 하게 됩니다. VCNC에서도 비트윈의 복잡한 회원 가입 프로세스나, 채팅, 모멘츠 등 다양한 기능에 대해 심층적인 분석을 위해 직접 데이터를 분석하고 있습니다.빅데이터 분석 기술큰 데이터를 다룰 때 가장 많이 쓰는 기술은 Hadoop MapReduce와 연관 기술인 Hive입니다. 구글의 논문으로부터 영감을 받아 이를 구현한 오픈소스 프로젝트인 Hadoop은 클러스터 컴퓨팅 프레임웍으로 비싼 슈퍼컴퓨터를 사지 않아도, 컴퓨터를 여러 대 연결하면 대수에 따라서 데이터 처리 성능이 스케일되는 기술입니다. 세상에 나온지 10년이 넘었지만 아직도 잘 쓰이고 있으며 데이터가 많아지고 컴퓨터가 저렴해지면서 점점 더 많이 쓰이고 있습니다. VCNC도 작년까지는 데이터 분석을 하는데 MapReduce를 많이 사용했습니다.주스를 만드는 과정에 빗대어 MapReduce를 설명한 그림. 함수형 프로그래밍의 기본 개념인 Map, Reduce라는 프레임을 활용하여 여러 가지 문제를 병렬적으로 처리할 수 있다. MapReduce slideshare 참조MapReduce는 슈퍼컴퓨터 없이도 저렴한 서버를 여러 대 연결하여 빅데이터 분석을 가능하게 해 준 혁신적인 기술이지만 10년이 지나니 여러 가지 단점들이 보이게 되었습니다. 우선 과도하게 복잡한 코드를 짜야합니다. 아래는 간단한 Word Count 예제를 MapReduce로 구현한 것인데 매우 어렵고 복잡합니다.MapReduce로 단어 갯수를 카운트하는 간단한 예제 (Java). 많은 코드를 작성해야 한다.이의 대안으로 SQL을 MapReduce로 변환해주는 Hive 프로젝트가 있어 많은 사람이 잘 사용하고 있지만, 쿼리를 최적화하기가 어렵고 속도가 더 느려지는 경우가 많다는 어려움이 있습니다.MapReduce의 대안으로 최근 아주 뜨거운 기술이 있는데 바로 Apache Spark입니다. Spark는 Hadoop MapReduce와 비슷한 목적을 해결하기 위한 클러스터 컴퓨팅 프레임웍으로, 메모리를 활용한 아주 빠른 데이터 처리가 특징입니다. 또한, 함수형 프로그래밍이 가능한 언어인 Scala를 사용하여 코드가 매우 간단하며, interactive shell을 사용할 수 있습니다.Spark으로 단어 개수를 카운트하는 간단한 예제 (Scala). MapReduce에 비해 훨씬 간단하다.Spark과 MapReduce의 성능 비교. I/O intensive 한 작업은 성능이 극적으로 향상되며, CPU intensive 한 작업의 경우에도 효율이 더 높다. (자료: RDD 논문)Apache Spark는 미국이나 중국에서는 현재 Hadoop을 대체할만한 기술로 급부상하고 있으며, 국내에도 최신 기술에 발 빠른 사람들은 이미 사용하고 있거나, 관심을 갖고 있습니다. 성능이 좋고 사용하기 쉬울 뿐 아니라, 범용으로 사용할 수 있는 프레임웍이기에 앞으로 더 여러 분야에서 많이 사용하게 될 것입니다. 아직 Spark를 접해보지 못하신 분들은 한번 시간을 내어 살펴보시길 추천합니다.기존의 데이터 분석 시스템 아키텍처기존의 데이터 분석 시스템 아키텍처기존의 시스템은 비용을 줄이기 위해 머신들을 사무실 구석에 놓고 직접 관리했으며, AWS S3 Tokyo Region에 있는 로그를 다운받아 따로 저장한 뒤, MapReduce로 계산을 하고 dashboard를 위한 사이트를 따로 제작하여 운영하고 있었습니다.이러한 시스템은 빅데이터 분석을 할 수 있다는 것 외에는 불편한 점이 많았습니다. 자주 고장 나는 하드웨어를 수리하느라 바빴고, 충분히 많은 머신을 확보할 여유가 없었기 때문에 분석 시간도 아주 오래 걸렸습니다. 그리고 분석부터 시각화까지 과정이 복잡하였기 때문에 간단한 것이라도 구현하려면 시간과 노력이 많이 들었습니다.Spark과 Zeppelin을 만나다이때 저희의 관심을 끈 것이 바로 Apache Spark입니다. MapReduce에 비해 성능과 인터페이스가 월등히 좋은 데다가 0.x 버전과는 달리 1.0 버전에서 많은 문제가 해결되면서 안정적으로 운영할 수 있어 비트윈 데이터 분석팀에서는 Spark 도입을 결정했습니다.Apache Zeppelin은 국내에서 주도하고 있는 오픈소스 프로젝트로써, Spark를 훨씬 더 편하고 강력하게 사용할 수 있게 해주는 도구입니다. 주요한 역할은 노트북 툴, 즉 shell에서 사용할 코드를 기록하고 재실행할 수 있도록 관리해주는 역할과 코드나 쿼리의 실행 결과를 차트나 표 등으로 시각화해서 보여주는 역할입니다. VCNC에서는 Zeppelin의 초기 버전부터 관심을 가지고 살펴보다가, Apache Spark를 엔진으로 사용하도록 바뀐 이후에 활용성이 대폭 좋아졌다고 판단하여 데이터 분석에 Zeppelin을 도입하여 사용하고 있고, 개발에도 참여하고 있습니다.또한, 위에서 언급한 하드웨어 관리에 드는 노력을 줄이기 위해서 전적으로 클라우드를 사용하기로 함에 따라서1 아래와 같은 새로운 구조를 가지게 되었습니다.새로운 데이터 분석 시스템 아키텍처새로운 데이터 분석 시스템 아키텍처새로운 데이터 분석 시스템은 아키텍처라고 하기에 다소 부끄러울 정도로 간단합니다. 애초에 전체 시스템 구성을 간단하게 만드는 것에 중점을 두었기 때문입니다. 대략적인 구성과 활용법은 아래와 같습니다.모든 서버는 AWS 클라우드를 이용수 대의 Zeppelin 서버, 수 대의 Spark 서버운영Spark 서버는 메모리가 중요하므로 EC2 R3 instance 사용로그는 별도로 저장하지 않고 서비스 서버에서 S3로 업로드하는 로그를 곧바로 가져와서 분석함중간 결과 저장도 별도의 데이터베이스를 두지 않고 S3에 파일로 저장Zeppelin의 scheduler 기능을 이용하여 daily batch 작업 수행별도의 dashboard용 Zeppelin을 통해 중간 결과를 시각화하며 팀에 결과 공유이렇게 간단한 구조이긴 하지만 Apache Spark와 Apache Zeppelin을 활용한 이 시스템의 능력은 기존 시스템보다 더 강력하고, 더 다양한 일을 더 빠르게 해낼 수 있습니다.기존현재일일 배치 분석코드 작성 및 관리가 어려움Zeppelin의 Schedule 기능을 통해 수행 Interactive shell로 쉽게 데이터를 탐험 오류가 생긴 경우에 shell을 통해 손쉽게 원인 발견 및 수정 가능Ad-hoc(즉석) 분석복잡하고 많은 코드를 짜야 함분석 작업에 수 일 소요Interactive shell 환경에서 즉시 분석 수행 가능Dashboard별도의 사이트를 제작하여 운영 관리가 어렵고 오류 대응 힘듦Zeppelin report mode 사용해서 제작 코드가 바로 시각화되므로 제작 및 관리 수월성능일일 배치 분석에 약 8시간 소요메모리를 활용하여 동일 작업에 약 1시간 소요이렇게 시스템을 재구성하는 작업이 간단치는 않았습니다. 이전 시스템을 계속 부분적으로 운영하면서 점진적으로 재구성 작업을 하였는데 대부분 시스템을 옮기는데 약 1개월 정도가 걸렸습니다. 그리고 기존 시스템을 완전히 대체하는 작업은 약 6개월 후에 종료되었는데, 이는 분석 성능이 크게 중요하지 않은 부분들에 대해서는 시간을 두고 여유 있게 작업했기 때문이었습니다.Spark와 Spark SQL을 활용하여 원하는 데이터를 즉석에서 뽑아내고 공유하는 예제Zeppelin을 활용하여 인기 스티커를 조회하는 dashboard 만드는 예제결론비트윈 데이터 분석팀은 수개월에 걸쳐 데이터 분석 시스템을 전부 재구성하였습니다. 중점을 둔 부분은빠르고 효율적이며 범용성이 있는 Apache Spark, Apache Zeppelin을 활용하는 것최대한 시스템을 간단하게 구성하여 관리 포인트를 줄이는 것두 가지였고, 그 결과는 매우 성공적이었습니다.우선 데이터 분석가 입장에서도 관리해야 할 포인트가 적어져 부담이 덜하고, 이에 따라 Ad-hoc분석을 수행할 수 있는 시간도 늘어나 여러 가지 데이터 분석 결과를 필요로 하는 다른 팀들의 만족도가 높아졌습니다. 새로운 기술을 사용해 본 경험을 글로 써서 공유하고, 오픈소스 커뮤니티에 기여할 수 있는 시간과 기회도 생겼기 때문에 개발자로서 보람을 느끼고 있습니다.물론 새롭게 구성한 시스템이 장점만 있는 것은 아닙니다. 새로운 기술들로 시스템을 구성하다 보니 세세한 기능들이 아쉬울 때도 있고, 안정성도 더 좋아져야 한다고 느낍니다. 대부분 오픈소스 프로젝트이므로, 이러한 부분은 적극적으로 기여하여 개선하여 나갈 계획입니다.비트윈 팀에서는 더 좋은 개발환경, 분석환경을 위해 노력하고 있으며 이는 더 좋은 서비스를 만들기 위한 중요한 기반이 된다고 생각합니다. 저희는 항상 좋은 개발자를 모시고 있다는 광고와 함께 글을 마칩니다.연관 자료: AWS 한국 유저 그룹 - Spark + S3 + R3 을 이용한 데이터 분석 시스템 만들기↩저희는 언제나 타다 및 비트윈 서비스를 함께 만들며 기술적인 문제를 함께 풀어나갈 능력있는 개발자를 모시고 있습니다. 언제든 부담없이 [email protected]로 이메일을 주시기 바랍니다!
조회수 1814

스켈티인터뷰 / 스켈터랩스의 잡학다이너마이트 변규홍 님을 만나보세요:)

Editor. 스켈터랩스에서는 배경이 모두 다른 다양한 멤버들이 함께 모여 최고의 머신 인텔리전스 개발을 향해 힘껏 나아가고 있습니다. 스켈터랩스의 식구들, Skeltie를 소개하는 시간을 통해 우리의 일상과 혁신을 만들어가는 과정을 들어보세요! 스켈터랩스의 잡학다이너마이트 변규홍 님을 만나보세요:)PART1. About Skelter Labs사진1. 스켈터랩스의 소프트웨어 엔지니어, 변규홍 님Q. 간단한 자기소개를 부탁한다.A. 이름은 변규홍. 스켈터랩스에서 소프트웨어 엔지니어로 일하며, 컴퓨터에게 열심히 한국어를 가르치고 함께 배우고 있다. 대충 20년 전부터 컴퓨터 공부를 시작해서 컴퓨터 관련된 일이라면 사족을 못쓰는 덕후이기도 하다.Q. 현재 스켈터랩스에서 어떤 업무를 맡고있는가.A. 스켈터랩스의 인공지능 대화 엔진 개발 팀인 헤르메스(Hermes)에서 흔히 ‘챗봇’이라 부르는 인공지능 대화 엔진을 만들고 있다. 우리가 만드는 인공지능 대화 엔진은 ‘챗봇을 만들고자 하는 사람들이 누구나 쉽게 챗봇을 만들도록 돕는 편리한 사용'을 목표로 한다. 때문에 비개발자도 이해하기 쉽도록 효율적이고 간편한 UI와 구조로 개발하고 있다. 거기서 나는 어떻게 하면 컴퓨터가 사람이 하는 말을 더 잘 알아듣고 잘 대답할 수 있는지 연구하고 있다. 어떤 처리를 해야하는지, 언어의 어떤 패턴을 인식하는지 등 ‘자연어 처리(Natural Language Processing,NLP)’ 혹은 자연언어처리라고 불리는 기술 전반에 대한 연구를 진행하고 있다.Q. 자연어 처리라는 부분이 생소하다. 언어의 분석이나 처리에 대한 얘기를 더 해줄 수 있나.A. 챗봇 위주로 설명해 보자. 우리가 한국어 문장을 컴퓨터나 스마트폰에 입력할 때, 특히 채팅할 때는 문장의 변화가 심한 편이다. 띄어쓰기를 실수할 수도 있고 급식체같은 축약어를 사용하기도 한다. 같은 의도를 담은 문장이 아주 다르게 표현되는가 하면, 비슷한 문장이 어순이나 표현 한 두 가지만 바뀌어도 전혀 다른 뜻이 되기도 한다. 이러한 인간의 언어를 컴퓨터가 잘 알아들을 수 있도록 분석하고 처리하는 것이다. 입력된 문장에서 어떤 부분이 명사고 어떤 부분이 동사인지를 찾거나, 문장 속에서 어떤 형태소에 집중해야 하는지 분석한다. 그리고 은행 계좌나 전화번호처럼 규칙에 맞는 숫자가 다양하게 입력될 수 있는 경우를 찾아내기도 한다. 이런 과정을 거쳐 사람이 어떤 의도를 갖고 입력한 문장인지, 어떤 정보가 담겨있는지 식별해낼 수 있다.Q. 들어보니 기술에 대한 지식뿐만 아니라 언어학에 대한 조예가 필요한 분야로 보인다.A. 맞다. 이 분야를 전산학(컴퓨터공학)에서는 ‘자연언어처리’라고 하고 언어학에서는 ‘전산언어학(Computational Linguistics)’ 혹은 ‘계산언어학’이라고 한다. 학제 간 학문으로서의 성격이 강한 분야다. 초창기에는 언어학자들이 찾아낸 인간 언어의 구조, 규칙을 컴퓨터공학자 / 전산학자들이 프로그램으로 구현하는 연구가 많았다. 그러다가 애초의 예상보다 인간의 언어 구조가 훨씬 더 복잡하다는 것을 인식한 이후부터는 인간의 언어에서 규칙성을 찾는 과정도 통계적 방법 등을 통해 컴퓨터의 힘을 빌리게 되었다. 최근에는 요즘 화두인 머신러닝 기법을 적극적으로 적용하면서 연구 트렌드가 조금씩 바뀌고 있다. 다양한 규칙에 따라 문장을 분석하기보다, 빅데이터로 정리된 방대한 언어생활 자료를 컴퓨터 스스로 학습하여 문장 속에서 필요한 정보를 찾아내는 식으로의 전환이랄까. 하지만 여전히 좀 더 좋은 결과물을 내려면 언어학에 대한 지식과 규칙성에서 찾아낸 정보들이 필요한 것도 사실이다. 그래서 스켈터랩스에서는 규칙 기반 기법들과 머신러닝 기법 모두를 하이브리드 형태로 결합하여 대화 엔진을 개발하고 있다.Q. 아무리 다양한 형태로 기법을 결합하여 사용하더라도, 엔지니어가 언어학에 대해 연구하기는 쉽지 않아 보인다. 언어학을 별도로 공부하거나 혹은 언어학에 대한 관심을 이전부터 가지고 있었는지.A. 언어학이라기보다는 사실 나는 대학교에서 문학 동아리 활동을 오랫동안 했다. 자연스럽게 다양한 활동을 통해서 문학에 대한 얘기를 하다 보니 언어에 대한 관심도 꽤 높았던 것 같다. 무엇보다 구글코리아의 번역기 개발팀에서 인턴을 하며, 컴퓨터로 인간의 언어를 다루는 것이 굉장히 흥미롭다고 생각했고 꾸준히 관심을 이어왔다. Q. 구글 코리아 인턴 경험이 규홍님에게 여러모로 지대한 영향을 끼친 것으로 알고 있다. 그 얘기를 듣고 싶다.A. 대학에 처음 입학했을 때, 사실 실망감이 더 컸다. 합리적인 의사소통은 막혀있었고, 당시 학교의 학사제도 개편으로 인해 여러모로 시끄러운 상황이었다. 그러던 차에 마침 학교에 구글코리아에서 캠퍼스 리쿠르팅을 왔는데, 선배 중 한 명이 ‘왜 구글은 한국에서 인턴을 채용하지 않습니까' 라고 꽤나 당돌한 질문을 던졌다. 그렇게 구글 코리아 인턴 채용이 열려 면접 기회를 얻게 되었다. 당시 내 이력서에는 대학교 입학 후의 경력이라고는 연극동아리 공연 이력이 전부였기 때문에 일종의 두려움도 컸다. 하지만 일본어로 된 만화책을 컴퓨터에 넣으면 한국어로 번역된 만화책이 튀어나오게 하고, 컴파일(COMPILE) 사의 게임 중 미처 한국어로 번역되지 못한 게임들을 컴퓨터가 알아서 번역해 즐길 수 있게 하는, 그런 컴퓨터 프로그램을 직접 만들고 싶다는 꿈이 더 컸다. 마침 나의 면접관들도 구글 코리아 번역기 개발팀 분들이었다. 그렇게 구글 코리아 번역기 개발팀 인턴으로 입사하게 되었고, 그때의 경험이 나의 꿈의 실현 가능성에 대한 일종의 확신을 주었다.Q. 스켈터랩스에는 어떻게 입사하게 되었나A. 인턴 할 당시의 구글 코리아 사장이 지금 스켈터랩스 창업자, 조원규 대표님이다. 그리고 구글 코리아 면접관이었던 분이 우리 팀의 테크 리더(Tech Leader)를 맡고 있는 이충식 님이기도 하다. 작년 충식 님으로부터 어려운 문제를 풀어야 하는데 같이 한번 풀어보자는 연락을 받았다. 그 문제가 너무 어려울 것 같아서 답장을 망설이고 있었다. 그러다 이전 직장에 대한 염증과 새로운 일에 대한 호기심 등의 마음으로 충식님을 다시 만나 뵈니, 스켈터랩스에서 내가 어렸을 적 꿈꾸던 챗봇을 만들고 계셨다.Q.  스켈터랩스에서의 업무는 이전에 일했던 혹은 알고 있는 다른 개발자의 업무랑 어떻게 다른가. A. 사실 인공지능을 기반으로 한 스타트업에는 뛰어난 사람들이 많은 것 같다. 그러나 스켈터랩스가 다른 회사와 다른 점은 ‘내 동료가 누구인가'에 대한 인식의 범위가 조금 더 넓다는 점이다. 가령 디자이너는 디자이너끼리, 기획자들을 기획자끼리만 협력하고 부서에 따른 책임이나 업무 범위에 대해서 선을 긋는 문화가 흔히 있지 않나. 어떤 직장들은 수직적인 위계 구조를 강요하고 모든 걸 서류로 보고하게 만들기 때문에 일의 효율이 떨어지기도 한다. 그러나 스켈터랩스는 팀 간에, 직무 간에 서로의 업무 영역을 자로 재듯 규정하지 않고 넘나들며, 좀 더 활발한 소통을 추구한다. 덕분에 ‘하나의 공동체'라는 인식을 자연스럽게 가질 수 있다. 서로와 함께 일한다는 것에 대해 우리 스스로 가지는 자긍심도 대단하다. 사내에는 지인을 신규 입사자로 추천하는 채용 제도가 있는데, 그간 내가 일해왔던 회사 중 우리 회사만큼 열심히 지인들에게 추천하는 회사도 없었다. 사실 내가 일하는 회사가 별로면 친구에게 추천도 못 하지 않겠나. 그만큼 서로 만족하고, 자부심을 가지고 일한다는 것을 방증하는 면모인 것 같다.또한 스켈터랩스는 불필요한 서류 업무를 배제하는 대신, 아주 엄격한 코드 리뷰 시스템을 가지고 있다. 내가 과거에 근무했던 회사들은 많은 경우 상대적으로 지금 당장 작동하는 코드를 만들어 내는 것에 집중했다. 물론 이러한 방식이 때로는 실용적이다. 그러나 기능이 잘 작동되는지만 살피다 보니, 숨겨진 버그(Software Bug)가 남겨지고 이것이 뒤늦게 발견되어 더 큰 문제를 일으키기도 했다. 때로는 버그의 존재를 코드 작성자만이 알고 있기도 했다. 이렇듯 단기간 눈앞의 기능에만 집중하다가 코드의 품질이 저해되는 방식으로 개발이 진행되어 언젠가는 다시 수정해야 하는 일거리가 남겨지는 것을 ‘기술 부채(Technical Debt)’라고 부른다. 스켈터랩스의 코드 리뷰 문화는 사소한 영역까지 기술 부채를 남기지 않는다. 궁극적으로는 짧은 기간 완성도 높은 프로그램을 만들 수 있게 해주는 문화다. 엄격한 코드 리뷰가 가능한 것은 스켈터랩스의 개발자 역량이 높기 때문이기도 하다. 개발자들이 모두 기술에 대한 근본적인 이해와 최신 기술에 대한 섭렵을 두루 갖추었기에 타인이 작성한 코드도 바로 이해할 수 있다. 수준 높은 동료와 함께 일하며 피드백 받고 성장할 수 있다는 것은 회사의 굉장한 강점이라고 생각한다.사진2. 규홍 님과 다른 팀원 간의 코드 리뷰 모습.Q. 코드 리뷰 문화가 유익하기도 하지만, 일종의 압박감도 있을 것 같다. A. 압박감으로 여겨본 적은 없다. 한국 사회에서 개발자의 커리어에 대한 얘기를 나누다 보면 자연스럽게 ‘회사 일을 하다 보니 공부할 시간이 없어서 최신 기술을 알지 못해 뒤처진다.'라는 볼멘소리가 나온다. 그러나 스켈터랩스에서는 개발자 모두가 엄격한 코드리뷰를 거치는 과정에서 자연스럽게 더 나은 성능의 코드, 동료가 더 잘 이해할 수 있는 코드, 예상치 못한 예외 상황을 고려하는 코드를 작성하는 법을 실시간으로 배우게 되고, 때로는 그 과정에서 자연스럽게 코드 리뷰자가 제안하는 최신 기술에 대해 공부하고 습득하며 실력을 늘려나간다. 덕분에 코드 리뷰를 마치고 나면, 다음에 어떻게 해야 개선된 코드를 짤 수 있을지에 더 집중할 수 있고 실제로도 더 나은 코드를 작성할 수 있게 된다.물론 이런 문화가 신규 입사자로서는 다소 답답할 수 있을 것 같다. 나 또한 초반에는 ‘굳이 이런 디테일까지 다 잡아가며 이렇게 리뷰를 남겨야 할까'라는 생각을 해본 적도 있다. 그러나 스켈터랩스와 함께하는 시간이 점점 길어질수록, 꼼꼼한 리뷰로 기술 부채를 최소화하는 것이 팀 전체에도, 나의 성장에도 도움이 된다는 걸 느낀다.Q. 아무리 뛰어난 개발자가 있더라도 코드를 작성하는 사람은 한 명인데, 이를 함께 리뷰하다보면 작성된 코드를 이해하지 못하는 경우가 발생하지는 않나.A. 물론 그럴 수 있다. 때문에 스켈터랩스에서는 코드의 공동 소유, 공동 이해 개념을 깊이 이해하고, 잘 지킬 수 있게 만든다. 나만 이해할 수 있는 코드를 작성하면 장기적으로 다른 개발자들의 수정과 응용이 어려워진다. 그래서 스켈터랩스에서는 각 프로그래밍 언어별로 코딩 스타일 가이드를 준수할 것을 권장하고, 코드 리뷰 이전에도 가이드 준수 여부를 점검하는 도구를 활용하고 있다.Q. 스켈터랩스를 자랑한다면.A. 스켈터랩스는 아직 성장 중인, 그래서 ‘함께 만들어 갈 여지가 많은 회사'다. 나는 개인적으로 대기업부터 창업 초창기 단계의 스타트업까지 다양한 회사를 경험했는데, 이러한 과정에서 구성원 한 명 한 명이 회사의 문화와 기술적 원칙을 만들어가는데 얼마나 큰 영향을 주는지를 느꼈다. 스켈터랩스는 다양한 배경을 가진 개발자와 서로 영감을 주고받으며 함께 성장해가는 곳이다. 개발자 직군의 동료들과 비개발자 직군의 동료들이 끊임없이 소통하며 시행착오와 함께 점점 더 나은 기업문화를 만들어가고 있다. 그리고 실제로 이런 문화가 완성도 높은 프로그램을 만드는 데에 긍정적인 기여를 하고 있고, 현재는 성공 경험을 조금씩 안겨주고 있는 단계다. 역량 있는 인재들과 최신의 기술을 활용하여 새로운 결과물을 창출하는 것에 관심 있는 이들이라면 입사를 추천하고 싶다.#스켈터랩스 #사무실풍경 #업무환경 #사내복지 #기업문화 #개발팀 #팀원인터뷰 #팀원소개 #팀원자랑
조회수 2216

스포카 서버의 구조

안녕하세요. 스포카 개발팀에서 서버 관련 개발 업무를 담당하고 있는 문성원입니다. 오늘은 스포카 서버의 구조와 사용된 기술들에 대해서 함께 살펴보겠습니다.스택이란?먼저 스택(Stack)이란 용어에 대해서 함께 생각해보죠. 컴퓨터 과학을 공부하신 분들이라면 선입후출(FILO)이나 스택 오버플로우(Stack Overflow)등의 개념으로 익숙하실만한 용어기도 합니다. 그런데 서버 구조를 설명한다면서 왠 스택이냐구요? 다행히(?)도 지금부터 살펴 볼 스택은 솔루션 스택(Solution Stack)입니다. 스포카 서버라는 큰 솔루션이 원활히 동작하기 위해서 쓰이고 있는 각종 서브 시스템과 컴포넌트들의 묶음을 이야기하는 것으로 바꿔말하자면 이 글에서 다룰 기술 이야기는 모두 이 스택에 관한 이야기입니다.2011년 12월 현재 스포카 서버를 구성하고 있는 스택은 다음과 같습니다.DotcloudLinux 2.6.38.2nginx 0.8.53uwsgi 0.9.8.5Python 2.6.5Redis 2.2.2Celery 2.2.7Amazon Relational Database ServiceMySQL 5.5.12Amazon Simple Storage ServiceDotcloudDotcloud는 지금부터 설명드릴 스택을 묶어서 제공해주는 PaaS(Platform as a Service)의 일종입니다. Amazon Elastic Cloud Computing(Amazon EC2) 기반으로 동작하며 거기에 더해 손쉬운 확장과 배포가 장점입니다. 스포카 서버는 데이터베이스(Amazon RDS)와 업로드되는 데이터(Amazon S3) 이외의 모든 서비스를 Dotcloud를 통하여 제공하고 있습니다.nginx, uwsgi. 그리고 WSGI기본적으로 스포카 서버는 HTTP 형식의 요청을 받아 응답을 돌려주는 웹 어플리케이션입니다. 이러한 처리는 1차적으로 nginx를 통해 이뤄지는데, 이 중 서버사이드에서 처리가 필요한 경우에는 uwsgi라는 데몬이 이 처리를 담당합니다. (구버젼의 Apache Tomcat을 사용하시던 Java개발자분들은 Apache Tomcat과 Apache httpd와의 관계를 떠올리시면 편합니다.)이 경우 uwsgi는 일종의 어플리케이션 컨테이너(Application Container)로 동작하게 됩니다. 적재한 어플리케이션을 실행만 시켜주는 역할이죠. 이러한 uwsgi에 적재할 어플리케이션(스포카 서버)에는 일종의 규격이 존재하는데, 이걸 WSGI라고 합니다.(정확히는 WSGI에 의해 정의된 어플리케이션을 돌릴 수 있게 설계된 컨테이너가 uwsgi라고 봐야겠지만요.) WSGI는 Python표준(PEP-033)으로 HTTP를 통해 요청을 받아 응답하는 어플리케이션에 대한 명세로 이러한 명세를 만족시키는 클래스나 함수, (__call__을 통해 부를 수 있는)객체를 WSGI 어플리케이션이라고 합니다.정리하자면 스포카 서버는 WSGI에 맞게 작성된 프로그램을 nginx와 uwsgi를 통해 운용하여 요청을 처리하는 웹 어플리케이션이라고 할 수 있습니다.RedisRedis란 키-값(Key-Value) 저장 서버로 확장이 용이하며 속도가 우수합니다. 스포카 서버에선 이를 내부적인 임시 데이터 관리와 Celery의 작업(Task) 분배에 사용하고 있습니다.CeleryCelery는 Python으로 작성된 비동기 작업 큐(Asynchronous task queue/job queue)입니다. 앞서 소개한 작업(Task)를 브로커(Broker, 스포카 서버는 Redis를 사용)를 통해 전달하면 하나 이상의 워커(Worker)가 이를 처리하는 구조입니다. 포인트 적립-공유에 따른 분배처리, 포스팅 기능, 페이스북/트위터 공유등의 비동기 처리가 필요한 작업을 Celery에 위임하여 처리하고 있습니다.Amazon Relational Database Service대부분의 웹 어플리케이션과 마찬가지로 스포카 서버는 영속적으로 저장되어야하는 정보(회원 목록, 구매 내역)들을 디스크 기반의 데이터베이스(Database)에 저장합니다. Amazon Relational Database Service(Amazon RDS)는 Amazon EC2를 기반으로 그러한 데이터베이스를 간편하게 관리(모니터링, 백업, 접근제어)할 수 있게 도와주는 웹서비스입니다. Oracle과 MySQL을 지원하는데 스포카 서버는 그 중 MySQL을 사용하고 있습니다.Amazon Simple Storage ServiceAmazon Simple Storage Service(Amazon S3)는 Amazon RDS와 마찬가지로 Amazon EC2를 기반으로 한 데이터 저장 관리 서비스입니다. 스포카 서버에 업로드 되는 사진이나 문서등의 파일들을 통합하여 관리하여 서버의 인스턴스를 늘려 확장하는 경우에도 문제없이 대처할 수 있도록 하는 것이 주 목적입니다.#스포카 #스택 #개발 #개발자 #개발팀 #인사이트 #조언 #스킬스택 #스택설명
조회수 1740

HBase 설정 최적화하기

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

Good Developer 3 | 나쁜 개발자의 11가지 습관

세상에 나쁜 개발자는 없다. 나쁜 개발 습관만 있을 뿐나쁜 개발자란 누구를 지칭하는 것일까? 코드가 별로인 개발자? 커뮤니케이션이 안되는 개발자? 나쁜 개발자로 지칭될 수 있는 사람들은 굉장히 많다. 하지만, 세상에는 나쁜 개발자는 없다고 생각한다. 단지, 나쁜 개발 습관만 존재할 뿐. 즉, 누구든지 나쁜 습관을 버리고 좋은 습관을 갖는다면 언제든지 좋은 개발자가 될 수 있다는 것이다. 좋은 개발자, 나쁜 개발자. 이것은 칭호가 아니라 속성일 뿐이다. 언제든지 바뀔 수 있는 속성 말이다.이것이 속성인 이유는 누구든지 좋은 개발자와 나쁜 개발자의 속성들을 가지고 있기 때문이다. 단지 그 속성의 비율의 차이가 그 사람이 어떤 개발자인지 결정할 뿐이다. 흔히, 좋은 개발자라고 불리는 사람도 나쁜 개발 습관이 있을 수 있다. 또, 나쁜 개발자라고 욕을 먹는 사람도 좋은 개발 습관이 있을 수 있다.우리는 이 글에서 나쁜 개발 습관(혹은 속성)들을 알아보고 왜 그것이 나쁜지 그리고 그것을 어떻게 피하는지에 대해 이야기할 것이다. 좋은 습관이 아니라 나쁜 습관들을 이야기하는 이유가 있다. 좋은 습관은 습득하기 어렵다. 하지만, 나쁜 습관을 버리는 것은 더더욱 어렵다. 나쁜 습관을 피하는 것이 때로는 좋은 개발자가 되기 위한 요건일 수도 있다. 아래의 습관들을 보면서 자신을 진단해 보자.(아래의 습관들 중 습관인 것들도 있고 단순히 사고방식이나 경향인 것들이 있다. 여기서 습관은 사고방식이나 행동의 양식 등 총체적인 행동 방식 등을 의미한다.)습관 1: 코드 리뷰가 없다.지난번에 같이 해보니까 험악만 말만 나오고, 분위기만 안 좋아졌다. 후배들에게 코드 지적받는 것도 자존심 상하고... 그리고 대부분 시니어들이 지적하고 주니어들은 고개만 끄덕이는 자리 아닌가? 코드 리뷰 할 시간에 코드 한 줄이라도 더 짜서 프로젝트 마감일이나 지키는 게 낫지. 솔직히, 프로라면 자기 코드는 자기가 책임져야 하는 거 아닌가?습관 2: 문서화를 하지 않는다.아니 개발할 시간도 부족한데 무슨 문서화인가. 개발자가 개발하는 사람이지 문서 만드는 사람인가? 인수인계받을 사람 오면 직접 알려주면서 일주일이면 끝날 텐데 말이다. 그리고 이때까지 만든 문서들 만들고 나서 본적이나 있나? 그냥 보여주기식 파일이지 뭐.습관 3: 커뮤니케이션 향상에 관심이 없다.지금도 말 잘하고 대화 잘 통하는데 더 향상시킬게 있나? 그리고 개발자의 핵심은 커뮤니케이션이 아니라 코딩인데 말이야. 컴퓨터랑만 잘 소통하면 되지. 어차피 다른 부서에 있는 사람들은 개발 기술에 대해서 잘 알지도 못하고... 커뮤니케이션 스킬은 그런 사람들이 향상시켜야 한다고 생각한다.습관 4: 업무 공유가 되지 않는다. 자신의 일에 대해 알고 있는 사람이 없다. 데드라인 잘 지키고, 주어진 일을 잘 해내면 된다고 생각한다. 보고를 하기 전까지 굳이 보고하지 않고, 동료나 후배들과 업무 공유를 잘 하지 않는다. 어차피 내가 하는 일에 별로 관심도 없는데 공유해봤자 무슨 소용인가?습관 5: 코드의 복붙(복사 후 붙여넣기)가 '일상화'되어 있다.직접 만드는 것보다 이미 만들어진 코드들을 찾아서 Ctrl +c,v하는게 더 빠르고 생산성 있다고 생각한다. 동료 개발자랑 공통 모듈을 만들어 사용할 수 있겠지만 그렇게 하기에는 너무 많은 리소스가 낭비된다고 생각한다. 잘 돌아가기만 하면 되지 않나?습관 6: 자신의 부족한 점을 드러내지 않는다.부족한 점에 대해 동료들과 터놓고 얘기하지 않는다. 괜히 부끄럽고 껄끄럽기도 하고 자신의 부족한 점이 드러나는 것이 두렵다. 동료들이 조언을 해주려고 해도 방어적으로 나오거나 피한다. 동료의 진솔한 피드백이 없으니 한 번 단점을 만들면 끝까지 내 것으로 가져간다.습관 7: 새로운 기술을 익히는데 시간을 투자하지 않는다.세상은 정말 빠르게 변하고 있다. 그리고 그 변화의 중심은 기술이고 기술 중에서도 IT 기술이 정점에 있다고 봐도 무방하다. 새로운 기술은 새로운 기술자들이 익히는 것이라 생각한다. 지금 하고 있는 일만으로도 벅차다. 그리고 지금 쓰는 기술이 시대의 주류인데 쉽게 바뀔까?습관 8: 자신의 개발 환경에서 벗어나지 않는다.개발자 모임이나 개발 커뮤니티에 시간을 쓰는 것은 낭비라고 생각한다. 개발에 대해 새로운 시도를 하지 않는다. 새로운 프레임워크나 협업 툴들이 나와도 기존의 환경을 고집한다. 왜냐하면 지금 개발 환경이 너무 편하고 익숙하니까.습관 9: 자신이 맡은 개발과 관련된 비즈니스를 이해하지 않는다.개발자는 개발에만 신경 쓰면 된다고 생각한다. 지금 개발하고 있는 서비스의 비즈니스적 관점은 생각해 본 적 없다. 어차피 기획자나 마케터, 프로덕트 매니저가 신경 써야 할 일이라고 생각한다. 개발만으로도 바쁜데 그것까지 신경 쓰면 정말 골치 아파진다.습관 10: 개발에 대한 지신만의 장기적인 목표가 없다.어떤 개발자가 되어야 하는지에 대한 목표가 없다. 주어진 프로젝트 외에 자신이 하고 싶은 프로젝트를 하면서 개발을 발전시키지 않는다. 그냥 개발의 메인 스트림을 따라만 간다. 커리어나 다른 생활에 대한 걱정은 종종 하지만, 개발 자체에 대한 고민은 하지 않는다.습관 11: 자신의 나쁜 개발 습관에 관심이 없다.(습관은 아니지만....)내가 나쁜 개발자라고...? 내가 하고 있는 것들이 나쁜 습관들이라고??? 글쎄..... 그냥저냥 잘 하고 있는 거 같은데.... 라고 생각하는 당신! 아무리 좋은 개발자라도 나쁜 습관은 존재하기 마련이다. 좋은 개발자는 좋은 습관들을 가지고 있는 개발자기도 하지만, 나쁜 습관들이 많지 않은 개발자이기도 하다.나쁜 환경은 나쁜 개발자를 만든다.당신이 만약 스스로를 나쁜 개발자라고 생각한다면 아마 '나쁜' 환경에서 개발을 했을 가능성이 크다. 혹은 선배가 나쁜 개발자여서 그 습관을 그대로 보고 배웠다든지, 아니면 좋은 개발자에 대한 고민 없이 흘러가듯 개발을 배웠을 것이다. 예를 들어, 코드 리뷰를 하지 않았던 것은 회사에서 코드 리뷰를 안 했을 가능성이 크다. 혹은 문서화를 안 하는 경우, 그 회사에서 그것에 대해 크게 신경 쓰기 있지 않을 가능성이 크다.Bad developers are not born, but created.위에서도 언급했듯이 나쁜 개발자는 없다. 나쁜 습관들이 있을 뿐. 당신이 지금 위의 습관에서 많은 부분들이 해당된다 하더라도, 그 습관들을 바꾸면 된다. 다른 개발자들에게 있는 좋은 습관들을 보고 배우면서 자신에게 해당되는 나쁜 습관들을 하나씩 바꿔나가는 것이다.환경이 바뀐다고 자신이 바뀌지는 않겠지만, 나쁜 환경이 나쁜 개발자를 만드는 것처럼, 좋은 환경은 좋은 개발자를 만든다. 좋은 환경을 찾아가라! 직장이 그걸 주지 못한다면 다른 곳에서라도 찾아라. 좋은 개발자는 나쁜 습관들을 하나씩 바꿔나갈 때 될 수 있을 것이다. 다음 포스팅에서는 좋은 개발자가 되기 위해 필요한 정보들을 압축적으로 모아 포스팅할 것이다.
조회수 4649

자바스크립트 기초 문법 정리 Part 2 - 객체

지난 Part 1 포스팅에 이어 자바스크립트 기초 문법에 대해 정리해보았습니다. 이번 포스팅에서는 여러 객체와 그 객체에서 제공하는 각 메서드에 대해 정리하였습니다. 다루는 객체의 여러 메서드에 대해 정리하였기 때문에 전 포스팅처럼 간략하지는 않지만 이번 포스팅을 저장해 두고 자바스크립트로 개발하면서 필요할 때마다 참고하여 보기에는 좋을 것 같습니다. 다만, 메서드 사용 예의 코드는 넣지 않았으니 예제 부분이 필요하다면 필히 공식 문서를 참고해주세요. 익히는 것 자체도 공식 문서를 통하여 보는 것이 가장 좋지만 혹여 영어에 취약하신 분이라면 이 포스팅을 참고하는 것도 괜찮을 것 같습니다. :)내장 객체브라우저의 자바스크립트 엔진에 내장된 객체. String/Date/Array/Nath/RegExp Object 등이 있음.날짜 객체 DateDate 객체 생성new Date()new Date(milliseconds)new Date(dateString)new Date(year, month, day, hours, minutes, seconds, milliseconds)Date Get 메서드getDate() - 일 정보를 가져옴.getDay() - 요일 정보를 가져옴. 0(일요일)-6(토요일)getFullYear - 연도 정보를 가져옴. (yyyy)getHours() - 시간 정보를 가져옴.getMilliseconds() - 밀리초 정보를 가져옴. 0-999 (1/1000 초의 단위)getMinutes() - 분 정보를 가져옴.getMonth() - 월 정보를 가져옴. 현재 월에서 -1한 값으로 옴.getSeconds() - 초 정보를 가져옴.getTime() - 1970년 1월 1일부터 경과된 시간을 밀리초로 가져옴.Date Set 메서드setDate() - 일 정보를 설정.setFullYear() - 연도 정보를 설정. 원한다면 월과 일 정보도 설정할 수 있다.setHours() - 시간 정보를 설정.setMillseconds() - 밀리초 정보를 설정.setMinutes() - 분 정보를 설정.setSeconds() - 초 정보를 설정.setTime() - 1970년 1월 1일부터 경과된 시간을 밀리초로 설정.기타 Date 메서드now() - 1970년 1월 1일부터 지금까지의 밀리초를 반환.parse() - 날짜 형태의 문자열을 변환하여 1970년 1월 1일부터 입력한 날짜까지의 밀리초를 반환.toString() - Date 객체를 문자열로 변환.toJSON() - Date 객체를 JSON 데이터로 변환.valueOf() - Date 객체를 밀리초로 반환.숫자 객체 NumberNumber 생성var num = 1;      var num2 = new Number(1);Number 객체의 속성MAX_VALUE - 표현 가능한 가장 큰 수.MIN_VALUE - 표현 가능한 가장 작은 수.POSITIVE_INFINITY - 무한대 수 표기.NEGATIVE_INFINITY - 음의 무한대 수 표기.NaN - 숫자가 아닌 경우 표기.Number 객체 메서드toExponential(n) - 자수 표기법으로 소수점 n자리만큼 문자형 데이터로 반환.toFixed(n) - 소수점 n자리만큼 반올림하여 문자형 데이터로 반환.toPrecision(n) - 유효 숫자 n의 개수만큼 반올림하여 문자형 데이터로 반환.toString() - 숫자형 데이터를 문자형 데이터로 반환.valueOf() - 객체의 원래 값을 반환.parseInt(값) - 데이터를 정수로 변환하여 반환.parseFloat(값) - 데이터를 실수로 변환하여 반환.수학 객체 MathMath 메서드 및 상수Math.abs(숫자) - 숫자의 절댓값을 반환.Math.max(숫자1, 숫자2, 숫자3) - 숫자 중 최댓값을 반환.Math.min(숫자1, 숫자2, 숫자3) - 숫자 중 최솟값을 반환.Math.pow(숫자, 제곱값) - 숫자의 거듭제곱한 값을 반환.Math.random() - 0~1 사이의 난수를 반환.Math.round(숫자) - 소수점 첫째 자리에서 반올림하여 정수를 반환.Math.ceil(숫자) - 소수점 첫째 자리에서 무조건 올림에서 정수를 반환.Math.floor(숫자) - 소수점 첫째 자리에서 무조건 내림해서 정수를 반환.Math.sqrt(숫자) - 숫자의 제곱근 값을 반환.Math.PI - 원주율 상수를 반환.배열 객체 ArrayArray 생성var array = new Array();array[0] = 1;array[1] = 2;var array2 = new Array(1, "temp", true);var array3 = [1, true, "문자열도 가능"];Array 객체의 메서드 및 속성join(연결문자) - 배열 객체에 데이터를 연결 문자 기준으로 1개의 문자형 데이터로 반환.reverse() - 배열 객체에 데이터의 순서를 거꾸로 바꾼 후 반환.sort() - 배열 객체에 데이터를 오름차순으로 정렬.slice(index1, index2) - 배열 객체에 데이터 중 원하는 인덱스 구간만큼 잘라서 배열 객체로 가져옴.splice() - 배열 객체에 지정 데이터를 삭제하고 그 구간에 새 데이터를 삽입할 수 있음.concat() - 2개의 배열 객체를 하나로 결합.pop() - 배열에 저장된 데이터 중 마지막 인덱스에 저장된 데이터 삭제.push(new data) - 배열 객체에 마지막 인덱스에 새 데이터를 삽입.shift() - 배열 객체에 저장된 데이터 중 첫 번째 인덱스에 저장된 데이터를 삭제.unshift(new data) - 배열 객체의 가장 앞의 인덱스에 새 데이터를 삽입.length - 배열에 저장된 총 데이터의 개수를 반환.문자 객체 StringString 생성var str = "hello";      var str2 = new String("hi");String 객체 메서드 및 속성charAt(index) - 문자열에서 인덱스 번호에 해당하는 문자 반환.indexOf("찾을 문자") - 문자열에서 왼쪽부터 찾을 문자와 일치하는 문자를 찾아 최초로 일치하는 문자의 인덱스 번호를 반환. 찾는 문자가 없으면 -1 반환.lastIndexOf("찾을 문자") - indexOf와 동일하나 문자열의 오른쪽부터 찾음.match("찾을 문자") - indexOf와 동일하나 찾는 문자가 없으면 null을 반환.replace("바꿀 문자", "새 문자") - 문자열에서 왼쪽부터 바꿀 문자와 일치하는 문자를 찾아 최초로 찾은 문자를 새 문자로 치환.search("찾을 문자") - 문자열 왼쪽부터 찾을 문자와 일치하는 문자를 찾아 최초로 일치하는 인덱스 번호를 반환.slice(a, b) - a개의 문자를 자르고 b번째 이후에 문자를 자른 후 남은 문자를 반환.substring(a, b) - a 인덱스부터 b 인덱스 이전 구간의 문자를 반환.substr(a, 문자 개수) - 문자열에 a 인덱스부터 지정한 문자 개수만큼 문자열을 반환.split("문자") - 지정한 문자를 기준으로 문자 데이터를 나누어 배열에 저장하여 반환.toLowerCase() - 문자열에서 영문 대문자를 모두 소문자로 바꿈.toUpperCase() - 문자열에서 영문 소문자를 모두 대문자로 바꿈.length - 문자열에서 문자의 개수를 반환.concat("새로운 문자") - 문자열에 새로운 문자열을 결합.charCodeAt("찾을 문자") - 찾을 문자의 아스키 코드 값을 반환.fromCharCode(아스키 코드 값) - 아스키 코드 값에 해당하는 문자를 반환.trim() - 문자의 앞 또는 뒤에 공백 문자열을 삭제.브라우저 객체 모델(BOM)브라우저에 내장된 객체. window 객체브라우저 객체의 최상위 객체.window 객체 메서드open("url 경로", "창 이름", "옵션 설정") - 새 창을 열 때 사용.- open() 메서드 옵션 설정: width/height/left/top/location/status/scrollbars/tollbarsalert("메세지") - 경고 창을 띄움.prompt("질의 내용", "기본 답변") - 질의응답 창을 띄움.confirm("질의 내용") - 확인/취소 창을 띄움.- 확인 클릭시 true 반환, 취소 클릭시 false 반환.moveTo(x 위치값, y 위치값) - 창의 위치를 이동시킬 때 사용.resizeTo(너빗값, 높잇값) - 창의 크기를 변경시킬 때 사용.setInterval("스크립트 실행문", 시간 간격) - 일정 간격으로 반복하여 실행문을 실행시킬 때 사용.clearIntervar(참조 변수) - 참조 변수에 참조되어 있는 setInterval() 삭제.setTimeout("스크립트 실행문", 시간 간격) - 일정 간격으로 한 번만 실행문을 실행시킬 때 사용.clearTimeout(참조 변수) - 참조 변수에 참조되어 있던 setTimeout() 삭제.screen 객체사용자의 모니터 정보를 제공하는 객체.screen 객체 속성width/height/availWidth/availHeight/colorDepth(사용자 모니터가 표현 가능한 컬러 bit)location 객체사용자 브라우저의 주소 창에 url에 대한 정보와 새로 고침 기능을 제공하는 객체.location 객체 속성 및 메서드href - 주소 영역에 참조 주소를 설정하거나 URL 반환.hash - URL의 해시값을 반환.hostname - URL의 호스트 이름을 설정하거나 반환.host - URL의 호스트 이름과 포트 번호를 반환.port - URL의 포트 번호를 반환.protocol - URL의 프로토콜을 반환.search - URL의 쿼리를 반환.reload() - 새로 고침.history 객체사용자가 방문한 사이트 중 이전에 방문한 사이트와 다음 방문한 사이트로 다시 돌아갈 수 있는 속성과 메서드를 제공하는 객체.history 메서드 및 속성back() - 이전 방문한 페이지로 이동.forward() - 다음 방문한 페이지로 이동.go(이동 숫자) - 이동 숫자만큼의 페이지로 이동. 음의 값이면 이전 페이지로 이동.length - 방문 기록에 저장된 목록의 개수 반환.navigator 객체현재 방문자가 사용하는 브라우저 정보와 운영체제의 정보를 제공하는 객체.navigator 속성appCodeName - 방문자의 브라우저 코드명을 반환.appName - 방문자의 브라우저 이름 반환.appVersion - 방문자의 브라우저 버전 정보를 반환.language - 방문자의 브라우저 사용 언어를 반환.product - 방문자의 브라우저 사용 엔진 이름을 반환.platform - 방문자의 브라우저를 실행하는 운영체제를 반환.userAgent - 방문자의 브라우저와 운영체제의 종합 정보를 제공.문자 객체 모델(DOM)HTML 문서의 구조.선택자직접 선택자직접 문서에서 요소를 선택함. (id/class/폼 명/요소 명 등)document.getElementById("아이디 명") - 아이디를 이용해 요소를 선택.document.getElmentsByTagName("요소 명") - 요소의 이름을 이용해 요소를 선택.document.formName.inputName - 폼 요소에 name 속성을 이용해 요소를 선택.인접 관계 선택자직접 선택자를 사용해 선택해 온 문서 객체를 기준으로 가까이에 있는 요소를 선택함. (parentNode/childeNodes 등)parentNode - 선택한 요소의 부모 요소를 선택.childNodes - 선택한 요소의 모든 자식 요소를 선택. 선택한 모든 요소가 저장됨.firstChild - 선택한 요소의 첫 번째 자식 요소만 선택.previousSibling - 선택한 요소의 이전에 오는 형제 요소만 선택.nextSibling - 선택한 요소의 다음에 오는 형제 요소만 선택.문서 객체 이벤트 핸들러 적용하기onclick - 선택한 요소를 클릭했을 때 이벤트 발생.onmousevoer - 선택한 요소에 마우스를 올렸을 때 이벤트 발생.onmouseout - 선택한 요소에 마우스가 벗어났을 때 이벤트 발생.submit - 선택한 폼에 전송이 일어났을 떄 이벤트 발생.버튼document.getElementById("btn").onclick = function() {    alert("welcome");}일단은 참고하는 책을 기준으로하여 정리해보았는데 후에 시간이 될 때마다 공식 문서를 참고하여 번역한다는 생각으로 보다 세부적인 사항을 정리해도 좋을 것 같다는 생각이 드네요. 우선적으로는 빠르게 함수와 이벤트에 대해 배우고 객체에 대한 더 자세한 사항을 정리하도록 하겠습니다. 다음 포스팅은 자바스크립트의 함수와 이벤트에 대해 다룰 예정입니다!참고문헌:Do it! 자바스크립트+제이쿼리 입문 - 정인용JavaScript 튜토리얼 문서 (http://www.w3schools.com/js/default.asp)티스토리 블로그와 동시에 포스팅을 진행하고 있습니다.http://madeitwantit.tistory.com#트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유
조회수 1058

안드로이드 클라이언트 Reflection 극복기 - VCNC Engineering Blog

 비트윈 팀은 비트윈 안드로이드 클라이언트(이하 안드로이드 클라이언트)를 가볍고 반응성 좋은 애플리케이션으로 만들기 위해 노력하고 있습니다. 이 글에서는 간결하고 유지보수하기 쉬운 코드를 작성하기 위해 Reflection을 사용했었고 그로 인해 성능 이슈가 발생했던 것을 소개합니다. 또한 그 과정에서 발생한 Reflection 성능저하를 해결하기 위해 시도했던 여러 방법을 공유하도록 하겠습니다.다양한 형태의 데이터Java를 이용해 서비스를 개발하는 경우 POJO로 서비스에 필요한 다양한 모델 클래스들을 만들어 사용하곤 합니다. 안드로이드 클라이언트 역시 모델을 클래스 정의해 사용하고 있습니다. 하지만 서비스 내에서 데이터는 정의된 클래스 이외에도 다양한 형태로 존재합니다. 안드로이드 클라이언트에서 하나의 데이터는 아래와 같은 형태로 존재합니다.JSON: 비트윈 서비스에서 HTTP API는 JSON 형태로 요청과 응답을 주고 받고 있습니다.Thrift: TCP를 이용한 채팅 API는 Thrift를 이용하여 프로토콜을 정의해 서버와 통신을 합니다.ContentValues: 안드로이드에서는 Database 에 데이터를 저장할 때, 해당 정보는 ContentValues 형태로 변환돼야 합니다.Cursor: Database에 저장된 정보는 Cursor 형태로 접근가능 합니다.POJO: 변수와 Getter/Setter로 구성된 클래스 입니다. 비지니스 로직에서 사용됩니다.코드 전반에서 다양한 형태의 데이터가 주는 혼란을 줄이기 위해 항상 POJO로 변환한 뒤 코드를 작성하기로 했습니다.다양한 데이터를 어떻게 상호 변환할 것 인가?JSON 같은 경우는 Parsing 후 Object로 변환해 주는 라이브러리(Gson, Jackson JSON)가 존재하지만 다른 형태(Thrift, Cursor..)들은 만족스러운 라이브러리가 존재하지 않았습니다. 그렇다고 모든 형태에 대해 변환하는 코드를 직접 작성하면 필요한 경우 아래와 같은 코드를 매번 작성해줘야 합니다. 이와 같이 작성하는 경우 Cursor에서 원하는 데이터를 일일이 가져와야 합니다.@Override public void bindView(View view, Context context, Cursor cursor) { final ViewHolder holder = getViewHolder(view); final String author = cursor.getString("author"); final String content = cursor.getString("content"); final Long timeMills = cursor.getLong("time"); final ReadStatus readStatus = ReadStatus.fromValue(cursor.getString("readStatus")); final CAttachment attachment = JSONUtils.parseAttachment(cursor.getLong("createdTime")); holder.authorTextView.setText(author); holder.contentTextView.setText(content); holder.readStatusView.setReadStatus(readStatus); ... } 하지만 각 형태의 필드명(Key)이 서로 같도록 맞춰주면 각각의 Getter와 Setter를 호출해 형태를 변환해주는 Utility Class를 제작할 수 있습니다.@Override public void bindView(View view, Context context, Cursor cursor) { final ViewHolder holder = getViewHolder(view); Message message = ReflectionUtils.fromCursor(cursor, Message.class); holder.authorTextView.setText(message.getAuthor()); holder.contentTextView.setText(message.getContent()); holder.readStatusView.setReadStatus(message.getReadStatus()); ... } 이런 식으로 코드를 작성하면 이해하기 쉽고, 모델이 변경되는 경우에도 유지보수가 비교적 편하다는 장점이 있습니다. 따라서 필요한 데이터를 POJO로 작성하고 다양한 형태의 데이터를 POJO로 변환하기로 했습니다. 서버로부터 받은 JSON 혹은 Thrift객체는 자동으로 POJO로 변환되고 POJO는 다시 ContentValues 형태로 DB에 저장됩니다. DB에 있는 데이터를 화면에 보여줄때는 Cursor로부터 데이터를 가져와서 POJO로 변환 후 적절한 가공을 하여 View에 보여주게 됩니다.POJO 형태로 여러 데이터 변환필요Reflection 사용과 성능저하처음에는 Reflection을 이용해 여러 데이터를 POJO로 만들거나 POJO를 다른 형태로 변환하도록 구현했습니다. 대상 Class의 newInstance/getMethod/invoke 함수를 이용해 객체 인스턴스를 생성하고 Getter/Setter를 호출하여 값을 세팅하거나 가져오도록 했습니다. 앞서 설명한 ReflectionUtils.fromCursor(cursor, Message.class)를 예를 들면 아래와 같습니다.public T fromCursor(Cursor cursor, Class clazz) { T instance = (T) clazz.newInstance(); for (int i=0; i Reflection을 이용하면 동적으로 Class의 정보(필드, 메서드)를 조회하고 호출할 수 있기 때문에 코드를 손쉽게 작성할 수 있습니다. 하지만 Reflection은 튜토리얼 문서에서 설명된 것처럼 성능저하 문제가 있습니다. 한두 번의 Relfection 호출로 인한 성능저하는 무시할 수 있다고 해도, 필드가 많거나 필드로 Collection을 가진 클래스의 경우에는 수십 번이 넘는 Reflection이 호출될 수 있습니다. 실제로 이 때문에 안드로이드 클라이언트에서 종종 반응성이 떨어지는 경우가 발생했습니다. 특히 CursorAdapter에서 Cursor를 POJO로 변환하는 코드 때문에 ListView에서의 스크롤이 버벅이기도 했습니다. Bytecode 생성 Reflection 성능저하를 해결하려고 처음으로 선택한 방식은 Bytecode 생성입니다. Google Guice 등의 다양한 자바 프로젝트에서도 Bytecode를 생성하는 방식으로 성능 문제를 해결합니다. 다만 안드로이드의 Dalvik VM의 경우 일반적인 JVM의 Bytecode와는 스펙이 다릅니다. 이 때문에 기존의 자바 프로젝트에서 Bytecode 생성에 사용되는 CGLib 같은 라이브러리 대신 Dexmaker를 이용하여야 했습니다. CGLib CGLib는 Bytecode를 직접 생성하는 대신 FastClass, FastMethod 등 펀리한 클래스를 이용할 수 있습니다. FastClass나 FastMethod를 이용하면 내부적으로 알맞게 Bytecode를 만들거나 이미 생성된 Bytecode를 이용해 비교적 빠른 속도로 객체를 만들거나 함수를 호출 할 수 있습니다. public T create() { return (T) fastClazz.newInstance(); } public Object get(Object target) { result = fastMethod.invoke(target, (Object[]) null); } public void set(Object target, Object value) { Object[] params = { value }; fastMethod.invoke(target, params); }  Dexmaker 하지만 Dexmaker는 Bytecode 생성 자체에 초점이 맞춰진 라이브러리라서 FastClass나 FastMethod 같은 편리한 클래스가 존재하지 않습니다. 결국, 다음과 같이 Bytecode 생성하는 코드를 직접 한땀 한땀 작성해야 합니다. public DexMethod generateClasses(Class<?> clazz, String clazzName){ dexMaker.declare(declaringType, ..., Modifier.PUBLIC, TypeId.OBJECT, ...); TypeId<?> targetClassTypeId = TypeId.get(clazz); MethodId invokeId = declaringType.getMethod(TypeId.OBJECT, "invoke", TypeId.OBJECT, TypeId.OBJECT); Code code = dexMaker.declare(invokeId, Modifier.PUBLIC); if (isGetter == true) { Local<Object> insertedInstance = code.getParameter(0, TypeId.OBJECT); Local instance = code.newLocal(targetClassTypeId); Local returnValue = code.newLocal(TypeId.get(method.getReturnType())); Local value = code.newLocal(TypeId.OBJECT); code.cast(instance, insertedInstance); MethodId executeId = ... code.invokeVirtual(executeId, returnValue, instance); code.cast(value, returnValue); code.returnValue(value); } else { ... } // constructor Code constructor = dexMaker.declare(declaringType.getConstructor(), Modifier.PUBLIC); Local<?> thisRef = constructor.getThis(declaringType); constructor.invokeDirect(TypeId.OBJECT.getConstructor(), null, thisRef); constructor.returnVoid(); }  Dexmaker를 이용한 방식을 구현하여 동작까지 확인했으나, 다음과 같은 이유로 실제 적용은 하지 못했습니다. Bytecode를 메모리에 저장하는 경우, 프로세스가 종료된 이후 실행 시 Bytecode를 다시 생성해 애플리케이션의 처음 실행성능이 떨어진다.Bytecode를 스토리지에 저장하는 경우, 원본 클래스가 변경됐는지를 매번 검사하거나 업데이트마다 해당 스토리지를 지워야 한다.더 좋은 방법이 생각났다. Annotation Processor 최종적으로 저희가 선택한 방식은 컴파일 시점에 형태변환 코드를 자동으로 생성하는 것입니다. Reflection으로 접근하지 않아 속도도 빠르고, Java코드가 미리 작성돼 관리하기도 편하기 때문입니다. POJO 클래스에 알맞은 Annotation을 달아두고, APT를 이용해 Annotation이 달린 모델 클래스에 대해 형태변환 코드를 자동으로 생성했습니다. 형태 변환이 필요한 클래스에 Annotation(@GenerateAccessor)을 표시합니다. @GenerateAccessor public class Message { private Integer id; private String content; public Integer getId() { return id; } ... }  javac에서 APT 사용 옵션과 Processor를 지정합니다. 그러면 Annotation이 표시된 클래스에 대해 Processor의 작업이 수행됩니다. Processor에서 코드를 생성할 때에는 StringBuilder 등으로 실제 코드를 일일이 작성하는 것이 아니라 Velocity라는 template 라이브러리를 이용합니다. Processor는 아래와 같은 소스코드를 생성합니다. public class Message$$Accessor implements Accessor { public kr.co.vcnc.binding.performance.Message create() { return new kr.co.vcnc.binding.performance.Message(); } public Object get(Object target, String fieldName) throws IllegalArgumentException { kr.co.vcnc.binding.performance.Message source = (kr.co.vcnc.binding.performance.Message) target; switch(fieldName.hashCode()) { case 3355: { return source.getId(); } case -1724546052: { return source.getContent(); } ... default: throw new IllegalArgumentException(...); } } public void set(Object target, String fieldName, Object value) throws IllegalArgumentException { kr.co.vcnc.binding.performance.Message source = (kr.co.vcnc.binding.performance.Message) target; switch(fieldName.hashCode()) { case 3355: { source.setId( (java.lang.Integer) value); return; } case -1724546052: { source.setContent( (java.lang.String) value); return; } ... default: throw new IllegalArgumentException(...); } } }  여기서 저희가 정의한 Accessor는 객체를 만들거나 특정 필드의 값을 가져오거나 세팅하는 인터페이스로, 객체의 형태를 변환할 때 이용됩니다. get,set 메서드는 필드 이름의 hashCode 값을 이용해 해당하는 getter,setter를 호출합니다. hashCode를 이용해 switch-case문을 사용한 이유는 Map을 이용하는 것보다 성능상 이득이 있기 때문입니다. 단순 메모리 접근이 Java에서 제공하는 HashMap과 같은 자료구조 사용보다 훨씬 빠릅니다. APT를 이용해 변환코드를 자동으로 생성하면 여러 장점이 있습니다. Reflection을 사용하지 않고 Method를 직접 수행해서 빠르다.Bytecode 생성과 달리 애플리케이션 처음 실행될 때 코드 생성이 필요 없고 만들어진 코드가 APK에 포함된다.Compile 시점에 코드가 생성돼서 Model 변화가 바로 반영된다. APT를 이용한 Code생성으로 Reflection 속도저하를 해결할 수 있습니다. 이 방식은 애플리케이션 반응성이 중요하고 상대적으로 Reflection 속도저하가 큰 안드로이드 라이브러리에서 최근 많이 사용하고 있습니다. (AndroidAnnotations, ButterKnife, Dagger) 성능 비교 다음은 Reflection, Dexmaker, Code Generating(APT)를 이용해 JSONObject를 Object로 변환하는 작업을 50번 수행한 결과입니다.성능 비교 결과 이처럼 최신 OS 버전일수록 Reflection의 성능저하가 다른 방법에 비해 상대적으로 더 큽니다. 반대로 Dexmaker의 생성 속도는 빨라져 APT 방식과의 성능격차는 점점 작아집니다. 하지만 역시 APT를 통한 Code 생성이 모든 환경에서 가장 좋은 성능을 보입니다. 마치며 서비스 모델을 반복적으로 정의하지 않으면서 변환하는 방법을 알아봤습니다. 그 과정에서 Reflection 의 속도저하, Dexmaker 의 단점도 설명해 드렸고 결국 APT가 좋은 해결책이라고 판단했습니다. 저희는 이 글에서 설명해 드린 방식을 추상화해 Binding이라는 라이브러리를 만들어 사용하고 있습니다. Binding은 POJO를 다양한 JSON, Cursor, ContentValues등 다양한 형태로 변환해주는 라이브러리입니다. 뛰어난 확장성으로 다양한 형태의 데이터로 변경하는 플러그인을 만들어서 사용할 수 있습니다. Message message = Bindings.for(Message.class).bind().from(AndroidSources.cursor(cursor)); Message message = Bindings.for(Message.class).bind().from(JSONSources.jsonString(jsonString)); String jsonString = Bindings.for(Message.class).bind(message).to(JSONTargets.jsonString());  위와 같이 Java상에 존재할 수 있는 다양한 타입의 객체에 대해 일종의 데이터 Binding 기능을 수행합니다. Binding 라이브러리도 기회가 되면 소개해드리겠습니다. 윗글에서 궁금하신 점이 있으시거나 잘못된 부분이 있으면 답글을 달아주시기 바랍니다. 감사합니다. 
조회수 1636

Android 의 Sqlite Tip

Android 와 SqliteAndroid 에서 Sqlite 는 오랫동안 사용되어 왔습니다. 지금은 Realm 과 그외의 데이터베이스들이 그 위치를 넘보고 있지만 여전히 사용되고 있음은 틀림없습니다.현재 Jandi 는 서버의 대다수 정보를 앱의 Sqlite-Database 에 Cache 를 하고 있습니다. 따라서 Sqlite 를 얼마나 잘 분리하고 제어하느냐가 앱 자체의 라이프사이클에 큰 영향을 미칩니다.오늘은 Android 팀이 Sqlite 를 어떻게 사용하는지 공유해드리고자 합니다.1. ORM안드로이드만 하신 분들에게는 다소 생소한 개념일 수 있지만 다른 분야에서는 널리 사용되고 있습니다.Android 에서 Sqlite 는 Database 용 Access 객체를 통해서 column/row 단위로 정보를 가져와서 객체를 완성합니다. 하지만 Access 객체에 일일이 Query 를 작성하는 것은 실수가 많을 뿐더러 column/row 단위 정보 매핑 작업은 매우 불편하고 지루하며 잠재적 버그를 내포한 작업니다.그러기 때문에 Object-Query-Databse 를 각각에 맞게 매핑해주는 라이브러리들 통해 단순하고 반복적인 작업을 간단하게 회피할 수 있습니다.아래의 블로그들이 Sqlite-Orm 라이브러리를 사용하는 좋은 정보들이 될 것입니다. 현재 Jandi-Android 의 주요 Orm 라이브러리는 OrmLite 입니다.Sqlite-Orm : 네이버 기술블로그GreenDao BenchmarkRealm Database2. Database-Access유사 관심사 Domain 끼리 묶음수많은 데이터를 테이블로 관리하다보면 많은 Database-Access-Object(DAO) 가 필요합니다. 하지만 자세히 들여다보면 관계된 것끼리의 묶음이 생기게 되며 이를 묶어서 하나의 Access 객체를 만들 수 있습니다.Jandi 의 메시지는 크게 Text, File, Sticker 로 구분되어 있으며 이에 대한 상위로 Message 라는 개념이 있습니다. Text, File, Sticker 는 하위에 각각 2~3개의 Table 로 구성되어 있습니다. 이 전체를 각각 분리해서 관리하면 그에 따른 부수적인 제어 코드들이 불가피 하기 때문에 Jandi 에서는 최상위 Message 도메인에 맞춰서 하나의 묶음으로 관리하였습니다.코드는 다음과 같은 형태를 띄고 있습니다.public class MessageRepository { public List getMessages(/*args...*/) { /*코드 생략*/}; public Message save(/*args...*/) { /*코드 생략*/}; public Message update(/*args...*/) { /*코드 생략*/}; public Text getText(/*args...*/) { /*코드 생략*/}; /*이하 생략*/ } 이러한 형태로 독립성을 가질 수 있는 최상위 Domain 을 기준으로 Repository 클래스를 가지고 있습니다.3. Repository 요청 관리하기위의 모습처럼 관심사별로 Domain 을 분리한 이유 중 가장 큰 이유는 Domain 단위로 요청을 관리하기 위함입니다.Android-Sqlite 는 내부적으로 Read-Write lock 을 가지고 있지만 신뢰도가 높다 할 수 없으며 다양한 테이블에 동시 접근하는 경우 오류가 나지 않을 것이라 보장할 수 없습니다.따라서 보장이 안될바에 1번에 1개의 요청만 처리 할 수 있도록 Domain 단위로 요청을 제한해버리자는 결론을 냈습니다.그러기 위해 2가지 코드를 사용하였습니다.Lock 객체 사용요청을 래핑할 template interface 사용하기멀티 쓰레드로 요청을 처리할 때 Lock 객체를 통해 1번의 1개씩의 동작만 할 수 있도록 하였으며 이를 좀 더 쉽게 쓸 수 있도록 하기 위해 Template Interface 를 만들었습니다.public class LockTemplate { private Lock executorLock; LockTemplate() { executorLock = new ReentranceLock(); } protected T execute(Executable e) { executorLock.lock(); try { return e.execute(); } finally { executorLock.unlock(); } } interface Executable { T execute(); } } 위와 같은 클래스를 만들고 앞서 만든 Repository 클래스에 상속받도록 하였습니다.코드는 다음과 같습니다.public class MessageRepository extends LockTemplate { /*싱글톤으로 동작하도록 합니다. 코드 생략*/ public List getMessages(long roomId) { return execute(() -> { return dao.query(roomId); }); } public int save(List messages) { return execute(() -> { return dao.save(messages); }); } } 위와 같이 함으로써 최종적으로 같은 Repository 에 멀티쓰레드에서 요청을 하여도 1개의 처리만 할 수 있도록 원천적으로 작업하였습니다.정리Android 에서 Sqlite 는 Mysql 이나 PostSQL 과 유사한 RDBMS 를 제공하는 DB 툴입니다. 하지만 기본적인 사용이 매우 번거러울 뿐만 아니라 메모리릭과 오류에 매우 쉽게 노출됩니다. 그래서 다음과 같은 방법을 통해 최소한의 안전망을 구현했습니다.ORM 을 사용하라.반복적이고 DB 접근 과정에서 오류를 최소화 시켜줍니다.Lock 을 의도적으로 사용하라.멀티쓰레드 접근에 의한 오류를 최소화 합니다.synchroized 보다는 concurrent 패키지에서 제공해주는 Lock 을 사용해주세요.#토스랩 #잔디 #JANDI #개발 #앱개발 #인사이트
조회수 1721

날짜 변환, 과연 그리 간단할까?

안드로이드에서는 입력한 날짜를 변환 및 검증하는 로직을 간단하게 구현하기 위해 SimpleDateFormat 클래스를 종종 활용하게 되는데 이 클래스는 규칙에 관대하다(lenient)는 재미난 특성이 있습니다. java.text.SimpleDateFormat 클래스의 근간이 되는 java.text.DateFormat 클래스의 다음 API 문서를 살펴봅시다.By default, parsing is lenient: If the input is not in the form used by this object’s format method but can still be parsed as a date, then the parse succeeds. Clients may insist on strict adherence to the format by calling setLenient(false).파싱 기본 동작은 관대합니다. 이 객체의 날짜 포맷과 일치하지 않는 입력이 주어지더라도 날짜 형태만 유지한다면 파싱이 성공합니다. 클라이언트 코드에서는 setLenient(false) 메소드를 호출해 파싱 규칙을 여전히 엄격하게 가져갈 수 있습니다.lenient 라는 흔하지 않은 단어 때문에 의미가 잘 와닿지 않습니다만, 캠브릿지 영영사전에 따르면 ‘관대하다’ 라는 뜻이 있다고 하네요.lenient /ˈliː.ni.ənt/ ▶ adjective ▶ Level C2(Mastery Proficiency)A lenient punishment is not severe.Thesaurus: allowing, forgiving, merciful, permissive, tolerant하지만 규칙에 관대하다는 말이 무슨 의미인지 여전히 와 닿지 않습니다. 잠시, 아래의 소스코드를 읽고 그 결과를 한번 예측해 볼까요? parse 메소드는 기본적으로 lenient 하다는 특성에 주의합시다./* * 2017년 13월 32일 이라는 입력에 대해 어떤 결과가 나타날까? * 1. 2017-13-32 * 2. 2018-02-04 * 3. 2017-01-01 * 4. 2018-01-01 * 5. ParseException 이 발생 */ val userDate = "2017-13-32" val date = SimpleDateFormat("yyyy-MM-dd").parse(userDate) val localDate = LocalDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC) println ("사용자의 ISO-8601 Date 입력 결과는 ${localDate.year}년-${localDate.month}월-${localDate.dayOfMonth}일 입니다.") lenient 라는 사전 hint 없이 바로 문제를 낼 경우 사람들이 제일 많이 선택한 결과는 ParseException 이 발생한다 였습니다. 하지만 lenient 한 특성으로 인해 실행 결과는 의외로 두번째, 즉 2018년 2월 4일 입니다. 막상 글로 풀어 쓰려니 별 것 아닌 내용처럼 보입니다만, 필자가 담당하는 서비스에서 이 특성을 제대로 파악하지 못해 특정 사용자의 생년월일을 제대로 인식하지 못한 문제가 있었습니다.또한 우리가 흔히 아는 달력을 쓰지 않는 국가도 있다는 점 까지 고려한다면 날짜 변환이라는 것이 간단한 문제가 아니게 됩니다. 즉, 한국인의 관념 속의 ‘달력’ 이란 Gregorian calendar 를 기반으로 한 ISO-8601 달력 입니다. 그런데 이 달력을 쓰지 않는 문화권도 있습니다(한국도 흔하진 않지만 ‘단기’ 라는 별도의 달력을 쓰기도 합니다). 이런 문제 때문에, 글로벌 서비스를 준비하고 계신다면 날짜 변환 문제를 꼭 점검해 보셔야 합니다.Android 에는 이 문제를 해결해 주는 클래스가 있습니다만 불행히도 API Level 이 26이나 되어 2018년 현재에는 제대로 쓰긴 어렵습니다. 다행히도 이 문제를 보완한 joda-time 라이브러리의 안드로이드 포팅 버전도 있으니 이 라이브러리의 도입을 검토해 보는 것도 좋은 문제 해결 방법이 될 것입니다.#개발 #인사이트 #하이퍼커넥트 #개발자 #안드로이드 #개발후기
조회수 1654

공용 계정용 OTP 관리방법

제대로 된 기업용 서비스라면 의례 다중 계정과 권한 제어 기능을 함께 제공하기 마련이다. 그래서 공용 계정을 굳이 만들 이유가 하나도 없다. 하지만 일부 서비스(그리고 대부분의 한국의 기업 서비스)는 단일 계정만 지원하는데다 AWS 같은 서비스도 root 계정이 따로 있어서 계정 관리 이슈가 불거지기 마련이다. 계정 아이디와 암호의 경우는 LastPass 같은 기업용 계정 관리 서비스를 사용하거나 팀 공용 계정 비밀번호 관리하기에서 소개된 방식과 같이 약간은 불편하지만 비용이 들지 않는 수단을 도입하여 관리하면 된다. 그런데 MFA 또는 2FA(2-Step Verification)라고도 부르는 OTP로 계정을 보호할 때는 OTP 정보를 공유하기가 쉽지 않다. 일반적으로 MFA 계정은 Google Authenticator 같은 앱을 설치해 관리한다. OTP 정보와 계정 암호를 한 계정에서 관리하지 않아야 한쪽이 노출되어도 보안을 유지할 수 있기 때문이다. 문제는 Google Authenticator와 Authy 같은 도구를 특정 휴대폰에 설치하면 여러 사람이 OTP 정보를 공유하기 힘들다는 것이다. 그래서 몇가지 솔루션을 찾아보았는데 “이거다!” 싶은 건 없어도 gauth라는 명령줄 기반 도구에 안착하게 되었다.gauth.csv라는 파일에 OTP 정보를 아래와 같이 입력하고AWS: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 Airbnb:abcd efgh ijkl mnop Google:a2b3c4d5e6f7g8h9 Github:234567qrstuvwxyzgauth를 실행하면 아래와 같이 OTP 토큰을 확인할 수 있다.$ gauth prev curr next AWS 315306 135387 483601 Airbnb 563728 339206 904549 Google 453564 477615 356846 Github 911264 548790 784099 [======= ]이제 gauth.csv 파일만 라스트패스 등으로 제한된 사용자에게 안전하게 공유하면 된다.개선 사항DailyHotel/gauth는 pcarrier/gauth를 개선한 tuxmartin/gauth를 Docker 이미지로 감쌌다. 그래서 Golang 개발환경을 갖추고 소스코드를 빌드하지 않아도 바로 사용할 수 있다. 자세한 사용법은 README 문서에 적어두었다.#데일리 #데일리호텔 #개발 #개발자 #개발팀 #인사이트 #꿀팁

기업문화 엿볼 때, 더팀스

로그인

/