'Engineering' 카테고리의 글 목록
www.recopick.com

안녕하세요. 레코픽팀 정재훈입니다.


오늘은 너무나도 당연해서 잊고 살았던 내용들에 대해서 복습을 해볼까 합니다.


HTTP GET과 POST의 차이점을 복습하고. GET, POST가 어떻게 명세 되어 있는지 확인 해보도록 하겠습니다.


HTTP (HyperText Transfer Protocol)는 서버와 클라이언트간에 통신을 위해 설계 되었습니다. 


요청 - 응답 프로토콜을 이용해 통신을 하는데요


HTTP 명세상에 Get과 Post는 아래와 같은 형태로 사용하라고 되어 있습니다. 


Get : 특정 리소스로 부터 데이터를 요청


GET


/blog/entry/1?rate=3&comment=2



Post: 특정 리소스에 데이터를 제출 


POST


POST /blog/post HTTP/1.1

Host:blog.recopick.com

post=글내용&title=글제목



GET 과 POST의 차이점

 

 GET

POST 

 브라우저 리로드

 동의없이 요청

 브라우저가 다시 전송할 지 물음

 브라우저 북마크

 가능

 불가능

 브라우저 히스토리

 가능

 불가능

 인코딩 타입

 application/x-www-form-urlencoded

 다양(multipart, json, xml, custom)  

 데이터 캐쉬

 캐쉬될 수 있음

 불가능 

 데이터 길이제한

 URL 길이가 2048로 제한 

 제한 없음 

 데이터 타입제한

 String만

 제한 없음 (바이너리 가능) 

 보안

 data가 URL상에 다 노출.

- 브라우저 북마크 될 수 있으며 ,히스토리에 남음. 

- 웹서버 로그에 데이터 저장됨.

Get가 비교했을 때 상대적으로 안전함.

- 브라우저 북마크, 히스토리에 데이터 노출 X

- 웹서버 로그에 데이터 저장 X

 가시성

 데이터 요청 형태가 URL에 다 보임

 안보임

 Idempotent method

 YES

 NO

 Safe method

 YES 

 NO 


* Safe method는 HTTP method중 resource를 수정하지 않는 method를 뜻합니다. 오해하시면 안되는 것은 Get method가 resource를 수정하지 않는다는 것을 뜻하는게 아니구요. resource를 수정하는 데 쓰지 말라는 뜻입니다.

 

idempotence (멱등성) 은 수학이나 전산학에서 연산의 성질 중 하나로 여러번 적용하더라도 결과가 달라지지 않는 것을 의미 합니다. 예를 들자면 abs(abs(x)) , 1x1 , max(x,x)등이 있습니다. 따라서 idempotent HTTP method는 여러번 요청해도 결과 값이 달라지지 않는 것을 의미 합니다.

HTTP 명세상으로는 GET은 데이터 요청 , POST는 데이터 제출로 둘이 하는 역할이 다르지만 GET도 데이터를 제출, 요청에 사용할 수 있습니다. 


하지만 명세와 다르게 Get을 non-safe , non-idempoent하게 사용할 경우에는 매우 주의해야 합니다. 

예를 들면, middleware에 프록시가 있고 Safe method (GET)으로 리소스를 수정하는 경우에 문제가 발생할 수 있습니다. http://api/article/1/rateup이라는 GET Method로 리소스를 변경 할 경우 이미 이전에 동일한 method가 다른사람에 의해 호출 되었다면 서버로 전송되지 않고 프록시에서 캐쉬 값을 바로 클라이언트에 전달하는 경우가 발생할 수 있습니다.  

 

그리고 가시성 부분 때문에 데이터를 제출 하는 경우에도 GET을 선호하는 분들이 있습니다. 보안상의 위험, 안전성 을 포기하더라두요.


HTTP 명세상에서 용도를 정해둔 것은 잠재적인 위험으로부터 보호하기 위한 것입니다.


GET은 Idempotent, Safe method라고 명세한 것은 일종의 규약인데 이걸 non idempotent, non safe 하게 쓰면 진짜 괴팍한 겁니다.


당연한 내용이 너무 길어 졌네요.


한줄 요약 들어갑니다.  POST는 Create , Get은 Read. 다른 용도로 쓰지말자.


다음에는 Hapi.js 2부나 REST API Versioning에 대해 글을 쓸 예정입니다. 


참고 (https://github.com/for-GET/know-your-http-well/blob/master/methods.md)

methoddescriptionsafeidem.cache.spec
CONNECT"requests that the recipient establish a tunnel to the destination origin server identified by the request-target and, if successful, thereafter restrict its behavior to blind forwarding of packets, in both directions, until the connection is closed."RFC7231#4.3.6,
RFC2616#9.9
DELETE"requests that the origin server remove the association between the target resource and its current functionality."RFC7231#4.3.5,
RFC2616#9.7
GET"requests transfer of a current selected representation for the target resource."RFC7231#4.3.1,
RFC2616#9.3
HEAD"is identical to GET except that the server MUST NOT send a message body in the response (i.e., the response terminates at the end of the header block)."RFC7231#4.3.2,
RFC2616#9.4
OPTIONS"requests information about the communication options available on the request/response chain identified by the effective request URI."RFC7231#4.3.7,
RFC2616#9.3
POST"requests that the target resource process the representation enclosed in the request according to the resource's own specific semantics."RFC7231#4.3.3,
RFC2616#9.5
PUT"requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload."RFC7231#4.3.4,
RFC2616#9.6
TRACE"is used to invoke a remote, application-layer loopback of the request message."RFC7231#4.3.8,
RFC2616#9.8





Posted by recopick
TAG get, HTTP, method, post

댓글을 달아 주세요

안녕하세요. 이번에 Recopick팀에 새로 합류하게 된 정재훈이라고 합니다.

먼저 간단히 제 소개를 드리면, 저는 Recopick팀에서 메인업무로 기획을 맡고 있으며 부 업무로 UX 디자인을 진행하고 있습니다. 

는 훼이크! 닥치는 대로 뭐든 하는 개발자 입니다. (a.k.a 닥치고 시키는대로하는 개발자) 

네, 하루살이 처럼 오늘 만을 살아가는 개발자입니다. (내일이 안 보여요..) 

팀 합류 전에는 iOS 클라이언트 개발을 주로 했었구요.(깃헙 팔로우 하기)  백엔드 개발도 잠깐 했었는데 Ruby on Rails을 이용 했었습니다.

지금은 노드로 이동했습니다. 하하하하하하하하하하하…. 죄송합니다. ㅡ,.ㅡ; 이동 후 멘탈에....



몇 줄 안 썼는데 벌서 피로감이 몰려 오네요. 나머지는 다음주에 쓰고싶네요. 그럼 짜이지엔 쒜쒜.



도 훼이크… 추억돋는 짤을 사용해보고 싶었어요.

이제부터 정말로 Hapi.js에 대해 설명 드리도록 하겠습니다.  

1. Hapi.js 란 무엇인가?

Hapi (HTTP API server)는 WalmartLabs에서 월마트 모바일 플랫폼에 사용하기 위해 만들었습니다. 리드개발자는 OAuth 리드 개발자로 잘 알려진 Eran Hammer 입니다.
이름 그대로 API Server 특히 모바일을 위한 서버 개발에 좋은 기능들을 많이 담고 있습니다. 내 대세는 모바일.. 물론 일반 웹서버 개발에도 전혀 불편함이 없습니다. 이후에 차차 설명드리겠습니다만. 다양한 기능들을 지원합니다. Batch Request, Pack, Pre, Plugin등이 Core 개발 시에 고려되어 굉장히 효율적으로 동작합니다. 

Hapi 탄생배경
개발팀은 월마트 모바일 개발을 위해 요구사항이 정해져 있었고 요구사항을 충족시키는 프레임워크를 개발해야 되었습니다. 처음 개발 시에는 Express framework를 기반으로 하피 개발을 시작했으나 몇가지 난관에 봉착하게 됩니다. 우선 mobile을 위해 batch functionality를 추가하다 express는 http server와 router 가 밀접하게 연결 되어 있어 효율적으로 기능을 추가 못하는 상황에 봉착합니다. 또한 너무 많은 미들웨어들이 connect roots(req,res,next)에 들어가 있어서 디버깅이 쉽지 않았다고 합니다. 
간단히 요약해드리면 쉽게 가려고 Express에 Wrapping하려다 망테크 한번 타고 분노를 Express 단점에 두고 맨탈 다잡고 Express의 문제점을 수정하는 방식으로 새로 만들게 되었다는 그런 스토리입니다.~ (물론 자신들이 원하는 프레임워크 요구사항 안에서의 문제 입니다) 자세한 내용은 여기 참조 

이후에 자세히 설명 드리겠지만 위에 탄생 배경으로 부터 유추해 볼 수 있는 Hapi의 장점은 아래와 같습니다.
1. Http Server 와 Router의 분리.
2. 심플한 Core (기본 미들웨어의 이해 없이는 깊이 있는 디버깅이 힘든 것과는 다르다)
3. 비즈니스 로직과 트랜스포트 레이어의 분리. 

물론 Express에서도 노력하면 비즈니스 레이어와 트랜스포트를 분리는 할 수 있겠지요. 그러나 비즈니스 로직은 사업이 변경 됨에 따라 자주 변경될 것이고 시간에 쫒기다 보면 분리에 대한 생각은 우선순위에서 밀리게 되겠지요. 하지만 Hapi 처럼 프레임워크 상에 구조적으로 분리를 강요한다면 비즈니스 로직이 자주 수정 되어도 Isolation을 지킬 수 있겠지요. 

저도 저자 처럼. 프레임워크 상에서 구조적 분리를 강요하는 것이 옳다고 생각합니다. 이게 프레임워크 철학인 것이죠. 또 다른 Hapi.js 장점은 이해하기 쉬운 심플한 Core와 필요한 플러그인들만 붙여쓸 수 있도록 완벽히 분리된 모듈들입니다. 

하피는 안전성이 검증된 오픈소스 프레임워크 입니다.
 모질라, 야후, 페이팔, Beats Audio, npmjs.org, 디즈니등에서 사용되어 지고 있으며 계속해서 발전해 나가고 있습니다.
 
Hapi는 프레임워크 컨셉이 명확합니다. 
1. configuration-centric design framework
2. Business Logic 과 transport layer의 분리 
3. Plugin Architecutre 와 Pack을 통한 모듈화.
4. native node의 장점을 유지 . (buffer ,stream)

많은 회사들이 (Express 말고) Hapi를 선택했습니다. 또는 전환 했습니다. (여기)
엄청난 User Pool을 가진 Express를 두고 왜 많은 회사들이 Hapi.js를 사용할까요? 

2. Why Hapi.js ?


"ㅇㅇ Hapi 좋은거 알겠어 근데 Express.js가 기능은 더 많은데  왜 Hapi를 써야되지?”

일단. 둘간의 비교를 하기 전에 NPM의 경우 왜 Hapi를 선택했는지 알아보고 다시 돌아오겠습니다.

NPM은 새로운 웹사이트를 구축하기 전에 프레임워크를 조사했습니다. 

프레임워크 선정 기준은 아래와 같았다고 합니다. 
1. 모듈화
2. 간결하고 기능 추가가 쉬울 것
3. 쉽고 빠르게 컨트리뷰터들이 적응하고 개발할 수 있을 것 ( 협업이 쉬운 프레임워크)
4. 템플릿 엔진
5. 그외 (안전성, 보안, 생산성, 지원)
그외 다른 조건으로는 개발기간은 1주일 정도 였다고 합니다.

조사 결과.
Restify - 템플릿엔진 없음 ,  Sails - 너무 기능이 많음  , KoaJS - generators를 쓴것은 맘에 들지만 아직 프로덕션레벨 사용가능 의문? 
-> Express or Hapi

NPM이 Hapi.js를 선정한 이유.
1. hapi plugin을 이용한다면 각 모듈 서비스를 별로 분리 개발이 가능하여 나중에 각각 별도의 서비스화가 가능하다. Express라면 별도의 모듈화 과정이 필요하다.
2. 성능. Walmartlabs는 Node.js Core Memory Leak을 발견하고 해결한 팀입니다. 이 팀이 Benchmark Drive Development를 기반으로 하피를 개발했고 결과적으로 성능 또한 훌륭하다고 합니다. 그렇겠지요 월마트 모바일에 쓸려면 성능은 중요하겠지요. 저 같은 중생은 믿고 쓰는거지요. 
3. 신뢰성,안전성 (Core 부분 Coverage 100%)
4. 빠른 이슈해결. (hapi 개발팀의 대응이 빠릿했데요.)

제가 보기에는 2,3,4 항목 보다는 1번 모듈화가 쉽다는 점이 NPM이 Express.js 대신 hapi.js를 선정한 가장 큰 이유 일 것 같습니다.

그외의 Hapi.js의 우수성.
1. Plugin Architecture
2. 훌륭한 기본 플러그인 Validation(joi) login(bell)….


3. Hapi vs Express 


Express.js의 문제점
1) http server와 router가 밀접하게 연결되어 있다.  
2) 여러가지 미들웨어들이 내부에 존재. : 너무 많은 서드파티 미들웨어를 가져오다 보니 해당 미들웨어 대한 공부 없이는 전체를 이해하기가 너무 어렵다. 디버깅은 헬게이트 안그래도 요상한 에러가 잘나는 Node.js에 충격과 공포!
3) 철학이 없다. 이것 저것 모든지 다 있는 만물상이 되어버렸다. 



Express.js와 비교하여 Hapi.js가 가진 장점
1) '프레임워크의 철학이 개발프로세스와 코딩스타일에 영향을 미친다’  - Plugin 시스템을 통해 Core는 깔끔한 상태로 유지하며 모듈화를 통해 다양성을 충족하는 방향을 잡았다.
2) Business Logic과 Transport Layer의 분리와 Plugin을 통한 모듈화를 통해 대형프로젝트에 협업하기 좋은 구조를 프레임워크에 녹여 내었다.
3) 코드를 깨끗하고 간결하게 관리 할 수 있다.

Express.js와 비교하여 Hapi.js가 가진 단점.
1) 커뮤니티 베이스가 상대적으로 작다
2) User Pool이 상대적으로 작다.


Merge Conflict 따위 일상이야! , 자바스크립트에 뭔 Configuration Centric이야 하시는 분들은 Express 계속 쓰시면 되구요.

코드보다는 설정 중심이 가독성이 좋고 깔끔하거든!, 이제 Node.js 협업이 중심이야. 모듈화 깔끔하게 되면 좋지 않니? 하시는 분들은 


저와 함께 Hapi를 사용 해보시면 되겠습니다. 실제로 홈피의 Tutorial 내용이 Core의 대부분이구요 10~20분 정도면 금방 익힐 수 있습니다. 

그리고 현재 Hapi.js의 커뮤니티 베이스도 지속적으로 증가할 것 같습니다. 그 이유는 반사이익 때문인데요.. 자세한 것은 다음장에서 설명드리겠습니다.

4. 어두운 그림자가 드리운 Express.js 

Express에 대해 모두가 우려하는 사항에 대해 객관적으로 말씀 드리겠습니다.

1) 떠나간 메인 개발자 TJ - Farewell Node.js
노드의 콜백지옥에 흥미를 잃고(?) Go로 떠나시면서 Express.js 메인테이너를 모집하게 됩니다. 꽤 유명한 사건이라 길게 설명을 하지 않겠습니다. (자세한 사항은 윗링크 참조)

2) Strongloop에 넘어간 Express와 떠나가는 Contributor들
메인테이너 권한을 팔았다? Express 리포는 TJ에서 Strongloop라는 스타트업 회사에 넘어갔습니다. Strongloop는 Express의 메인테이너가 되며 엄청난 홍보효과를 누렸지요.
물론 TJ가 팔았다는 증거는 없습니다. 정황 상… 여러사람들이 의심하고 있습니다.
여러사람들이 정황상 팔렸다고 생각하는 가장 큰이유는  Exppress.js foundation에 Repo로 넘기면 되었을 텐데…그렇게 하기로 했었구…

하여튼 Express 판매와 관련하여 3 개월전 Express.js Repo에서는 '왔다 장보리' 급의 막장드라마가 펼쳐졌었죠

참고 Open Source Dickishness by Eran Hammer

야! 싸움구경 났데~ Maintainer가 바뀐날 Github repo issue 



TJ의 반박글 StrongLoop & Express 

[첨부] 지난 12개월 동안 Commit 수를 보면 왜 Douglas가 TJ에게 분노했는가를 알수 있습니다
303  Douglas Christopher Wilson
 84  Jonathan Ong
 66  Roman Shtylman
 49  TJ Holowaychuk
  5  Fernando Silveira

저는 TJ가 한말 중에 '대부분의 상업적 지원을 받은 오픈소스 프로젝트는 죽거나 망했다'는 내용이 와닿네요. 

왜~ 슬픈 예감은 틀린적이 없나~~~

'I completely understand the rage that I'm seeing from everyone, it's easy to assume every commercial sponsorship will lead to the death of a project or poor results, but I'm sure we can all list numerous cases where the opposite is also true. I think it still takes a community to hold it together regardless.’ 

물론 현재까지 만으로도 Express는 훌륭한 프레임워크입니다. 하지만 많은 컨트리뷰터들이 이탈한 것은 자명한 사실이고 앞으로는 StrongLoop가 리드 메인테이너로서 자신들이 방향을 정하고 진행되어가겠죠. 상업적 기업이 활용하는 프레임워크인 이상 오픈소스라도 자신들의 방향에 맞춰지게 되지 않을까 싶습니다. 또한 상업적인 용도로 쓰일 프레임워크에 무료로 공헌하는 개발자는 없겠지요. 

물론 시간이 지나봐야 알겠지만 좋은 컨트리뷰터들이 계속해서 떠나간다면 익스프레스의 미래는 어둡다고 생각되어집니다.

이런 상황이라면 Sail.js 같이 Express Foundation 위에 만들어진 Framework들은 빠른 시일 내에 koa로 갈아타지 않을 까 싶습니다. 

여러가지 미련을 남겨둔채 여기서 1부 Why Hapi.js를 마치겠습니다.





다음 2부에서는 Hapi.js Restful Service 개발에 대해서 알아보겠습니다.


Posted by recopick

댓글을 달아 주세요

안녕하세요. RecoPick 팀의 김성민입니다.

지난번 DynamoDB의 Write Capacity를 낮춰서 DynamoDB 비용 줄이기 글에 이어서 DynamoDB 비용을 어떻게 또 절감했는지 공유해드리고자 합니다.

지난번 글에서 설명한 것처럼 DynamoDB 비용을 좌우하는 주요 요인이 Write Capacity이기 때문에, 저희 팀에서는 Write Capacity를 줄여서 DynamoDB 비용을 줄였습니다.


하지만 아무리 Write Capacity를 절반 정도로 줄였다고 하더라도, Write 연산이 전혀 없는 시간에 Write Capacity를 일정 수준을 유지해 줘야 하므로 Write 연산이 전혀 없는 시간의 DynamoDB 비용이 여전히 낭비되는 문제가 있었습니다.

마른 수건이라도 한 번 더 짜 본다는 심정으로 Read, 특히 Write 연산이 없는 경우에 DynamoDB 비용을 줄 일 방법이 없을까 고민을 하던 중에 Read/Write 연산이 없는 경우, DynamoDB의 Read/Write Capacity를 낮춰서 비용을 줄일 수 있지 않을까 하는 생각이 들었습니다.

DynamoDB의 Read/Write Capacity 변화를 감시하다가, Read/Write Capacity가 낮아지면 자동으로 Read/Write Provisioned Capacity를 낮추고, 다시 높아지면 올려주면 될 것 같았습니다.

여기서 고려해야 할 점은 DynamoDB의 제약 사항 중, 1일 최대 4회까지 Read 혹은 Write Provisioned Capacity를 낮출 수 있다는 점입니다. Read/Write Provisioned Capacity를 올리는 것은 DynamoDB 비용이 증가하므로, 회수 제한이 없지만, 낮추는 것은 1일 4회로 제한되어 있기 때문에, Read/Write Capacity 변화가 빈번한 경우 자동으로 Read/Write Provisioned Capacity를 조정하기 어렵다는 생각이 들었습니다.

이러한 고민을 하던 중에 Dynamic DynamoDB라는 오픈 소스를 발견했습니다.
Dynamic DynamoDB는 앞서 설명한 것처럼, DynamoDB의 Read/Write Capacity 변화에 따라 Read/Write Capacity를 자동으로 조절해주는 도구입니다.

특히, Read/Write Capacity 증가 시점 및 비율 등을 설정으로 조절할 수 있기 때문에, 1일 4회로 제한된 Read/Write Capacity를 낮출 수 있는 DynamoDB의 제약 조건 내에서도 Provisioned Read/Write Capacity를 자동으로 변경할 수 있었습니다.

아래 그래프는 Dynamic DynamoDB 적용 후, DynamoDB의 Write Capacity 변화 그래프입니다.





Dynamic DynamoDB 설치 및 운영 방법은 Dynamic DynamoDB 문서를 참고 하시면 되기 때문에 자세한 설명은 하지 않겠습니다.


대신 몇 가지 주의하실 점을 설명해 드리자면,


(1) Read/Write 연산이 전혀 없으면서도 Read/Write Capacity를 줄이고자 한다면, allow-scaling-down-Reade-on-0-percentallow-scaling-down-writes-on-0-percent를 true로 설정해야 합니다.


(2) Dynamic DynamoDB는 기본적으로 CloudWatch의 5분 동안의 모니터링 결과를 사용해서 Read/Write Capacity를 조절하는데, CloudWatch 결과의 최신성을 조절하려면, lookback-window-start를 조정하시면 됩니다.

lookback-window-start 값을 5로 하면, now() - 5(분) 에서 now() (분)까지의 Cloud Watch 모니터링 결과를 사용하게 됩니다.

하지만 5분 미만의 Cloud Watch 모니터링 결과를 사용할 수 없으므로, 너무 작게 설정하면, Read/Write Capacity가 자동 조절되지 않습니다.


(3) 1일 4회까지 Read/Write Capacity를 낮출 수 있다는 Dynamo DB 제약 사항을 지키려면,

increase-{reads, writes}-with는 작게 decrease-{reads, writes}-with는 크게 하는 편이 좋습니다.

즉, Batch 작업의 경우, Read/Write가 천천히 증가하다가 작업 종료 시점에 급격하게 줄어들기 때문에, 증가 비율보다 감소 비율을 크게 해서 작업 종료 시점에 Read/Write Capacity를 크게 낮춰서 1일 Read/Write Capacity 감소 횟수를 줄이는 것이 좋습니다.


[참고 자료]







Posted by recopick

댓글을 달아 주세요

안녕하세요. RecoPick팀의 김성민 입니다.


이번 글에서는 HBase를 사용하면서, 궁금했던 점들 몇 가지를 질의 응답 형식으로 정리해 봤습니다.

FAQ와 다르게 자주하는 질문들이 아닌 것 같아서, 제목을 IAQ[각주:1]라고 했습니다.


Q1. HBase의 .META. 테이블도 split 될까?

A1. 안됨

HBase의 .META. 테이블은 region 정보를 저장하고 있는 특별한 테이블인데, BigTable 논문에서 .META. 테이블이 여러 region으로 나눠질 수 있도록 설계했지만, HBase에서는 .META. 테이블은 split 되지 않도록 구현했습니다. 그리고, -ROOT- 테이블은 .META. 테이블이 저장된 region 정보를 가지고 있기 때문에, split 되지 않습니다. 하지만, HBase에서는 .META. 테이블이 split 되지 않기 때문에, 0.94 이후 버전 부터는 -ROOT- 테이블을 없애고, .META. 테이블만 사용하도록 변경하였습니다.


.META. 테이블에 모든 region의 메타 정보(데이터가 저장된 위치 정보)를 저장하고, .META. 테이블이 split 되지 않기 때문에, HBase 클러스터는 최대 1개의 region에 저장할 수 있는 메타 정보 개수 만큼의 region을 저장할 수 있습니다. 즉, HBase 클러스터를 무한히 크게 할 수 없습니다.




Fig 1-(a). .META. 테이블 구조



Fig 1-(b). Zookeeper와 .META. 테이블 구조





Fig 1-(c). BigTable의 root, meta 테이블 구조


Q2. (minor/major) compaction 작업은 서로 다른 region들 사이에서도 진행되는가?

A2. 안됨.

HBase에서는 주기적으로 데이터 사이즈가 작은 파일들을 하나로 합쳐주는 compaction 작업을 진행합니다. 데이터를 저장할 때, 자동으로 split 해서 저장했다가, 파일 크기가 작은 파일들이 많이 생길 경우, 하나의 큰 파일로 합쳐주는 작업을 진행하는데, compaction 작업의 단위는 directory가 아니라 file 단위이기 때문에, 서로 다른 region에 있는 작은 파일들을 하나로 merge하지는 않습니다.

HBase에서 region은 HDFS상에서 하나의 directory를 의미하고, compaction의 동일한 region 즉, directory 내에 있는 파일들을 하나의 큰 파일로 합쳐주는 작업이라고 이해하시면 됩니다.




Fig 2. Minimum compaction threshold


Q3. HBase에서 저장하고자 하는 테이블(region)을 미리 split 한 후, 데이터를 저장할 때, major compaction이 시작되면, 데이터(HFile)가 없는 테이블(region)은 삭제 되는가?

A3. 안됨.

HBase에서는 write 연산이 빈번하게 연속적으로 발생할 경우, region split 역시 빈번하게 발생 하기 때문에, 미리 region을 split 해서 write할 때, region이 split되는 것을 회피해서 write 성능을 향상 시키는 방법을 종종 사용합니다. 그런데, 실제 write 연산을 수행하기 전에 region을 split 하는 것이기 때문에, 실제 데이터는 존재하지 않고, .META. 테이블에 rowkey와 region server 정보가 기록되어 있습니다. 이후, write 연산을 수행하면, 저장할 데이터의 row key가 미리 split한 region에 start과 end row key 범위에 들어있지 않은 경우, 특정 region에는 데이터가 전혀 존재하지 않을 수 있습니다. 이렇게 되면, major compaction을 할 때, region의 데이터 사이즈가 작기 때문에, 데이터가 존재하지 않은 region은 삭제 될 것 같다고 추측할 수 있습니다. 하지만, Q2에서 설명한 것 처럼, compaction은 여러 파일들을 하나로 합치는 작업이지 디렉터를 하나로 합치는 작업이 아닙니다. 따라서, region은 일종의 디렉터리이기 때문에, 데이터 즉, 파일을 하나도 가지고 있지 않은 region (비어 있는 디렉터리)들을 compaction 작업 시, 하나로 합치거나 삭제 되지 않습니다.




Fig 3. HBase Architecture - Storage


Q4. Region이 split 될 때, region의 데이터 크기를 기준으로 split 되는지, 아니면, region 내의 row key의 최소, 최대 값을 기준으로 하는지?

A4. 설정에 따라 다름.

Region을 split하는 정책은 "hbase.regionserver.region.split.policy" 라는 설정에 따라서, 결정됩니다.

0.94 이전 버전에서는 region이 데이터 크기를 기준으로 split 했지만, 이후에는 defalut 설정이 변경되었습니다.

다음과 같은 미리 정의된 Split policy가 있습니다. (물론, custom split policy를 구현하실 수도 있습니다.)


(1) ConstantSizeRegionSplitPolicy

(2) IncreasingToUpperBoundRegionSplitPolicy

(3) KeyPrefixRegionPolicy

(4) DelimitedKeyPrefixRegionSplitPolicy

(5) DisabledRegionSplitPolicy




Fig 4. HBase Region splitting


Q5. WAL (Write Ahead Log) 를 disable 시킨 상태에서 write을 수행 중이던 region server가 crash 될 경우, crash 이전까지 write한 데이터는 HBase에 저장되었을까?

A5. 알수 없음.

HBase에서 write 연산을 수행하면, WAL(HDFS)와 Memstore(Memory)에 데이터를 기록하는데, WAL을 사용하지 않을 경우, Memstore에만 데이터를 쓰고, Memstore에 더 이상 데이터를 쓸 수 없을 경우나, 혹은 일정 시간 간격으로 Memstore에 있는 데이터를 HDFS에 강제로 기록합니다. 그런데, 만약, Memstore의 데이터가 HDFS에 flush 되지 않은 시점에 region server가 crash된 경우, 메모리 상에 있는 Memstore 데이터가 사라지기 때문에, region server가 crash 되기 전에 write 한 데이터가 정상적으로 저장되었다고 확신할 수 없습니다.

만약, WAL을 사용했다면, 새로운 region server가 실행 되면서, WAL를 replay 하기 때문에, region server가 crash 되기 전의 데이터가 정상적으로 저장됩니다.




Fig 5-(a). HBase Write Path



Fig 5-(b). HBase Write Sequence Diagram



[참고 자료]

Q1. (1) http://blog.cloudera.com/blog/2013/04/how-scaling-really-works-in-apache-hbase/ 

       (2) https://blog.safaribooksonline.com/2012/11/16/tip-2-hbase-meta-layout/

(3) http://research.google.com/archive/bigtable.html


Q2. (1) http://hbase.apache.org/book.html#compaction


Q4. (1) http://hortonworks.com/blog/apache-hbase-region-splitting-and-merging/

       (2) http://hbase.apache.org/book/config.files.html#hbase.regionserver.region.split.policy


Q5. (1) http://hbase.apache.org/book/config.files.html#hbase.regionserver.optionalcacheflushinterval

       (2) http://www.slideshare.net/enissoz/hbase-and-hdfs-understanding-filesystem-usage

       (3http://blog.sematext.com/2012/07/16/hbase-memstore-what-you-should-know/

(4http://blog.cloudera.com/blog/2012/06/hbase-write-path/

(5http://www.larsgeorge.com/2010/01/hbase-architecture-101-write-ahead-log.html

 

  1. Peter Norvig의 Java IAQ에서 제목을 인용함(http://norvig.com/java-iaq.html) [본문으로]
Posted by recopick
TAG HBase

댓글을 달아 주세요

안녕하세요. RecoPick팀 김군우입니다.


RecoPick에서는 쉽고 빠르게 추천 서비스를 사이트에 적용할 수 있도록, 아래와 같은 추천 위젯을 제공 해 드리고 있습니다.


RecoPick 추천 위젯추천 위젯 스크린샷



참고

 - 커스텀 CSS를 통해 추천 위젯의 디자인을 마음껏 바꿔보세요.: http://blog.recopick.com/40

 - 한 페이지에 여러 개의 추천 위젯을 제공해보세요!: http://blog.recopick.com/31

 - 베이비 엔젤에 추천이 적용되었습니다. + 추천 위젯 소식: http://blog.recopick.com/9



추천 위젯에는 한 번에 좀 더 많은 추천 상품을 보여 달라는 요구사항에 맞추어 Swipe(혹은 페이징) 기능이 포함되어 있습니다.


처음 추천 위젯을 만들었을 때는 간단하게 Swipe.js 라이브러리를 사용하여 이 기능을 구현하였었습니다만, 이 라이브러리가 아이프레임으로 사용되었을 때에는 iOS에서는 브라우저가 죽어버리는 치명적인 결함이 있었습니다. https://github.com/thebird/Swipe/issues/407


결국 고객사의 다양한 요구사항을 지속적으로 반영할 수 있도록 자체적으로 Swipe 라이브러리를 개발하였고, 이를 오픈 소스로 공개하였습니다.


RecoPick Swipe https://github.com/mctenshi/recopick-swipe


RecoPick Swipe는 터치 디바이스 에서의 Swipe 동작에 따른 이전, 다음 페이지 이동, PC 환경을 위한 이전, 다음 버튼 설정, 무한 Swipe, 현재 페이지 표시 등의 기능을 제공합니다. 사이트를 통해 자세한 동작 원리, 데모, 설명 문서 등을 확인 하시기 바랍니다.


사이트를 둘러볼 시간이 없으신 분들은 아래 데모를 통해 PC/모바일에서 모두 사용 가능한 Swipe 기능을 간단히 체험해보세요~

http://jsfiddle.net/mctenshi/qA9aw/1/


RecoPick Swipe 데모데모 스크린샷



RecoPick Swipe는 여러 사이트에서 서비스를 하여 왔고, Swipe 기능 개발에 필요한 요소들을 지속적으로 갖추어 왔습니다. 앞으로도 이 라이브러리는 RecoPick 서비스와 함께 발전해나아갈 예정이니 많이 이용해주세요~ :)



Posted by recopick

댓글을 달아 주세요

  1. 하늘섬 2014.07.09 18:30 신고 Address Modify/Delete Reply

    쉽게 사용하도록 bower에도 등록되었으면 좋겠네요!

안녕하세요. RecoPick 팀의 김성민 입니다.


MySQL을 사용하다 보면, 항상 골치가 아픈 문제 중의 하나가 MySQL을 Master-Slave 구조로 이중화한 경우 replication lag가 크게 발생하는 문제일 것입니다.

저희 팀 역시 AWS RDS 서비스에서 MySQL 5.5 (InnoDB)을 사용하면서, 간헐적으로 발생하는 replication lag 를 고민하던 중에 MySQL InnoDB 설정으로 문제를 해결할 수 있었습니다.

아래 그래프 처럼, replication lag가 1,000~3,500초 사이로 발생하고 있었습니다.




MySQL InnoDB 설정을 다음과 같이 변경한 이후에는 replication-lag가 500초 이하로 줄었습니다

innodb_flush_log_at_trx_commit = 2

이 마법 같은 시스템 설정에 대해서 좀 더 알아보면,

innodb_flush_log_at_trx_commit


innodb_flush_log_at_trx_commit이 0으로 설정되면, 로그 버퍼는 초 당 한번씩 로그 파일이 기록이 되고 디스크 연산에 대한 플러시는 로그 파일에서 실행되지만, 트랜젝션 실행 시점에는 아무것도 실행되지 않게 된다. 이 값이 1 (디폴트)로 설정되면, 로그 버퍼는 각 트랜젝션이 실행될 때마다 로그 파일에 기록되고 로그 파일에서 디스크 연산에 대한 플러시가 실행된다. 2로 설정되면, 로그 버퍼는 각 실행 시점마다 파일로 기록되지만, 디스크 연산에 대한 플러시는 실행되지 않는다. 하지만, 로그 파일에 대한 플러시는 값이 2일 때에도 초당 한번씩 실행된다. 초당1회의 플러시는 모든 초당 이루어진다고는 장담할 수가 없는데, 그 이유는 프로세스 스케쥴링 문제 때문이라는 점을 알아두자.

이 변수의 디폴트 값은 1이며, 이 값은 ACID와의 호환성을 위해 요구되는 값이다. 여러분은 이 값을 1 이외의 값으로 설정해서 보다 좋은 성능을 얻을 수는 있겠지만, 크래시가 나게 되면 한 순간에 모든 것을 잃어 버릴 수도 있다. 만일 이 값을 0으로 설정한다면, 어떠한 mysqld 프로세스 크래시라도. 만일 이 값을 2로 설정한다면, OS 크래시 또는 전원 불량을 통해서만 마지막 초 순간의 트랜젝션이 지워지게 된다. 하지만, InnoDB의 크래시 복구는 영향을 받지 않으며 따라서 크래시 복구는 변수 값에 상관없이 실행된다. 대다수의 OS와 몇몇 디스크들은 디스크에 대한 플러시 연산을 제대로 실행하지 못한다는 점을 알아두자.

 
Note: 트랜젝션을 사용하는 InnoDB을 이용한 리플리케이션 설정에서 내구성과 일관성을 최대로 확보하기 위해서는, 여러분이 사용하는 마스터 서버의my.cnf 파일에서innodb_flush_log_at_trx_commit=1sync_binlog=1innodb_safe_binlog (MySQL 5.0.3 이전 버전의 경우)를 사용해야 한다. (innodb_safe_binlog는 5.0.3 이후에는 필요가 없다.)


(출처: MySQL 5.0 한글 매뉴얼)


요약하자면,
innodb_flush_log_at_trx_commit = 2 로 설정하면,
- commit 할 때마다 log 파일에 쓰지 않고, 1초 간격으로 log 파일에 기록함 

- 장애 발생 시, 1초간의 transaction 유실이 있을 수 있음


즉, log 파일을 1초 간격으로 기록하기 때문에, 파일 쓰기 횟수가 줄어들어서 replication lag를 크게 줄일 수 있었습니다.

MySQL replication lag 때문에 고민하시는 분들은 innodb_flush_log_at_trx_commit 설정을 변경해 보시는 것은 어떨까요?

[참고 자료]

(1) MySQL 5.0 한글 매뉴얼, 14.2.4. InnoDB 스타트업 옵션 및 시스템 변수

- http://www.mysqlkorea.co.kr/sub.html?mcode=manual&scode=01&m_no=21862&cat1=14&cat2=422&cat3=438&lang=k


(2) MySQL 5.5 Reference Manual, 14.3.16 InnoDB Startup Options and System Variables
http://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html


(3) Reasons for MySQL Replication Lag

- http://www.mysqlperformanceblog.com/2011/07/29/reasons-for-mysql-replication-lag/


(4) Fighting MySQL Replication Lag

- http://www.mysqlperformanceblog.com/2008/09/22/fighting-mysql-replication-lag/


(5) Managing Slave Lag with MySQL Replication

- http://www.mysqlperformanceblog.com/2007/10/12/managing-slave-lag-with-mysql-replication/


(6) 5 Tricks to Avoid MySQL Replication Delays

- http://mechanics.flite.com/blog/2012/06/12/5-tricks-to-avoid-mysql-replication-delays/


Posted by recopick

댓글을 달아 주세요

안녕하세요. RecoPick 팀의 김성민 입니다.

이번에는 Hadoop Job Counter 제한 초과 오류에 이어서 Hadoop map-reduce 작업 중 발생하는 OOM (Out of memory) 에러에 대해서 공유 드리고자 합니다. 


현상


syslog logs

2014-04-28 18:38:05,051 FATAL org.apache.hadoop.mapred.Task: attempt_201402170624_239030_r_000008_0 : Map output copy failure : java.lang.OutOfMemoryError: Java heap space
        at org.apache.hadoop.mapred.ReduceTask$ReduceCopier$MapOutputCopier.shuffleInMemory(ReduceTask.java:1640)
        at org.apache.hadoop.mapred.ReduceTask$ReduceCopier$MapOutputCopier.access$3000(ReduceTask.java:1197)
        at org.apache.hadoop.mapred.ReduceTask$ReduceCopier.shuffle(ReduceTask.java:2364)
        at org.apache.hadoop.mapred.ReduceTask$ReduceCopier$MapOutputCopier.getMapOutput(ReduceTask.java:1511)
        at org.apache.hadoop.mapred.ReduceTask$ReduceCopier$MapOutputCopier.copyOutput(ReduceTask.java:1360)
        at org.apache.hadoop.mapred.ReduceTask$ReduceCopier$MapOutputCopier.run(ReduceTask.java:1292)



원인


Hadoop map-reduce 작업은 아래 그림과 같이 4단계로 이루어지는데, 


Figure 1. Shffle and sort in MapReduce



map 태스크의 결과가 reduce 태스크로 복사될 때, map의 결과 데이터 치수가 작을 경우, reduce 태스크의 JVM 메모리에 복사됩니다. 
그런데 reduce 태스크의 JVM 메모리가 작은 경우, 위와 같이 Out Of Memory Error가 발생합니다. 
reduce 태스크의 JVM 메모리로 복사될 수 있는 map 태스크의 데이터 치수는 mapred.job.shuffle.input.buffer.percent (default 값은 0.70) 설정으로 조정 가능합니다.  

해결 방법


reduce 태스크의 JVM 메모리에 맞게 mapred.job.shuffle.input.buffer.percent 값을 0.70 -> 0.20 으로 설정해서 map-reduce 작업을 수행하도록 하였습니다.

[참고 자료]

(1) 7 Deadly Hadoop Misconfigurations  by Kate Ting, Cloudera
http://archive.apachecon.com/na2013/presentations/27-Wednesday/Big_Data/14:45-7_Deadly_Hadoop_Misconfigurations-Kathleen_Ting/HadoopTroubleshootingApacheCon.pdf
http://files.meetup.com/12611842/03-27-14-Cloudera.pdf

(2) Hadoop Troubleshooting 101 by Kate Ting, Cloudera
http://www.slideshare.net/cloudera/hadoop-troubleshooting-101-kate-ting-cloudera

(3) Hadoop: The Definitive Guide, 3rd Edition 




Posted by recopick

댓글을 달아 주세요

안녕하세요. RecoPick 김태현입니다.
이번 글에서는 Python boto로 Amazon Elastic MapReduce(EMR)  사용하는 방법에 대해서 간단한 예제와 함께 살펴보겠습니다.

RecoPick 에서는 대용량 추천 계산을 위해 EMR을 사용하고 있습니다. EMR은 Hadoop Framework 을 쉽게 사용할 수 있도록 AWS에서 제공해주는 서비스입니다.
EMR에서는 실제 장비를 사용한 시간만큼만 요금이 부과되기 때문에 24시간 Hadoop Platform을 사용하지 않는다면, 비용절감을 위해 EMR 적용을 추천해드립니다.

오늘은, EMR을 구동하기 위한 다양한 방법이 있겠지만, 그중 하나인 Python boto를 이용하는 방법에 대해서 간단한 예제와 함께 설명해 드리려고 합니다.
boto는 AWS 서비스를 python에서 쉽게 사용할 수 있도록 제공되는 python package 입니다. 설치 및 관련 API는 아래 참고를 확인하세요.

1. EMR을 사용하기 위해 관련된 package를 import 합니다.


  import boto.emr

  

  from boto.emr.step import JarStep

  from boto.emr import BootstrapAction


2. EMR을 사용하기 위한 connection을 맺습니다.


  conn = boto.emr.connect_to_region(

      aws_access_key_id='YOUR_KEY_ID',

      aws_secret_access_key='YOUR_ACCESS_KEY',

      region_name='YOUR_REGION_NAME')


3. 별도의 Hadoop 설정이 필요하다면 필요한 설정들을 list로 만듭니다.


  hadoop_config_params = ['-m', 'mapred.child.java.opts=-Xmx2g',

                          '-m', 'mapred.reduce.memory.mb=3072',

                          '-m', 'mapred.job.reuse.jvm.num.tasks=-1',

                          '-m', 'mapred.map.tasks.speculative.execution=false',

                          '-m', 'mapred.reduce.tasks.speculative.execution=false',

                          '-h', 'dfs.replication=2']


4. Bootstrap Action을 사용하여 Hadoop 설정을 진행하고, ganglia 모니터링이 필요하다면 이것 역시 Bootstrap Action을 통해 install 합니다.


  hadoop_config_bootstrapper = BootstrapAction('hadoop-config',

                                               's3://elasticmapreduce/bootstrap-actions/configure-hadoop',

                                               hadoop_config_params)

  hadoop_monitor_bootstrapper = BootstrapAction('ganglia-config',

                                                's3://elasticmapreduce/bootstrap-actions/install-ganglia',

                                                '')


5. 실제 프로그램에서 사용할 main class와 관련 args를 list로 설정하고


  step_args = ['com.recopick.MainClassPath',

               '--arg1',

               'arg1',

               '--arg2',

               'arg2']


6. Hadoop 작업에 사용될 jar 파일의 s3 위치를 지정합니다.


  jar_path = 's3n://jar_path'

 
7. 위에서 만들어진 step_args와 jar_path를 이용하여 Hadoop job 하나를 step으로 생성합니다. 추가 step이 필요하다면 이 과정을 반복하면 됩니다.


  job_steps = JarStep(name='my-steps',

                      jar=jar_path,

                      step_args=step_args)


8. 생성된 step list를 argument로 EMR을 실행합니다. 이때 Hadoop version 및 ec2 장비 type과 장비 수를 설정할 수 있습니다. 


  job_id = conn.run_jobflow(name='job_name',

                            ami_version='3.0.4',

                            ec2_keyname='keyname',

                            availability_zone='zone',

                            master_instance_type='master_ec2_type',

                            slave_instance_type='slave_ec2_type',

                            num_instances='ec2_num',

                            log_uri='s3://recopick-snoop/log/snoop-emr/',

                            bootstrap_actions=[hadoop_config_bootstrapper],

                            steps=[job_steps])

 
9. ec2 장비에 tag를 할당해서 다른 ec2 장비와 용도를 구분할 수 있고, ‘costCenter’ tag를 이용하면 각 EMR 작업마다 요금을 구분할 수도 있습니다.


  tag_dict = {}

  tag_dict['Name'] = 'emr.' + job_id

  conn.add_tags(job_id, tag_dict)


참고로 ec2 장비 type에 따라 네트워크 속도가 다르므로, 빠른 처리속도를 원한다면 높은 사양의 ec2를 사용하는 것을 추천해드립니다.
RecoPick 에서는 process 수를 같게 맞췄을 때 m1.medium보다 c1.xlarge를 사용했을 때 약 20% 정도 처리속도가 빨라진 것을 경험할 수 있었습니다.
 
아래는 전체 sample script입니다.


  import boto.emr

 

  from boto.emr.step import JarStep

  from boto.emr import BootstrapAction

 

  conn = boto.emr.connect_to_region(

      aws_access_key_id='YOUR_KEY_ID',

      aws_secret_access_key='YOUR_ACCESS_KEY',

      region_name='YOUR_REGION_NAME')

 

  hadoop_config_params = ['-m', 'mapred.child.java.opts=-Xmx2g',

                          '-m', 'mapred.reduce.memory.mb=3072',

                          '-m', 'mapred.job.reuse.jvm.num.tasks=-1',

                          '-m', 'mapred.map.tasks.speculative.execution=false',

                          '-m', 'mapred.reduce.tasks.speculative.execution=false',

                          '-h', 'dfs.replication=2']

 

  hadoop_config_bootstrapper = BootstrapAction('hadoop-config',

                                               's3://elasticmapreduce/bootstrap-actions/configure-hadoop',

                                               hadoop_config_params)

  hadoop_monitor_bootstrapper = BootstrapAction('ganglia-config',

                                                's3://elasticmapreduce/bootstrap-actions/install-ganglia',

                                                '')

 

  step_args = ['com.recopick.MainClassPath',

               '--arg1',

               'arg1',

               '--arg2',

               'arg2']

 

  jar_path = 's3n://jar_path'

 

  job_steps = JarStep(name='my-steps',

                      jar=jar_path,

                      step_args=step_args)

 

  job_id = conn.run_jobflow(name='job_name',

                            ami_version='3.0.4',

                            ec2_keyname='keyname',

                            availability_zone='zone',

                            master_instance_type='master_ec2_type',

                            slave_instance_type='slave_ec2_type',

                            num_instances='ec2_num',

                            log_uri='s3://recopick-snoop/log/snoop-emr/',

                            bootstrap_actions=[hadoop_config_bootstrapper, hadoop_monitor_bootstrapper],

                            steps=[job_steps])

 

  tag_dict = {}

  tag_dict['Name'] = 'emr.' + job_id

  conn.add_tags(job_id, tag_dict)


참고
1. https://aws.amazon.com/ko/elasticmapreduce/
2. https://github.com/boto/boto
3. http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/useprogramaccess.html


Posted by recopick

댓글을 달아 주세요

안녕하세요. RecoPick 팀의 김성민입니다. 

이번에는 Hadoop map-reduce 작업 중 발생한 특이한(?) 에러에 대해서 공유 드리고자 합니다. 

팀 업무 특성상 로그 기록 분석을 위한 통계 작업을 많이 하는데, 아주 단순한 통계 작업을 하다가 아래와 같은 에러가 발생했습니다.


현상


HTTP ERROR 500

Problem accessing /jobdetailshistory.jsp. Reason:
    Too many counters: 121 max=120


Caused by:
org.apache.hadoop.mapreduce.counters.LimitExceededException: Too many counters: 121 max=120
        at org.apache.hadoop.mapreduce.counters.Limits.checkCounters(Limits.java:61)
        at org.apache.hadoop.mapreduce.counters.Limits.incrCounters(Limits.java:68)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.addCounter(AbstractCounterGroup.java:77)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.addCounterImpl(AbstractCounterGroup.java:94)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.findCounterImpl(AbstractCounterGroup.java:122)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.findCounter(AbstractCounterGroup.java:112)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.findCounter(AbstractCounterGroup.java:129)
        at org.apache.hadoop.mapred.Counters$Group.findCounter(Counters.java:323)
        at org.apache.hadoop.mapred.Counters$Group.findCounter(Counters.java:217)
        at org.apache.hadoop.mapreduce.util.CountersStrings.parseEscapedCompactString(CountersStrings.java:272)
        at org.apache.hadoop.mapred.Counters.fromEscapedCompactString(Counters.java:595)
        at org.apache.hadoop.mapred.jobdetailshistory_jsp._jspService(jobdetailshistory_jsp.java:339)
        at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
        at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1221)
        at org.apache.hadoop.http.lib.StaticUserWebFilter$StaticUserFilter.doFilter(StaticUserWebFilter.java:109)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
        at org.apache.hadoop.http.HttpServer$QuotingInputFilter.doFilter(HttpServer.java:1068)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
        at org.apache.hadoop.http.NoCacheFilter.doFilter(NoCacheFilter.java:45)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
        at org.apache.hadoop.http.NoCacheFilter.doFilter(NoCacheFilter.java:45)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
        at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:399)
        at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
        at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
        at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766)
        at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:450)
        at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
        at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
        at org.mortbay.jetty.Server.handle(Server.java:326)
        at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
        at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:928)
        at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:549)
        at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
        at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
        at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:410)  
        at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)


원인

에러 발생 원인은 "Too many counters: 121 max=120" 에러 로그 메시지처럼, Job Tracker에서 사용하는 Counter 최대 개수를 초과해서, Job Tracker 웹 UI에서 작업 history를 볼 수 없었습니다. 

실제로 Exception이 발생하는 부분의 코드를 추적해 보면 알 수 있듯이 map reduce·job.counters.limit 기본 설정 값이 120입니다.


[MRJobConfig.java 소스 코드]

package org.apache.hadoop.mapreduce;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;

@InterfaceAudience.Private
@InterfaceStability.Evolving
public interface MRJobConfig {

  public static final String COUNTERS_MAX_KEY = "mapreduce.job.counters.max";
  public static final int COUNTERS_MAX_DEFAULT = 120;

  public static final String COUNTER_GROUP_NAME_MAX_KEY = "mapreduce.job.counters.group.name.max";
  public static final int COUNTER_GROUP_NAME_MAX_DEFAULT = 128;

  public static final String COUNTER_NAME_MAX_KEY = "mapreduce.job.counters.counter.name.max";
  public static final int COUNTER_NAME_MAX_DEFAULT = 64;

  public static final String COUNTER_GROUPS_MAX_KEY = "mapreduce.job.counters.groups.max";
  public static final int COUNTER_GROUPS_MAX_DEFAULT = 50;


[Limits.java 소스 코드]

package org.apache.hadoop.mapreduce.counters;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import static org.apache.hadoop.mapreduce.MRJobConfig.*;

@InterfaceAudience.Private
public class Limits {

  static final Configuration conf = new Configuration();
  public static final int GROUP_NAME_MAX =
      conf.getInt(COUNTER_GROUP_NAME_MAX_KEY, COUNTER_GROUP_NAME_MAX_DEFAULT);
  public static final int COUNTER_NAME_MAX =
      conf.getInt(COUNTER_NAME_MAX_KEY, COUNTER_NAME_MAX_DEFAULT);
  public static final int GROUPS_MAX =
      conf.getInt(COUNTER_GROUPS_MAX_KEY, COUNTER_GROUPS_MAX_DEFAULT);
  // mapreduce.job.counters.limit is deprecated in favor of
  // mapreduce.job.counters.max in Hadoop 2 so we support both here
  public static final int COUNTERS_MAX =
      conf.getInt(COUNTERS_MAX_KEY,
        conf.getInt("mapreduce.job.counters.limit", COUNTERS_MAX_DEFAULT));

  private int totalCounters;
  private LimitExceededException firstViolation;

  // ...중략...

  public synchronized void checkCounters(int size) {
    if (firstViolation != null) {
      throw new LimitExceededException(firstViolation);
    }
    if (size > COUNTERS_MAX) {
      firstViolation = new LimitExceededException("Too many counters: "+ size +
                                                  " max="+ COUNTERS_MAX);
      throw firstViolation;
    }
  }

Hadoop map-reduce 작업 시에는 Job Tracker와 Task Tracker가 관리하는 기본 counter 외에 사용자가 필요에 따라서 counter를 추가할 수 있습니다.


예를 들어 서비스별 PV/UV와 같은 단순한 통계 계산을 할 때, 결과를 HDFS에 남기는 것 보다, map-reduce의 counter로 남기면, HDFS에 결과를 쓸 필요 없이 바로 계산 결과를 가져올 수 있습니다. 그런데 서비스 개수가 120개 이하일 때는 문제가 발생하지 않지만, 서비스 개수가 120개를 초과할 경우, job tracker의 counter 제한에 걸려서 위와 같은 에러가 발생하게 됩니다.


해결 방법


사용자 counter 개수가 120개가 초과할 것으로 예상한다면, 결과를 HDFS에 남기는 것이 좋습니다. 

혹은, job tracker를 실행시킬 때, job counter 최대 개수를 충분히 크게 하면 이런 문제는 회피할 수 있을 것입니다. 하지만 job counter가 job tracker의 memory에 보관되기 때문에, job counter 최대 개수를 늘릴수록 job tracker 실행하기 위해 더 많은 메모리가 필요하다는 점을 유의하셔야 합니다. 


또한, Hadoop 버전 별로 기본 설정 값이 다르므로, 사용하는 Hadoop 버전의 설정을 미리 확인하시는 편이 좋습니다. 


다음에도 Hadoop 사용할 때, 경험하기 힘든(?) 에러를 볼 경우, 블로그를 통해서 공유 드리도록 하겠습니다.



[참고 자료]

Posted by recopick

댓글을 달아 주세요

안녕하세요. RecoPick팀의 김성민입니다. 

지난번 DynamoDB vs HBase 비교 분석 포스팅에 이어서 이번에는 DynamoDB의 Write Capacity를 줄이는 방법을 소개해 드리겠습니다. 

Dynamo DB의 장점은 다른 NoSQL(abase, Cassandra, MongoDB 등등)에 비해서 관리 비용이 거의 zero에 가깝다는 것입니다. 
사용자가 특별한 설정을 할 필요도 없고, Read/Write 성능 항상을 위해서 여러 가지 복잡한 설정 옵션을 고려할 필요가 없이, AWS 관리 console에서 몇 번의 클릭만으로 쉽게 관리할 수 있습니다. 

하지만 단점은 Read/Write Capacity 에 따라서 사용 요금이 큰 편차를 보인다는 것입니다.


 

 

 #1

 #2

 #3

 Indexed Data Storage

 Dataset Size (GB)

 1

 1

 1

 Provisioned Throughput Capacity

 Item Size (All attributes) (KB)

 64

 64

 64

 

 Number of items read per second (Reads/Second)

 0

 100

 0

 

 Number of items written per second (Writes/Second)

 250

 250

 500

 Data Transfer

Data Transfer Out (GB/Month)

 10

 10

 10

 

 Data Transfer In (GB/Month)

 10

 10

 10

Estimate of your Monthly Bill ($)

 

 9848.24

 9938.68

 19654.49


Table 1. DynamoDB의 월평균 예상 사용 요금



위의 표는 초당 Write 횟수에 따라서 DynamoDB의 월평균 예상 사용 요금이 비례하고 있음을 보여줍니다. 
위 표처럼, 초당 Write 회수가 250 Writes/Second에서 500 Writes/Second로 증가하면, 비용이 거의 2배로 오르게 됩니다. 

반면에 Read 회수에 따라서는 DynamoDB의 비용이 크게 변화되지 않습니다. 
결국, 초당 Write 횟수, 즉 Write Capacity 설정이 DynamoDB의 사용 비용에 큰 영향을 줍니다. 


하지만 현실적으로 적절한 Write Capacity 값을 찾기는 쉽지 않습니다. 

예를 들어, 아래 그래프와 같이 Write operation이 발생할 경우, 다음 중 가장 적합한 Write Capacity는 얼마일까요?

 
(1) 100 ~ 250 
(2) 250 ~ 500 
(3) 750 ~ 1,500 
(4) 1,500 ~ 

정확한 정답은 없지만, 아래 그래프를 보면, Write 회수가 평균 750 Writes/Second이고, 최대 1,500 Writes/Second이므로 Write Capacity 대략 750 ~ 1,500 정도가 되어야 할 것입니다.




Figure 1. DynamoDB Write Capacity 그래프 



즉, DynamoDB에서는 Write 횟수가 갑자기 증가하는 것을 대비해서 예측 가능한 최대 Write Capacity 만큼 설정해야 합니다. 결국, 순간 최대 Write 횟수에 의해서 DynamoDB의 사용 요금이 큰 영향을 미칠 수밖에 없습니다. 
만약, Write Capacity를 낮게 하게 되면, Write 회수가 순식간에 증가할 경우, write operation이 실패하기 때문에, 데이터를 안전하게 보관할 수 없게 됩니다. 

그렇다면, 어떻게 Write Capacity를 낮추면서, 순간적으로 Write operation이 증가할 경우에도 write operation의 실패 없이 데이터를 안전하게 보관할 수 있을까요? 
여러 가지 방법이 있을 수 있겠지만, 대략 아래와 같은 2가지 방법을 생각해 볼 수 있습니다. 

(1) Write 회수의 증감에 따라서, Dynamo DB의 Write Capacity를 자동으로 조절해 주는 방법 
(2) Write 회수 자체를 항상 일정한 수준으로 유지하는 방법 

1번의 경우, DynamoDB의 Write Capacity 조정 횟수 및 조정 폭이 제한적이고, 변경된 Write Capacity가 DynamoDB에 적용되는데 일정 시간이 걸리기 때문에, 온라인 트랜잭션 처리(OLTP) 시에는 적합하지 않을 수도 있습니다. 

2번째 방법은 Write operation의 속도 지연이 없이 Write 회수 자체를 항상 일정한 수준으로 유지해야 한다는 점이 어렵습니다. Write operation을 수행하는 쪽에서는 DynamoDB의 Write Capacity에 상관없이 항상 원하는 수준으로 빠르게 write operation을 수행할 수 있도록 해야 합니다. 

저희 팀의 경우, Write operation을 수행하는 쪽이 될 수 있는 대로 DynamoDB의 Write Capacity에 의존하지 않고, 최대한 write throughput을 낼 수 있게 하려고 1번보다는 2번 방법으로 DynamoDB의 Write Capacity를 낮추도록 했습니다.


Write 회수 자체를 일정한 수준으로 유지하기 위해서 저희가 생각한 방식은 Producer-Consumer 패턴으로 Write operation을 처리하는 것입니다. 즉, 기존에는 DynamoDB에 바로 write operation을 수행했지만, DynamoDB에 write 을 수행하는 Producer와 DynamoDB 사이에 Queue를 도입해서 Producer는 Queue에 write를 하고, Consumer가 Queue에서 DynamoDB에 write 할 데이터를 꺼내와서 write operation을 수행하는 것입니다. 


기존 방식은 Producer의 write 회수와 속도가 DynamoDB의 Write Capacity를 결정했지만, Producer-Consumer 패턴에서는 
Queue의 대기 시간과 Consumer의 처리 속도 조절해서 DynamoDB의 Write Capacity를 최소화할 수 있습니다. 
그래서, Producer는 기존처럼 빠르게 write를 수행하고, Consumer가 DynamoDB에 일정한 속도로 DynamoDB에 write를 하게 함으로써, 항상 write 회수가 일정한 수준으로 유지될 수 있도록 하는 것입니다.


(a) Producer-Consumer 패턴 적용 전


(b) Producer-Consumer 패턴 적용 후


Figure 2. DynamoDB Write Operation 방식



하지만 Producer-Consumer 패턴을 구현할 때, 다음과 같은 2가지 점이 만족하여야 합니다. 


(1) Queue나 Consumer에서 장애가 발생하더라도 write operation이 자동으로 수행되어야 함. 

(2) Producer의 write 횟수가 갑자기 증가하더라도 producer의 write throughput을 떨어뜨리지 않을 정도로 Queue가 동작해야 하는 함 


위의 2가지 조건을 모두 충족 시키는 Kafka와 Storm을 사용하였습니다.
Kafka는 분산 Queue이기 때문에, 장애 발생 시, 자동으로 fail over가 가능하고, 확장성이 뛰어나기 때문에, 장비만 추가함으로써, write throughput을 높일 수 있습니다. 
Consumer로는 Storm을 사용하였는데, 역시 Kafka와 마찬가지로 장애 발생 시 자동으로 fail over가 가능할 뿐만 아니라, Hadoop과 다르게 실시간 streaming 처리가 가능하므로 Storm을 선택했습니다. 


물론, Kafka-Storm 대신 Amazon SQS와 Kinesis를 사용할 수도 있었지만, 저희 팀에서는 Kafka와 Storm을 사용하면서 이미 성능 검증을 마쳤기 때문에, Kafka와 Storm을 주저 없이 선택할 수 있었습니다. 


Producer-Consumer 패턴에 사용할 Queue와 Consumer로 Kafka와 Storm을 선택한 후, 실제 어떻게 DynamoDB의 Write Capactiy를 조정하기 위해서 아래와 같이 모델링 작업을 했습니다.


(1) DynamoDB Write operation 횟수 /  sec = 평균 Producer의 총 Write 횟수 / Queue 대기 시간 (sec)
(2) Queue 대기 시간 = Consumer의 프로세스 1개의 평균 처리 속도 x Consumer의 프로세스 개수


저희 팀의 요구 사항은 Producer의 초당 write 횟수와 관계없이 Dynamo DB의 초당 write 횟수를 조정하고 싶었기 때문에, 위의 식 (1)에서 평균 Producer의 총 Write 횟수는 고정값으로 하고 Queue 대기 시간을 변동 값으로 해서 초당 Dynamo DB Write 횟수를 원하는 수준으로 낮출 수 있도록 Queue 대기 시간을 추정했습니다. Queue 대기 시간은 위의 식 (2)에서 처럼 Consumer의 처리 속도가 일정할 경우, Consumer 프로세스의 개수에 좌우됩니다. 따라서 Consumer로 사용한 Storm 프로세스의 평균 속도를 측정한 후, Dynamo DB Write Capacity를 낮추기 위해 추정한 Queue 대기 시간에 맞도록 프로세스 개수를 조정했습니다.


이런 방식으로 해서 저희 팀은 DynamoDB의 Write Capacity를 1/3 수준으로 낮출 수 있었습니다.



Figure 3.  DynamoDB Write Operation 방식 변경 후, Write Capacity 그래프 



요약 하자면, Kafka와 Storm을 이용해서 Producer Consumer 패턴으로 DynamoDB의 write operation 방식을 변경해서, write capacity를 1/3로 줄일 수 있었습니다.


참고로 Kafka와 Storm을 이용한 Producer Consumer 패턴의 다른 응용으로는 뉴스나 트위터와 같은 스트리밍 데이터의 트래픽을 regulation 하는데 사용 가능합니다. 혹은 slide share와 같이 사용자가 데이터를 올리는 경우에도 응용 가능합니다.


[참고 사항]
(1) Auto Scale DynamoDB With Dynamic DynamoDB - http://aws.typepad.com/aws/2014/02/auto-scale-dynamodb-with-dynamic-dynamodb.html

(2) Amazon Kinesis - http://aws.amazon.com/ko/kinesis/
(3) Amazon Kinesis: Real-Time Processing of Streaming Big Data - http://aws.typepad.com/aws/2013/11/amazon-kinesis-real-time-processing-of-streamed-data.html

(4) Amazon Simple Queue Service(Amazon SQS) - http://aws.amazon.com/ko/sqs/
(5) SQS: Super Queue Service - http://aws.typepad.com/aws/2007/05/sqs_super_queue.html

(6) AWS Simple Monthly Calculator (AWS 월 사용량 계산기) - http://calculator.s3.amazonaws.com/index.html

(7) Limits in DynamoDB - http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html 


(8) CDP:Job Observer Pattern - http://en.clouddesignpattern.org/index.php/CDP:Job_Observer_Pattern




Posted by recopick
TAG dynamodb

댓글을 달아 주세요