회사에서 새로 만드는 시스템을 크게 몇 개의 컴포넌트로 잘라서 디커플링을 시도했다. 시스템 전체를 흐르는 데이터가 거치는 각 단계마다 투입되는 자원량을 조절할 수 있어야 하며, 머신도 나뉘어야 ... 하고 코드 의존성도 님하 좀 ㅠㅠ
이 과정에서 함수 인자, 혹은 글로벌 자료구조 등으로 공유하던 데이터를 넘겨줄 방법 - 그러니까 IPC - 이 필요하게 되었고, 대부분의 정보 전달을 Queue with Persistent layer 로 대체할 수 있었다. 반응성보다는 단위 시간당 처리량이 중요한 시스템이라서 ...
여러개의 큐 서버가 만들어지고, 여기에 데이터를 넣고 빼고 하게 되는데 이 과정에서 딸려오는 귀차니즘이 소켓통신. 그리고 몇 가지 이유로 인하여 큐 서버를 포함하여 관련되는 코드들이 자바/C/C++/Python 들이 섞일 수 있는 가능성을 가지게 되었다. (사실 이게 디커플링의 의도중 하나이긴 한데 ...)
파이썬만 있다면야 pickling 이라는 편한 방법이 있지만, 다른 언어가 들어오면 조금 골치아파지고... 이런 상황에서 Google protocol buffer 가 떠올라서 가져다 사용. 가장 큰 특징은,
1. Code generation
- 유피넬의 누군가(...)가 세미나에서도 이야기했던 방법인데, Definition data structure -> Code to handle 방향으로 코드를 생성해 버린다. 코드 레벨에서 서버/클라를 포함한 이 데이터를 다루는 녀석들이 모두 공유할 수 있다.
- 코드 생성이기 때문에, 오타라던가 ( ... ) 양쪽 코드의 불일치 등으로 생기는 문제는 거의 제로
- 데이터 정의가 바뀌면 코드 생성->컴파일->링크 를 다시 해야 한다
- 이 툴(그러니까 google protocol buffer)를 도입함으로써 생기는 제약이 별로 없다 - 프레임웍을 가져다 쓰는거에 비하면야...
2. Structured data definition
- 일반적인 클래스 디자인과 비슷하게 데이터를 정의할 수 있다
- 만들어진 데이터 타입들을 조합하여 복잡한 블럭을 만들 수 있다
3. Language support
- 고려중인 Java/C++/Python 세가지 모두 코드를 찍어내준다
- 언어와 상관 없이 이 코드들은 서로 호환되는 데이터를 만든다 (Serialize/Parse 결과가 일치한다는 뜻)
그리고 부가적으로 빠르다, 같은게 있긴 하다. ... 하지만 아직 확인해보진 않았으니 뭐 ... 그래봐야 내가 직접 만든 코드보다 백만배 빠르진 않겠지... 훌륭한 점은 퍼포먼스 저하를 최소한으로 막고(과연 그럴까?는 코드를 까봐야 알겠지만...) 사용하기 편해졌어! 를 확보한 것, 이라고 생각할 수 있는데.
뭐 이 장점들은 확실히 강력한 것 같다. 단점은... Composite type 에 대한 assignment를 막아놓은게 좀 귀찮은데, 이건 내부 구현에 따라오는 이슈인 것 같다. 로직에서 다루는 데이터 타입과 데이터 주고받기에 쓰이는 타입이 다르면 - 로직에서 프로토콜 버퍼 객체를 사용하지 않으면 - Send/Recv 할 때 데이터의 복사가 한번 일어나게 되는데 이건 좀 곤란한 것 같다. ... 하지만, 잘 살펴보면 보내거나 받는 데이터를 Stream으로 만들어서 해결할 수 있을 것 같기도 한데, 지금은 튜토리얼만 슥 보고 대충 가져다 쓰는거라 뭐 ... 복사 없이도 처리가 가능할 것 같긴 하니까 이건 패스.
큰 코드조각은 아니지만, 유용한 걸 잘 만든다. 구현 자체는 논외로 하고, 컨셉이 좋지. (더불어 리카님도 굿 ㅋ) 어느 정도에서 Abstraction layer를 만들어야 사용성을 최소한으로 해치면서 Drawback을 줄일 수 있을지 - 에 대한 감이 좋아 보인다. 이런건 좀 배우자.
def draw(start, center): current = start i = 1 while not close_to_center(current): pos = polar_to_euclid(current) stdscr.addstr(pos[1] + center[1], pos[0]*2 + center[0], "%s" % i, curses.A_NORMAL) current = get_next_coord(current) i += 1
C/C++ 에서는 대입문 - 이 사실 문(statement)이 아니라 식(expression)이다. 따라서 다음 코드가 동작한다.
#include <stdio.h>
int main() { int x = 10; printf("%d\n", x = 20);
return 0; }
실행하면 20 이란는 숫자가 콘솔에 찍힌다. 파이썬에서는 대입문이 맞다. 따라서
print x=10
이런 파이썬 코드는 유효하지 않다. 별 생각 없이 쓰고 있었는데, 파이썬에 대해 아무것도 모르고 있었구나 -_-; ... 뿐 아니라 프로그래밍 언어 자체에 대해 기초가 부족했다. 아 뼈아퍼라...
사실, 문과 식을 구분해서 쓰는 사람이 그다지 많지는 않다(고 느낀다). 나도 가끔 필요할 때만 확인해보곤 하는데 ... 자, 정확히 statement, expression 이 무엇인지 설명한다면? 이라고 하니까 바로 답이 튀어나오지 않는다. 그래서 위키에서 긁어왔다.
In computer programming a statement can be thought of as the smallest standalone element of an imperativeprogramming language. A program is formed by a sequence of one or more statements. A statement will have internal components (eg, expressions). - From wikipedia
An expression in a programming language is a combination of values, variables, operators, and functions that are interpreted (evaluated) according to the particular rules of precedence and of association for a particular programming language, which computes and then produces (returns, in a stateful environment) another value. The expression is said to evaluate to that value. As in mathematics, the expression is (or can be said to have) its evaluated value; the expression is a representation of that value. - From wikipedia
그냥 개인적인 생각이지만, 해보면 쉽다 쉽다 - 하는 함수형 언어를 사람들이 처음 접할때 많이 좌절하곤 하는데 그 이유가 이게 아닐까 한다. Imperative language에 익숙한 상황에서는 statement 의 나열로 문제를 해결하려고 하기 쉬운데, Functional language에서는 그게 쉽지 않다 - 기 보다는 제한적이거나 불가한 경우도 있다. 함수형 언어를 써보면 새로운 접근 방식을 익히게 된다고 하는데, 그 새로운 접근이 뭐냐? 라고 물으면 좀 난감했는데, 일단 이렇게 정리해볼 수 있으려나?
Expression oriented problem solving
Expression은 side-effect가 있을 수도 있고, 없을 수도 있다 - 고 한다. 이에 반해 Statement는 Side-effect를 만들어내기 위한 것이라고도 하는데 (statements do not return results and are executed solely for their side effects, - From wikipedia), side-effect를 지향/지양 하는 정도에 따라서도 접근이 달라질 수 있는 것 같다.
역시 기본부터 차근차근 다져야 하는데, 이런것도 확실히 정리 안해두고 코드를 만지고 있었다니 부끄럽다. :'( 일단 낙서 종료.
side-effect, referential transparency 등에 대해서도 살짝 정리해둬야겠다.
여기서부터는 cursor 를 가지고 놀면 된다. cursor.execute("show tables"), cursor.execute("create table test (id int)") 뭐 이런 식으로 ... 그래서 테스트를 위해서 엔트리 1억개를 insert 하는 코드를 만들었다. 대충 이런 모양이다.
... 29 # Generate a bunch of (from, to) pairs and insert into the table 30 values = [] 31 for x in xrange(0, VN): 32 from = x 33 digest.update(str(x)) 34 to = hash(digest.hexdigest()) % CN 35 values.append( (from, to) ) 36 if from % CHUNK_SIZE == CHUNK_SIZE-1: 37 query = "INSERT INTO info_test (from, to) VALUES " + ", ".join([str(x) for x in values]) 38 cursor.execute(query) 39 values = [ ] ...
그런데... 몇분쯤은 걸리겠지 하는 생각에 엔터를 눌렀는데, 실행이 순식간에 끝난다. 엉? 엔트리 1억개를 넣는건데 1초도 안걸리다니, 요즘 DB가 졸 짱쎄졌구나! 라는 생각에 터미널 창에서 select * from info_test limit 1000; 을 때려 보았다.
mysql> select * from info_test limit 1000; Empty set (0.00 sec)
mysql> _
엉? 뭐지? -_-; 그리고 삽질 시작. (cursor.close(), db.close() 도 밑에서 잘 해 주었다)
한시간쯤 뇌가 날아갔다가, 군것질을 하고 돌아오셨다. 그동안 몇몇 테스트를 해보면서 내린 결론은,
insert만 안된다
는 것. 왜 그럴까... insert는 쓰기 작업이고, 자주 일어나고, 연속적으로 일어나는 경우가 많으니까 아마도 어댑터가 나름 캐싱했을것 같은데 ... -> 빙고!
썅썅봐 -_-; db.close() 앞에 db.commit() 을 넣어주니까 제대로 들어간다. API에 보면,
commit(...)
Commits the current transaction
라고 달랑 한줄 나와있고, 나는 트랜잭션을 쓴 적이 없다. -_-; 아니 저게 없으면 방법당한다고 어디 한마디라도 써놓던가... ㅠㅠ 그리고 예의상(매너?) cursor.close() 나 db.close() 에서 들고 있는 캐시는 다 처리해 주어야 하는것 아님? -_-+
예전에 하둡님도 close 할때 flush 안해주셔서 초큼 난감했었는데, 이거 요즘 유행인가보다.