태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

'2012/08'에 해당되는 글 3건

  1. 2012.08.23 Amazon glacier ... 를 쓸 뻔 했던 이야기
  2. 2012.08.10 회사일 하면서 삽질하거나 느낀거 끄적 (2)
  3. 2012.08.02 도깨비: 요청/응답 json 포맷 사용 + mongodb 저장소 붙이기 (6)

Amazon glacier ... 를 쓸 뻔 했던 이야기

Tech 2012. 8. 23. 23:03

오늘은 평소에 삽질하던거랑 상관 없는 단독삽질. Amazon glacier를 써본다.

1. What is glacier?
아마존의 Archiving storage service. 저장 용량에 비해 매우 싼 대신, 트래픽 비용이 있고 꺼내오는 것도 실시간이 아니다.

참고자료:
http://www.zdnet.co.kr/news/news_view.asp?artice_id=20120822145556
http://aws.amazon.com/ko/glacier/

내가 개인 서버를 꾸려서 archive 디렉토리에 넣어둔 데이터가 대략 200G 정도 되는데, 이걸 glacier에 던져두고 싶다.
요금은 한달에 3천원 정도 예상한다. (미국 동부 0.01 USD/GB/MONTH 기준)

2. Create a vault
Amazon AWS 계정이 필요하다. 웹 콘솔에서 Services -> Glacier 를 클릭하면 결제정보가 없는 경우 payment method 를 확인한다고 뜨고, 결제 정보가 잘 들어가 있는 경우 Create vault 버튼이 덩그라니 보인다.
Vault 는 아카이브를 모으는 창고 같은 개념이다. 난 DGoonStorage 라는 이름의 vault 를 만들었다.
왼쪽 위 Region 에서 적절한 장소를 고르는걸 잊지 말자. 난 제일 싼 미국 동부(…)로 했다.
귀찮으니 Notification 설정 따위는 그냥 다 스킵.
Vault를 생성하고 나면 뭔가 보인다 …
하지만 여기서 바로 업로드를 할 수는 없다. Glacier는 콘솔에서 Vault생성/리스트업 이외의 작업이 불가하다.
파일을 업로드/다운로드하는 것 모두 "프로그래밍" 이 필요하다. 그게 내가 이런 쓰잘데기 없는 문서를 쓰는 이유이기도 하고…

3. Unit
Glacier에 저장되는 단위는 위에서 생성한 Vault, 그리고 Archive, 마지막으로 File 이 있다.

Archive는 하나 혹은 여러개의 File을 묶는 단위이다. Archive에는 Unique identifier가 발급되며 업로드/다운로드의 단위가 된다.
한 번의 업로드 요청으로 업로드 가능한 것은 4GB 까지. Archive 하나의 최대 용량은 40TB.
큰 Archive는 Multipart 요청으로 여러개의 조각으로 쪼개서 올려야 한다는 것.
그리고 Archive는 일단 정해지면 추가나 덮어쓰기가 불가하다. 삭제하고 다시 올리는 수 밖에 없다.
+ 올리고 3개월 이내, 혹은 업로드 중에 그만두고 삭제하면 '삭제비용'이 따로 든다!

Vault는 여러개의 Archive를 묶는 단위이다. Upto 1000 vaults per account per region 이라고 한다.
Archive가 하나도 없는 Vault만이 삭제가 가능하므로 Vault를 지우려면 안에 있는 Archive를 먼저 지워야 한다.

4. Amazon API
Python Boto 라는 훌륭한 라이브러리가 있다. 예전에 API 세팅할 때에는 자바로 삽질하느라 짜증났는데, 세상 좋아졌다.
하지만 Glacier는 비교적 최신에 릴리즈된 서비스라서 아직 boto 에 구현이 되어 있지 않다.

BOTO 관련 링크:
https://github.com/boto/boto
http://docs.pythonboto.org/en/latest/index.html

하지만 boto 베이스로 glacier upload/download 를 구현한 코드들이 gist 어딘가를 떠돌고 있으니(https://gist.github.com/3426839, https://gist.github.com/3424129), 조만간에 boto 에서 지원하리라 생각한다.
boto 설정을 위해서는 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 두 개가 필요하다. 이것은 웹 콘솔에서

Security credentials -> Access Keys

에서 보거나 생성할 수 있다. 이걸 boto 라이브러리에게 전달하는 방법은 설정파일, 환경변수, connect 할때 직접 등 여러가지가 있다.
나는 ~/.boto 파일에

[Credentials]
aws_access_key_id = <MY_ACCESS_KEY>
aws_secret_access_key = <MY_SECRET_KEY>

이렇게 적어넣는 방법을 사용했다. boto 라이브러리는 pip install boto 로 설치 가능하다.



나도 일단 boto 위에 만들어보려고 하는데, glacier api reference 찾는게 좀 어렵다. 검색해도 잘 안나오네. 검색해서 찾는데 30분이나 걸렸다.

Developer guide: http://docs.amazonwebservices.com/amazonglacier/latest/dev/
API Ref for glacier: http://docs.amazonwebservices.com/amazonglacier/latest/dev/amazon-glacier-api.html

금방 될 줄 알았는데, 일단 문서를 읽어봐야 할테니 시간 좀 걸리겠네 …
boto 에 추가될때까지 기다리는게 더 빠를지도!



5. Epilogue

잠시 검색 후: issue 를 보니,

https://github.com/boto/boto/issues/929
https://github.com/boto/boto/tree/glacier

이렇게 벌써 브랜치가 생겨있다. 빠르네 ㅋㅋㅋ

아, 회사 일 할게 많아서 오늘은 삽질을 별로 못했다. 남은 시간은 boto 라이브러리(와 glacier 브랜치) 코드나 읽으며 보내야겠다. boto는 회사일에도 유용할 듯. 자바 껒여.



'Tech' 카테고리의 다른 글

Amazon glacier ... 를 쓸 뻔 했던 이야기  (0) 2012.08.23
회사일 하면서 삽질하거나 느낀거 끄적  (2) 2012.08.10
D2, array.reverse property has a side-effect. orz  (45) 2011.07.12
Thrift in D  (6) 2011.05.15
fix: ugly gitweb page  (3) 2010.12.12
(1/2) Programming in clojure  (4) 2010.08.08
Trackbacks 0 : Comments 0

Write a comment


회사일 하면서 삽질하거나 느낀거 끄적

Tech 2012. 8. 10. 13:54

요새 <종료되지 않고 계속 실행되며 모종의 일을 하거나, 서비스를 하는 프로세스> 들을 만들고 있다.

초보 프로그래머 디군은 참으로 많은 삽질을 하였는데, 실수를 다시 하지 않기 위해서 몇 가지 끄적여둔다.


1. 커널파라미터와 limits를 확인
/etc/security/limits.conf, /etc/sysctl.conf
특히 nofile 때문에 방법당하는 경우가 많다.

2. 리밋은 리밋일 뿐
  하드리밋이 풀려 있어도 돌고 있는 프로세스 자체에 적용된 값은 다를 수 있다.
  쉘에서 ulimit -a 로 확인했어도 프로세스 실행 계정이 다른 경우 쉘에서 보이는 것과 다른 값으로 뜰 수 있다.
  프로세스 자체적으로 getrlimit 으로 확인하고, setrlimit 으로 필요한 리소스를 확보해야 한다.
  필요한 만큼의 리소스를 얻지 못한다면 경고를 띄우거나 실행을 거부해도 좋아 …
  실행중인 프로세스의 정보는 cat /proc/[PID]/limits 로 볼 수 있다.

3. 타임아웃
  클라이언트에서 접속을 끊었어도 서버에서 callback 걸고 기다리는 중에 모를 수 있다.
실제로 오래 기다리는 경우도 발생하기 때문에 timed_async_read 에 타임아웃 5천초를 주었는데,
다른쪽 엔드포인트에서 접속 끊은걸 5천초동안 모르고 있는 상황이 발생 -> CLOSE_WAIT 가 쌓인다...
idle 대기일 때도 적절히 소켓 너머가 살아있는지 확인해줄 필요가 있다.
그 외에도 어디에서든 IO가 생기는 경우에 타임아웃 없이 기다리는 일은 절대 없도록 한다.

4. 설명 못하면 새는거야
코드에서 new/delete 만 모아놓고 살펴봐도 자명한 메모리 릭이 보이는 경우가 많다.
new/delete 뿐 아니라 모든 경우에 해당하는데, 할당된 자원이 언제 어떻게 해제되는지
빈틈없이 설명할 수 없다면 자원이 새거나 프로그래머의 정신이 새거나 둘 중 하나다. RAII.

5. 민폐 프로세스 방지
본인 코드의 메모리 사용에 대해 100% 확신하더라도, 프로세스 자체적으로 메모리 사용량을 주기적으로 확인해서
문제가 생긴다면 자살을 하거나 로그에 경고를 남기거나 등등 모종의 조치를 취할 필요가 있다.
사용중인 모든 라이브러리들이 메모리 누수가 없다고 자신할 수 있는가?
혼자 메모리 다 처묵처묵해서 머신 통째로 방법하는 일은 피하자.

6. 최적화?
O 올바르게 동작하는 느린 코드를 빠르게 만든다
X 빠르게 동작하지만 문제가 있는 코드를 올바르게 만든다.
글로벌 락을 써도 좋으니까 일단 올바르게 동작하는 코드를 만들어라. 가능하면 이를 검증하는 테스트를 만들어라.
성능이 아무리 중요해도, 제발 바보같은 애를 먼저 만들라고!
근데 성능이 정말 중요한 부분에서 부분에서 스핀락이 느려서 어쩌고 등의 걱정을 한다면 설계부터 다시 고민해봐 임마.
premature optimization is the root of all evil

7. core dump 는 신의 축복

8. 저장소에 대고 수다를 떨어
작업을 하다가 a. 빌드에 성공, b. 테스트 통과, 둘중 한가지라도 되면 작업 브랜치에 바로 커밋해라.
여러 변경이 한번에 묶여서 커밋된 것 보다는 쓸데없이 많은 커밋이 차라리 낫다.

9. 이름 잘 짓자
예를 들어 timestamp 를 ts 로 줄여서 변수명에 붙이고 있는데,
if (ts < client_ts) … 이런게 들어가면 나중에 고생한다. 비교문에 들어가는 변수명들은 가능하면 대등하게 한다.
server_ts vs client_ts 라던가 current_ts vs client_ts 라던가 …

변수 이름을 보고 그 정확한 의미를 자명하게 알 수 없다면 이름을 바꿔라.

생각의 체계에서 "자명한 블럭"이 어느 높이에 있는지는 매우매우 중요하다.

+ 조건을 if 에 바로 넣지 말고 true/false 리턴하는 "좋은 이름의 함수"로 분리하고, 테스트를 열심히 하자.


10. c++ 테스팅 프레임웍은 짜증난다

boost-python 이 컴파일은 느리지만(... 애도) 붙이긴 쉽다. 그냥 boost-python 으로 파이썬 바인딩 하고 파이썬 unittest 로 테스트 만들어도 쓸만하다.


11. 로그 및 콘솔출력

백그라운드 프로세스는 상태 확인이 쉽지 않다. syslog 같은 곳에 적절히 떨궈도 좋다. 다른 로그랑 섞이면 헷갈리니까 local0 같은걸로 파일 분리해서 남겨도 좋다. 여튼 어떻게든 상태 확인할 길이 필요하다. tmux나 screen세션에 띄워두는 경우도 간혹 생기는데 개발 단계에서만 하자. tmux, screen이 segfault를 내면서 세션에 있던 프로세스 모두 다 들고 자폭하는 경우가 꽤 있다. nohup 으로 돌려 놓는 경우에 콘솔 출력이 많다면 nohup.out 이 하드디스크를 모두 다 채우고 문제가 생길 수 있다. ... 몇달을 돌려둬도 로그파일이 하드디스크를 다 채울 일이 없어야 한다. 오래된 애를 어딘가 아카이빙 서버로 보내고 지우거나 ...


'Tech' 카테고리의 다른 글

Amazon glacier ... 를 쓸 뻔 했던 이야기  (0) 2012.08.23
회사일 하면서 삽질하거나 느낀거 끄적  (2) 2012.08.10
D2, array.reverse property has a side-effect. orz  (45) 2011.07.12
Thrift in D  (6) 2011.05.15
fix: ugly gitweb page  (3) 2010.12.12
(1/2) Programming in clojure  (4) 2010.08.08
Trackbacks 0 : Comments 2
  1. etermory 2012.08.10 15:02 Modify/Delete Reply

    확신이 간다면 라이브러리를 의심하라!

    • Favicon of http://blog.dgoon.net dgoon 2012.08.10 16:36 Modify/Delete

      의심이 가도 소스코드에 접근할 수 없는 라이브러리면 낭패임.

Write a comment


도깨비: 요청/응답 json 포맷 사용 + mongodb 저장소 붙이기

Daily life/Hard study 2012. 8. 2. 22:20

지난번에 작업 - http://blog.dgoon.net/449 - 하다 말았던 부분을 이어서 한다. 도깨비에 추가로 할 작업은 다음 두가지이다.

1. 일단 요청/응답 포맷을 json으로 바꾼다.
2. persistent layer를 붙인다(mongodb?)

-----
query string 이나 parameter 는 버린다.
POST 로 요청을 받고 -> Content-type 이 application/json 인지 확인하고 -> body 를 읽어서 json.loads 를 한다.
요청이 가져야 하는 값들은 여기서 나오는 json 에 모두 포함되어 있어야 한다. 몇 가지 사소한 것들을 적어둔다.

a. 요청 타입(POST/GET)은 지난번에 @require_http_methods("POST") 로 강제했다
b. content-type은 request.META.get('CONTENT-TYPE') 에 들어있다.
c. body는 request.read() 를 하면 나온다

필수 데이터가 들어있는지 확인하는 코드(check_required)와 content-type을 확인하는 코드(check-content-type)을 함수로 분리하면 더미 로직은 get/put 둘 모두,

        check_content_type(request, "application/json")
        body = request.read()
        req_dict = simplejson.loads(body)
        check_required(GET_REQUIRED_FIELDS, req_dict.keys())
        print req_dict


이 정도로 간단해진다. 조건이 맞지 않는 경우 check_required, check_content_type 함수들은 직접 예외를 생성해서 던진다. 테스트 요청을 날리던 get.py, put.py 에서는 content-type 만 application/json 으로 바꾸고, urlencode 하던 부분을 simplejson.dumps 로만 바꾸면 된다.

{'app_key': 'deadbeef', 'condition': {}}
[02/Aug/2012 21:06:05] "POST /api/v1/get/ HTTP/1.1" 200 2
{'content': 'THIS_IS_CONTENT', 'tag_list': ['tag1', 'tag2', 'tag3'], 'meta': {}, 'app_key': 'deadbeef'}
[02/Aug/2012 21:06:16] "POST /api/v1/put/ HTTP/1.1" 200 2

이전보다 코드도 짧아지고, tag_list 도 리스트로 제대로 들어온다. 에잉 훨 낫네.

-----
persistent layer를 붙인다. 전에 튜토리얼을 따라가본 몽고디비를 쓰고 싶다. 그때는 c++ 클라이언트였는데, 파이썬이라고 뭐 딱히 다르진 않지 싶다. 더 쉽겠지...

http://www.mongodb.org/display/DOCS/Drivers -> http://www.mongodb.org/display/DOCS/Python+Language+Center

여기서 찾아보면 몇 가지 라이브러리가 있다. PyMongo 를 선택. 일단 언제나처럼 pip,easy_install,apt-get 순서로 패키지가 있나 뒤져본다.

sudo pip install pymongo

빙고. 파이썬 쉘 띄워서 import pymongo 해보니 뭔가 임포트 된다. 이걸 쓰면 되겠다.

http://api.mongodb.org/python/current/tutorial.html

여기 튜토리얼이니 잠깐 읽어보면 어렵지 않아요~ 지난번에 삽질하면서 katy 에 몽고디비 설치는 해 두었으니 그대로 활용하자. db=tokebi, collection=twit 으로 간다. store_mongodb.py 를 만들고, MongoStore 클래스에 몽고디비 접근 구현 코드를 둔다. 인터페이스는 mongo_store.get/put 이며 api_get, api_put 이 받은 요청을 거의 그대로 받는다.


api_get:
        app_key, condition = get_items(req_dict, GET_REQUIRED_FIELDS)             
        store = MongoStore('twit')
        retrieved = store.get(app_key, condition)

api_put:
        app_key, content, tag_list, meta = get_items(req_dict, PUT_REQUIRED_FIELDS)
        store = MongoStore('twit')
        ack = store.put(app_key, content, tag_list, meta)

api_get, api_put 에 각각 위의 코드가 추가되었다. 테스트 요청을 날려보면서 디버깅을 좀 해야 했는데, python get.py localhost 2223 > ~/public_html/get_error.html 등으로 저장한 뒤에 브라우저로 열어보면 좀 쉽다. 몽고디비에 쓰고 읽을 때 필요한 자료구조는 뭘까 하고 튜토리얼을 봤더니,

In PyMongo we use dictionaries to represent documents. ...

저장 단위가 파이썬 맵이므로 구현은 아주 간단하다. 심지어 문서를 꺼내오는 조건도 사전이므로,몽고디비 접근은 거의 자명하다.

#-*- encoding: utf-8 -*-from pymongo import Connection
CONN = Connection()

def doc2dict(d):
    ret = dict(d)
    ret['_id'] = str(ret['_id'])
    return ret

class MongoStore(object):
    def __init__(self, collection):
        self.collection = CONN.tokebi[collection]

    def get(self, app_key, condition):
        cond = condition.copy()
        cond['app_key'] = app_key
        return map(doc2dict, self.collection.find(condition))

    def put(self, app_key, content, tag_list, meta):
        doc = {
            'app_key': app_key,
            'content': content,
            'tag_list': tag_list,
            'meta': meta,
        }
        return self.collection.insert(doc)


api_get, api_put 에서 응답을 적절히 json 포맷으로 만들어 준다면 테스트 요청을 날리는 get.py 에서는 여러개의 문서 리스트를 응답으로 얻게 된다. 받은 결과를 화면에 찍어보면,

dgoon@katy:~/works/tokebi/sample_query$ python get.py localhost 2223
{'Content-type': 'application/json', 'Accept': 'text/plain'}
{'condition': {}, 'app_key': 'deadbeef'}
0
        content THIS_IS_CONTENT
        _id 501a74c272653e2217c9b255
        meta {}
        app_key deadbeef
        tag_list ['tag1', 'tag2', 'tag3']
1
        content THIS_IS_CONTENT
        _id 501a74d472653e222068f2b8
        meta {}
        app_key deadbeef
        tag_list ['tag1', 'tag2', 'tag3']
2
        content 두만강푸른물에
        _id 501a750372653e222068f2b9
        meta {}
        app_key deadbeef
        tag_list [u'\uac15\uc0b0\uc5d0', u'\ub178\ub798']
3
        content 티비유치원 하나둘셋
        _id 501a768072653e225e6cb3c1
        meta {}
        app_key deadbeef
        tag_list ['hana', 'dul', 'set']


이렇게 확인할 수 있다. 한글도 잘 들어가는 것을 확인할 수 있다. 2번 문서의 tag_list에 들어있는 것은,

>>> for k in [u'\uac15\uc0b0\uc5d0', u'\ub178\ub798']:
...     print k
...
강산에
노래

이렇게 유효한 한글이다. PyMongo 튜토리얼에서 설명되어 있는데 - BSON strings are UTF-8 encoded 라고 한다. Unicode문자열은 encode 되어 저장된다고. 그리고 simplejson 으로 직렬화 할때는 다시 유니코드가 되어서 응답을 받은 쪽에서는 한글이 모두 유니코드로 떨어지게 된다. 나중에 좀 헷갈릴 것 같다.


-> http://git.dgoon.net/?p=tokebi.git;a=commit;h=da49398646c0812a0d33d71cbec23e5052f5ca09


-----

추가적으로 해야 하는 작업은 ... 잊고 있었는데, 나 말고 다른 사람도 이걸 쓰게 하려면


1. user_id 같은걸 넣어야 하고,


최신 엔트리를 가져오려면,


2. created_at 이 필요하고,


사람을 가리거나 정렬이 필요하니


3. 적절한 인덱스 가 필요하다.


다음 작업은 이상 3개가 되겠다.

Trackbacks 0 : Comments 6
  1. Favicon of http://ub.cheapshoesel.com/ nike shoes 2013.04.08 05:44 Modify/Delete Reply

    우리가 어디에 있는가가 중요한 것이 아니라 어디로 가야 하느냐가 중요한것이다

  2. Favicon of http://ntu.contact-hotel.com/longchamp.php sac longchamp 2013.04.13 20:52 Modify/Delete Reply

    매우 지원, 아주 좋아, http://ntu.4dmv.com/montblanc.php mont blanc pens.

  3. Favicon of http://www.todsoutletonlinexx.com/ tods shoes 2013.04.23 18:22 Modify/Delete Reply

    당신은 내가사랑할 만한 사람이 아니예요,사랑하지 않으면 안될 사람이예요.

  4. Favicon of http://frk.hairstraightenernx.com ghd straightener 2013.04.29 03:30 Modify/Delete Reply

    좋으면 좋고 싫으면 싫은 거지, 뭐가 이렇게 어렵고 복잡하냐구

  5. Favicon of http://frk.shoesxstorex.com/ jordan 18 2013.04.29 08:16 Modify/Delete Reply

    좋으면 좋고 싫으면 싫은 거지, 뭐가 이렇게 어렵고 복잡하냐구

  6. Favicon of http://2962sacresgion511.com/ChicagoBlackhawksjersey.php Chicago Blackhawks Jersey 2013.07.15 18:36 Modify/Delete Reply

    태양이 바다에 미광을 비추면,나는 너를 생각한다.

Write a comment