mysql 의 성능을 향상시켜서 좀더 빠르게 처리하는 방법을 찾던 중입니다.

그 중에서 HandlerSocket 이라고 DeNA 에서 개발한 mysql SQL 쿼리 파싱을 최소화하고,

InnoDB 를 직접 접근하는 형태로 개발된 plugin 이 있습니다.


2009 년 당시에 발표했을 때 성능은 약 7배정도의 향상이 있었습니다.

참고: http://yoshinorimatsunobu.blogspot.kr/search/label/handlersocket


이 글은 HandlerSocket 통신 프로토콜을 간략하게 한글로 번역해둔 것입니다.

원본: https://github.com/DeNA/HandlerSocket-Plugin-for-MySQL/blob/master/docs-en/protocol.en.txt


HandlerSocket 프로토콜



<기본 문법>


라인 단위로 식별된다.

각 라인은 LF(0x0a) 로 끝난다.

각 라인은 HT(0x09) 토큰으로 식별되는 집합으로 구성된다.

토큰은 NULL 이거나 인코딩된 문자열이다.

NULL 과 empty string 은 식별해야 한다.

NULL 은 NULL(0x00) 으로 표현된다.


인코딩된 문자열은 다음과 같은 규칙을 따른다.

* 0x10 ~ 0xff 그 자체로 인코딩되어 있다.

* 0x00 ~ 0x0f 는 앞에 0x01 가 있고, 0x40 쉬프트된다.

  예를 들면 0x03 은 0x01 0x43 으로 인코딩된다.


문자열은 비어 있을 수 있다.

연속으로 0x09 0x0a 이라면 각 라인에 empty string 이 있다.




<요청과 응답>


단순한 요청/응답으로 구성된다.

접속한 뒤에 클라이언트는 요청을 보내고, 서버는 응답을 보낸다.

요청/응답은 한 라인으로 구성된다.

요청은 중첩될 수 있다.

한번에 여러 요청을 보내면, 한번에 여러 응답을 받는다.





<인덱스 열기>


'open_index' 요청은 다음과 같다.


P <indexid> <dbname> <tablename> <indexname> <columns> [<fcolumns>]


<indexid> 는 10진수이다.

<dbname> <tablename> <indexname> 은 문자열이다.

일차키(primary key)를 열기 위해서는 <indexname> 에 PRIMARY 로 쓴다.

<columns> 는 여러 컬럼명을 콤마로 구분한다.

<fcolumns> 는 여러 컬럼명을 콤마로 구분한다. 이것은 선택적이다.


'open_index' 요청을 한 뒤에는 HandlerSocket 플로그인은 해당 인덱스를 열고

접속이 끊길 때까지 유지한다.


각 open_index 는 <indexid> 로 식별하고,

<indexid> 를 이미 열었다면 예전 open_index 는 닫는다.


같은 <dbname> <tablename> <indexname> 에 여러 번

각기 다른 <columns> 를 사용할 수 있다.


효율성을 위해서 <indexid> 를 가급적 적게 써라.




<데이터 얻기>


'find' 요청은 다음과 같다.


<indexid> <op> <vlen> <v1> ... <vn> [LIM] [IN] [FILTER ...]


LIM 다음과 같은 인자들이다.


<limit> <offset>


IN 은 다음과 같은 인자들이다.


@ <icol> <ivlen> <iv1> ... <ivn>


FILTER 는 다음과 같은 인자들이다.


<ftyp> <fop> <fcol> <fval>


<indexid> 는 숫자다. 'open_index' 에 명시된 숫자다.


<op> 는 비교 연산자이다. 현재는 '=' '>' '>=' '<' '<=' 를 지원한다.

<vlen> 뒤에 오는 <v1> ... <vn> 인자의 길이이다.

이 값은 'open_index' 요청의 <indexname> 에 해당되는 인덱스 컬럼 갯수보다 작거나 동일하다.


<v1> ... <vn> 은 인덱스 컬럼 값이다.


LIM 은 선택적이다. <limit> <offset> 은 숫자다. 생략하면 1 0 으로 명시된 것으로 동작한다.

이 인자는 SQL 의 LIMIT 와 유사하다.

이들 값은 필터에 의해 무시된 레코드 갯수를 포함하지 않는다.


IN 은 선택적이다.  SQL 문법에서 WHERE ... IN 과 유사하다.

<icol> 는 <indexname> 의 인덱스 컬럼 갯수보다 작아야 한다.

IN 이 명시되면 <v1> ... <vn> 값에서 <icol> 번째 파라미터가 무시된다.


FILTER 는 선택적이다. FILTER 는 필터를 명시한다.

<ftyp> 는 'F' (filter) 또는 'W' (while) 이다.

<fop> 은 비교 연산자이다.

<fcol> 은 'open_index' 에서 <fcolums> 에 해당되는 컬럼 갯수보다 작아야 한다.

여러 개의 필터가 지정되면, 그것들을 논리적으로 AND 로 동작한다.

'F' 와 'W' 의 차이는 지정된 조건의 레코드를 찾지 못했을 때 'F' 는 레코드를 무시하고,

'W' 는 찾는 것을 멈춘다.




<데이터 갱신/삭제>


'find_modify' 요청은 다음과 같다.


<indexid> <op> <vlen> <v1> ... <vn> [LIM] [IN] [FILTER ...] MOD


MOD 는 다음과 같은 인자들이다.


<mop> <m1> ... <mk>


<mop> 는 'U' (update) '+' (increment) '-' (decrement) 'D' (delete) 'U?' '+?' '-?' 'D?' 이다.

뒤에 '?' 명시되면 레코드가 변경되지 전의 내용을 반환한다.

명시되지 않으면 변경된 레코드의 갯수를 반환한다.


<m1> ... <mk> 설정할 컬럼 값들을 명시한다.

<m1> ... <mk> 길이는 'open_index' 의 <columns> 에 명시된 길이보다 작거나 같아야 한다.

<mop> 가 'D' 이면 <m1> ... <mk> 는 무시된다.

<mop> 가 '+' 또는 '-' 이면 값은 숫자이어야 한다.

<mop> 가 '-' 이고 컬럼 값이 음수에서 양수 또는 양수에서 음수로 바뀔 때는 값이 바뀌지 않는다.




<데이터 삽입>


'insert' 요청은 다음과 같다.


<indexid> + <vlen> <v1> ... <vn>


<vlen> 는 뒤에 오는 <v1> ... <vn> 의 길이다.

'open_index' 에 지정된 <columns> 의 길이보다 작거나 같아야 한다.


<v1> ... <vn> 는 설정된 컬럼 값을 지정한다.

<columns> 에 없는 컬럼은 기본값으로 설정된다.




<인증>


'auth' 요청은 다음과 같다.


A <atyp> <akey>


<atyp> 는 '1' 이어야 한다.


'auth' 요청은 <akey> 값이 'handlersocket_plain_secret' 또는 'handlersocket_plain_secret_rw' 에

정확하게 지정되어야 한다.


인증이 활성화된 상태에서 'auth' 요청 이전에 다른 요청은 모두 실패한다.




<응답 문법>


각 요청에 대해서 다음과 같은 문법으로 응답한다.


<errorcode> <numcolumns> <r1> ... <rn>


<errorcode> 는 요청이 성공인지 실패인지 알려준다.

'0' 은 성공이고, 그 외는 에러이다.


<numcolumns> 는 결과 세트의 컬럼 갯수를 나타낸다.


<r1> ... <rn> 은 결과 세트이다. <r1> ... <rn> 길이는 <numcolumns> 의 배수이다.

<r1> ... <rn> 이 비어있을 수도 있다.


<errorcode> 가 0 이 아니면, <numcolumns> 는 항상 1이고 <r1> 은 사람이 읽을 수 있는

에러 메시지이다. 가끔은 <r1> 이 없을 수도 있다.




<'open_index' 에 대한 응답>


'open_index' 가 성공하면 다음을 반환한다.


0 1




<'find' 에 대한 응답>


'find' 가 성공하면 다음을 반환한다.


0 <numcolumns> <r1> ... <rn>


<numcolumns> 는 'open_index' 의 <columns> 의 길이와 동일하다.


<r1> ... <rn> 은 결과 세트이다. N 열을 찾으면 <r1> ... <rn> 길이는 <numcolumns> * N 이 된다.





<'find_modify' 에 대한 응답>


'find_modify' 가 성공하면 다음을 반환한다.


0 1 <nummod>


<nummod> 는 변경된 열의 갯수이다.


예외로 <mop> 에 '?' 를 지정하면, 응답 결과는 ' find' 에 대한 응답이 된다.




<'insert' 에 대한 응답>


'insert' 가 성공하면 다음을 반환한다.


0 1




<'auth' 에 대한 응답>


'auth' 가 성공하면 다음을 반환한다.


0 1



최근에 제가 이 프로토콜을 직접 C++ 로 구현해서 성능을 측정해본 결과

mysql SQL 로 하나, HandlerSocket 을 이용하나 성능차이는 미비했습니다.

이에 대한 기록은 나중에 별도로 정리하도록 하겠습니다.


성능에 대한 참고:


Posted by 집시F