MySQL client/server protocol 개요: 패킷, result set, prepared protocol
MySQL client/server protocol에서 handshake, packet framing, text result set, prepared statement binary protocol이 어떻게 동작하는지 운영 관점에서 정리한다.
1. 왜 MySQL protocol을 DBA가 알아야 하는가
MySQL 성능 문제를 논할 때 많은 팀은 SQL 실행 계획, InnoDB buffer pool, 락 경합, 인덱스 설계부터 떠올린다. 그러나 실제 운영 장애에서는 SQL 옵티마이저보다 더 아래 계층에서 문제가 시작되는 경우가 적지 않다. 애플리케이션이 갑자기 Packets out of order, MySQL server has gone away, Packet too large, Lost connection during query, Malformed packet 같은 오류를 내거나, 쿼리 자체는 단순한데도 응답 시간이 비정상적으로 늘어나는 상황이 대표적이다.
이때 필요한 관점은 “어떤 SQL이 느린가”만이 아니다. 클라이언트가 MySQL과 어떤 protocol로 대화하고, 그 대화가 어떤 패킷 구조와 왕복 흐름을 가지는가를 함께 이해해야 한다. 이 관점이 있으면 다음 문제를 더 정확히 분류할 수 있다.
- 지연의 원인이 SQL 실행 시간인지, 네트워크 왕복 횟수 증가인지 구분할 수 있다.
max_allowed_packet초과, 대형 result set 전송, BLOB/CLOB 처리 문제를 애플리케이션 버그와 분리해 해석할 수 있다.- text protocol과 prepared statement binary protocol의 차이를 연결 풀, statement cache, 재시도 로직과 함께 해석할 수 있다.
- 프록시, 로드밸런서, Aurora endpoint, RDS Proxy, ProxySQL 같은 중간 계층이 세션 상태에 어떤 제약을 주는지 판단할 수 있다.
- 장애 직후 대량 재연결이 발생할 때 handshake, authentication, prepare 재생성이 왜 함께 튀는지 설명할 수 있다.
결국 MySQL protocol은 개발자만의 관심사가 아니다. 세션 수명주기, 네트워크 비용, 결과 전송량, prepared statement 재사용성, failover 후 복구 시간을 해석하는 운영 언어이기도 하다.
2. 큰 그림: MySQL protocol은 요청/응답 패킷의 연쇄다
MySQL client/server protocol은 추상적으로 보면 매우 단순하다. 연결이 성립되면 서버와 클라이언트는 명령(command) 과 응답(response) 을 주고받는다. 다만 이 단순한 흐름 안에 다음 층이 겹쳐 있다.
- TCP 연결: 운영체제 수준의 소켓 연결이다.
- Initial handshake: 서버가 자신의 capability와 인증 방식을 알리고, 클라이언트가 응답한다.
- 명령 패킷 전송:
COM_QUERY,COM_STMT_PREPARE,COM_STMT_EXECUTE,COM_QUIT같은 command가 오간다. - 응답 패킷 전송: OK packet, ERR packet, result set metadata, row data packet이 돌아온다.
- 세션 유지 또는 종료: 연결 풀 환경이면 세션이 살아 있고, 짧은 연결 환경이면 즉시 닫힌다.
핵심은 MySQL이 “SQL 문자열 하나를 보내고 문자열 하나를 받는” 막연한 구조가 아니라는 점이다. 실제로는 여러 개의 protocol packet 이 순서대로 왕복하며, 그 수와 크기와 순서가 성능과 장애 양상에 직접 영향을 준다.
sequenceDiagram
autonumber
participant C as Client / Connector
participant S as MySQL Server
S->>C: Initial Handshake Packet
C->>S: Handshake Response
S->>C: OK Packet or ERR Packet
C->>S: Command Packet (COM_QUERY 등)
alt text protocol result
S->>C: Column Count
S->>C: Column Definition packets
S->>C: Row packets
S->>C: OK Packet
else prepared statement protocol
C->>S: COM_STMT_PREPARE
S->>C: Statement metadata
C->>S: COM_STMT_EXECUTE
S->>C: Binary Row packets / OK Packet
end
C->>S: COM_QUIT 또는 socket close
이 그림에서 운영자가 반드시 기억해야 할 사실은 두 가지다.
- 하나의 논리적 SQL 실행이 여러 packet 왕복으로 쪼개질 수 있다.
- prepared statement를 사용하면 SQL text를 계속 보내는 구조와 다른 경로가 열린다.
3. 패킷 관점의 핵심: 길이, sequence id, command byte
MySQL protocol packet은 대체로 “헤더 + payload” 구조를 가진다. 운영자가 모든 바이트 레이아웃을 외울 필요는 없지만, 다음 세 개념은 알아 두는 편이 좋다.
3.1 packet length
MySQL은 각 packet의 payload 길이를 보고 수신 경계를 판단한다. 따라서 애플리케이션이 매우 큰 BLOB, 긴 SQL 문자열, 거대한 multi-row insert, 대형 result set을 다룰 때는 단순 SQL 문제를 넘어 packet 크기 문제가 된다.
이때 대표적으로 연관되는 설정이 max_allowed_packet이다. 이 값은 너무 큰 단일 packet 또는 조립 결과를 거부하는 보호 장치로 이해하는 편이 안전하다. 실제 운영에서는 다음 상황에서 자주 문제가 된다.
- 대형 JSON/BLOB/CLOB를 한 번에 넣거나 읽을 때
- ORM이 지나치게 긴
IN (...)조건이나 대형 batch insert를 만들 때 - mysqldump/restore, ETL, CDC 초기 적재 작업에서 매우 큰 row payload가 발생할 때
- 애플리케이션과 서버의 packet 한도 설정이 서로 다를 때
3.2 sequence id
MySQL packet에는 같은 요청/응답 흐름 안에서 순서를 나타내는 sequence id가 붙는다. 정상 상황에서는 클라이언트와 서버가 같은 흐름을 같은 순서로 이해한다. 그러나 드물게 중간 프록시 버그, 잘못된 connector 상태, socket reuse 오류, 손상된 응답 처리 등이 개입하면 Packets out of order 같은 문제가 나타날 수 있다.
운영 관점에서 중요한 점은, 이 오류를 단순 네트워크 단절로만 보면 안 된다는 것이다. 실제로는 다음과 같은 원인이 섞일 수 있다.
- 커넥터 버그 또는 오래된 드라이버
- connection pool이 손상된 세션을 재사용하는 경우
- 프록시/미들웨어가 protocol state를 정확히 보존하지 못하는 경우
- 애플리케이션이 하나의 연결을 thread-safe하지 않게 공유하는 경우
3.3 command byte
클라이언트는 세션이 열리고 나면 단순히 SQL text만 보내는 것이 아니라, 먼저 “무슨 명령인지”를 나타내는 command를 보낸다. 대표적인 예시는 다음과 같다.
COM_QUERY: 일반 SQL text 실행COM_STMT_PREPARE: prepared statement 생성COM_STMT_EXECUTE: prepared statement 실행COM_STMT_CLOSE: prepared statement 해제COM_PING: 연결 생존 확인COM_QUIT: 세션 종료
즉, 운영자가 보는 Questions, Com_select, Com_stmt_prepare, Com_stmt_execute 같은 상태값은 단지 SQL 종류 통계가 아니라, 클라이언트가 어떤 protocol 경로를 택하고 있는지를 간접적으로 보여 준다.
4. Handshake: 모든 세션은 protocol 협상에서 시작한다
MySQL 연결은 TCP connect 이후 곧바로 SQL 실행으로 들어가지 않는다. 먼저 서버가 Initial Handshake Packet을 보내고, 클라이언트가 capability, 인증 응답, 문자셋, 부가 속성(connection attributes) 등을 담아 응답한다. 그 뒤 인증이 성공하면 비로소 세션이 열린다.
이 단계에서 운영자가 주목할 포인트는 다음과 같다.
- 인증 플러그인 호환성: 오래된 connector는 최신 기본 인증 방식과 맞지 않을 수 있다.
- TLS 협상: TLS 강제 정책이 있으면 인증 전에 실패할 수 있다.
- 문자셋 협상:
character_set_client,character_set_connection,character_set_results가 의도와 다르면 애플리케이션 한글/이모지/멀티바이트 처리 문제가 생길 수 있다. - 연결 속성(connection attributes): 프로그램명, connector 버전, client name이 Performance Schema에서 보이면 장애 추적에 도움이 된다.
다음 SQL은 현재 세션에서 protocol과 직접 연관된 기본 변수와 상태값을 확인하는 가장 안전한 출발점이다.
SELECT VERSION() AS mysql_version;
SHOW VARIABLES
WHERE Variable_name IN (
'max_allowed_packet',
'net_buffer_length',
'character_set_client',
'character_set_connection',
'character_set_results'
);
SHOW SESSION STATUS
WHERE Variable_name IN (
'Bytes_received',
'Bytes_sent',
'Questions',
'Com_select',
'Com_stmt_prepare',
'Com_stmt_execute'
);
실행 결과(MySQL 8.0.46):
mysql> SELECT VERSION() AS mysql_version;
+---------------+
| mysql_version |
+---------------+
| 8.0.46 |
+---------------+
1 row in set (0.00 sec)
mysql> SHOW VARIABLES
-> WHERE Variable_name IN (
-> 'max_allowed_packet',
-> 'net_buffer_length',
-> 'character_set_client',
-> 'character_set_connection',
-> 'character_set_results'
-> );
+--------------------------+----------+
| Variable_name | Value |
+--------------------------+----------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_results | latin1 |
| max_allowed_packet | 67108864 |
| net_buffer_length | 16384 |
+--------------------------+----------+
5 rows in set (0.00 sec)
mysql> SHOW SESSION STATUS
-> WHERE Variable_name IN (
-> 'Bytes_received',
-> 'Bytes_sent',
-> 'Questions',
-> 'Com_select',
-> 'Com_stmt_prepare',
-> 'Com_stmt_execute'
-> );
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| Bytes_received | 675 |
| Bytes_sent | 653 |
| Com_select | 2 |
| Com_stmt_execute | 0 |
| Com_stmt_prepare | 0 |
| Questions | 5 |
+------------------+-------+
6 rows in set (0.01 sec)
이 조회는 protocol decoder를 대신해 주지는 않지만, 운영자가 가장 먼저 확인해야 할 세 가지 축을 한 번에 보여 준다.
- 세션이 어떤 문자셋으로 대화하는가
- packet 관련 기본 버퍼/한도가 어떤가
- 이 세션이 text query 위주인지 prepared statement 위주인지 조짐이 있는가
5. Text protocol: 가장 단순하지만 가장 많이 쓰이는 경로
MySQL을 사용할 때 가장 흔한 경로는 text protocol이다. 애플리케이션이나 mysql CLI가 SQL 문자열을 COM_QUERY로 보내면, 서버는 이를 파싱하고 실행한 뒤 결과를 다시 text-oriented result set 형태로 돌려준다.
5.1 text protocol의 전형적 흐름
SELECT * FROM t WHERE id = 10 같은 쿼리를 생각해 보자. text protocol에서는 보통 다음 흐름이 발생한다.
- 클라이언트가 SQL 문자열 전체를 전송한다.
- 서버가 SQL을 파싱하고 실행 계획을 잡는다.
- 서버가 column count를 보낸다.
- 서버가 각 컬럼의 metadata를 보낸다.
- 서버가 row를 순서대로 보낸다.
- 종료 패킷 또는 OK 성격의 패킷으로 result set 끝을 알린다.
이 구조는 단순하고 호환성이 좋지만, 매번 SQL 문자열 전체를 보낸다는 특징이 있다. 쿼리가 짧고 단순하면 문제가 없지만, 다음 상황에서는 비용이 커질 수 있다.
- SQL 템플릿이 매우 길다.
- 같은 SQL 구조를 파라미터만 바꿔 수천 번 반복한다.
- 네트워크 왕복 지연이 큰 환경이다.
- 결과 컬럼 수가 많아 metadata 전송비용이 무시되지 않는다.
5.2 text protocol이 특히 유리한 경우
그렇다고 prepared statement가 항상 정답은 아니다. text protocol이 더 단순하고 안전한 경우도 많다.
- 일회성 관리 SQL
- DDL 중심 작업
- 복잡한 SQL을 그대로 사람이 읽고 실행해야 하는 운영 작업
- 드라이버가 prepared statement를 억지로 쓰다가 세션 상태를 과도하게 남기는 환경
- 프록시 multiplexing 효율이 더 중요한 환경
운영자는 “prepared statement가 더 고급 기능이니 항상 더 좋다”라고 단정하면 안 된다. 실제로는 반복 실행 패턴, 세션 수명, 프록시 구조, failover 후 복구 비용을 함께 보아야 한다.
6. Result set: 결과 자체보다 결과의 운반 방식이 중요할 때가 있다
많은 장애에서 문제는 SQL의 계산 자체보다 결과를 얼마나 크게, 얼마나 오래, 어떤 방식으로 보내느냐에 있다. result set 단계에서 자주 놓치는 포인트는 다음과 같다.
6.1 컬럼 메타데이터도 비용이다
result set은 row만 보내는 것이 아니다. 먼저 컬럼 수와 각 컬럼의 정의가 전송된다. 컬럼 수가 매우 많거나, 애플리케이션이 필요 이상으로 넓은 SELECT *를 사용하면 실제 row 수가 많지 않아도 전송 비용과 파싱 비용이 커질 수 있다.
6.2 큰 result set은 서버와 클라이언트 양쪽 메모리를 압박할 수 있다
서버는 실행 결과를 만들고, 클라이언트/드라이버는 이를 버퍼링하거나 스트리밍 방식으로 읽는다. 이때 다음 차이가 중요하다.
- 드라이버가 전체 결과를 메모리에 적재하는가
- cursor/streaming fetch를 지원하는가
- 애플리케이션이 읽는 속도보다 네트워크 송신이 더 빠른가
- 중간 프록시가 대형 result set을 버퍼링하는가
즉, “SQL은 0.1초 만에 끝났다”와 “사용자가 응답을 0.1초 만에 받았다”는 전혀 다른 말일 수 있다. protocol 관점에서는 실행 완료 시점과 결과 전송 완료 시점을 분리해서 봐야 한다.
6.3 운영에서 흔한 오해
다음 오해는 매우 흔하다.
LIMIT가 없는데도 쿼리가 느린 이유를 옵티마이저 탓으로만 돌린다.- API 응답 지연을 DB CPU 문제로만 본다.
- BLOB 다운로드 시간을 네트워크 계층이 아니라 SQL 문제로만 분류한다.
- mysql CLI에서 바로 보이니 애플리케이션에서도 같은 속도일 것이라고 가정한다.
실제로는 드라이버 fetch 방식, application serialization 비용, compression/TLS 비용, packet fragmentation, client-side buffering이 더 큰 병목일 수 있다.
7. Prepared statement protocol: text protocol과 무엇이 다른가
prepared statement protocol은 같은 SQL 구조를 반복 실행할 때 파싱 비용과 SQL text 전송량을 줄이는 방향으로 설계된 경로다. 다만 운영 관점에서 더 중요한 차이는 “빨라질 수 있다”보다 세션 상태가 생긴다는 점이다.
7.1 개념적 흐름
prepared statement는 대략 다음 두 단계로 생각하면 된다.
COM_STMT_PREPARE: SQL 템플릿을 서버 세션에 등록한다.COM_STMT_EXECUTE: statement id와 파라미터 값을 보내 실행한다.
이 구조에서는 매 실행마다 전체 SQL 문자열을 다시 보내지 않아도 된다. 대신 서버 세션 안에 statement가 남고, 연결 풀 환경에서는 그 상태가 오래 유지될 수 있다.
7.2 SQL PREPARE와 binary protocol prepared statement의 관계
운영 문서에서 혼동이 많은 부분이다. SQL 문법의 PREPARE ... EXECUTE ... DEALLOCATE PREPARE는 클라이언트 binary protocol prepared statement와 내부 구현이 완전히 동일하다고 단정할 수는 없지만, 세션 단위 prepared state가 생기고 실행 후 해제할 수 있다는 점에서 학습 및 관측 예제로 매우 유용하다.
즉, 문서에서는 다음처럼 이해하면 된다.
- connector가 사용하는 binary prepared protocol의 내부 패킷 자체를 SQL만으로 직접 볼 수는 없다.
- 그러나
PREPARE와performance_schema.prepared_statements_instances를 통해 “prepared statement가 세션 상태로 존재한다”는 핵심 특성은 재현 가능하다. - 아래 SQL 예제는 파라미터 바인딩 문법 자체보다 세션에 prepared state가 생기고, 실행 후에도 남아 있으며, 해제 시 사라진다는 점을 검증하는 데 초점을 둔다.
다음 예제는 prepared statement가 세션에 생성되고, 실행 횟수가 관측되며, 해제 후 사라지는 흐름을 보여 준다.
PREPARE stmt_demo FROM 'SELECT 10 AS value_one, 20 AS value_two, 30 AS sum_value';
EXECUTE stmt_demo;
SELECT STATEMENT_NAME, SQL_TEXT, COUNT_EXECUTE
FROM performance_schema.prepared_statements_instances;
DEALLOCATE PREPARE stmt_demo;
SELECT COUNT(*) AS remaining_prepared
FROM performance_schema.prepared_statements_instances;
실행 결과(MySQL 8.0.46):
mysql> PREPARE stmt_demo FROM 'SELECT 10 AS value_one, 20 AS value_two, 30 AS sum_value';
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> EXECUTE stmt_demo;
+-----------+-----------+-----------+
| value_one | value_two | sum_value |
+-----------+-----------+-----------+
| 10 | 20 | 30 |
+-----------+-----------+-----------+
1 row in set (0.00 sec)
mysql> SELECT STATEMENT_NAME, SQL_TEXT, COUNT_EXECUTE
-> FROM performance_schema.prepared_statements_instances;
+----------------+----------------------------------------------------------+---------------+
| STATEMENT_NAME | SQL_TEXT | COUNT_EXECUTE |
+----------------+----------------------------------------------------------+---------------+
| stmt_demo | SELECT 10 AS value_one, 20 AS value_two, 30 AS sum_value | 1 |
+----------------+----------------------------------------------------------+---------------+
1 row in set (0.00 sec)
mysql> DEALLOCATE PREPARE stmt_demo;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT COUNT(*) AS remaining_prepared
-> FROM performance_schema.prepared_statements_instances;
+--------------------+
| remaining_prepared |
+--------------------+
| 0 |
+--------------------+
1 row in set (0.00 sec)
이 예제는 두 가지를 확인하게 해 준다.
- prepared statement는 실행 즉시 사라지는 것이 아니라 세션 안에 남을 수 있다.
- 명시적으로 해제하거나 세션이 종료되어야 정리된다.
따라서 connection pool과 statement cache가 결합되면, 애플리케이션은 의도하지 않아도 서버 측 prepared statement 재고를 크게 늘릴 수 있다.
8. 세션 속성으로 보는 connector 흔적
현대 connector는 handshake 단계에서 프로그램명, client library, connector 버전 같은 connection attributes를 보내는 경우가 많다. 이 값은 모든 환경에서 동일하게 보장되지는 않지만, 보이면 장애 추적과 원인 분리에 매우 유용하다.
예를 들어 같은 MySQL 서버에 여러 애플리케이션이 붙는다면 다음 질문을 던질 수 있다.
- 어떤 connector가 오래된 인증 방식이나 protocol 동작을 쓰는가
- 특정 프로그램에서만
Packets out of order또는 reconnect storm이 발생하는가 - 같은 SQL 문제처럼 보여도 실제로는 특정 드라이버 계열에서만 나타나는가
다음 SQL은 현재 연결의 connection attributes를 확인하는 예제다.
SELECT PROCESSLIST_ID, ATTR_NAME, ATTR_VALUE
FROM performance_schema.session_connect_attrs
WHERE PROCESSLIST_ID = CONNECTION_ID()
ORDER BY ATTR_NAME
LIMIT 10;
실행 결과(MySQL 8.0.46):
mysql> SELECT PROCESSLIST_ID, ATTR_NAME, ATTR_VALUE
-> FROM performance_schema.session_connect_attrs
-> WHERE PROCESSLIST_ID = CONNECTION_ID()
-> ORDER BY ATTR_NAME
-> LIMIT 10;
+----------------+-----------------+------------+
| PROCESSLIST_ID | ATTR_NAME | ATTR_VALUE |
+----------------+-----------------+------------+
| 13 | _client_name | libmysql |
| 13 | _client_version | 8.0.46 |
| 13 | _os | Linux |
| 13 | _pid | 294 |
| 13 | _platform | x86_64 |
| 13 | os_user | root |
| 13 | program_name | mysql |
+----------------+-----------------+------------+
7 rows in set (0.00 sec)
이 결과는 connector마다 다르다. 어떤 환경에서는 program_name, _client_name, _client_version 등이 보이고, 어떤 환경에서는 매우 적게 보이거나 비어 있을 수 있다. 중요한 것은 절대값이 아니라, 문제가 나는 연결 집합과 정상 연결 집합의 차이를 보는 데 있다.
9. 운영 해석: protocol을 알면 보이는 것들
9.1 max_allowed_packet는 단순 튜닝값이 아니다
많은 팀이 max_allowed_packet를 오류가 나면 키우는 값 정도로 생각한다. 그러나 이 값은 애플리케이션이 어떤 payload 모델로 MySQL을 쓰고 있는지 드러내는 지표이기도 하다. 값이 자주 문제를 일으킨다면 다음 질문을 먼저 해야 한다.
- 정말로 DB가 대형 단일 payload를 받아야 하는가
- 애플리케이션이 전송 단위를 더 작게 나눌 수 없는가
- BLOB를 DB에 직접 넣는 전략이 맞는가
- replication, backup, binlog, redo/undo, client memory까지 포함한 전체 비용을 감당 가능한가
즉, packet 한도 문제는 단순 설정 이슈가 아니라 데이터 운반 전략 문제인 경우가 많다.
9.2 prepared statement는 성능 기능이면서 세션 상태 기능이다
prepared statement를 켜면 parse 비용이 줄 수 있다. 하지만 동시에 세션당 상태가 늘어난다. 이는 다음 상황에서 운영 이슈가 된다.
- 대형 connection pool
- SQL 템플릿 종류가 많은 ORM
- 긴 세션 수명
- failover 뒤 대량 reconnect + reprepare
- 프록시 multiplexing을 기대하지만 세션 상태 때문에 재사용이 제한되는 구조
즉, prepared statement는 단순 query optimization이 아니라 세션 상태 설계로 이해해야 한다.
9.3 result set 병목은 SQL 병목처럼 보일 수 있다
DB CPU가 낮고 execution plan도 멀쩡한데 API 응답이 느릴 때는 result set 전송 단계와 애플리케이션 소비 속도를 의심해야 한다. 다음 상황이 대표적이다.
- SELECT 대상 컬럼이 과도하게 많다.
- 쿼리 결과를 애플리케이션이 한 번에 모두 버퍼링한다.
- 네트워크 RTT가 커서 row fetch가 비효율적이다.
- TLS/압축/프록시 계층이 대형 결과에서 비용을 키운다.
9.4 프록시와 Aurora 환경에서는 protocol state의 의미가 더 커진다
Aurora MySQL은 기본 SQL 문법과 protocol 호환성을 유지하지만, 운영 의미는 일반 Community MySQL 단일 인스턴스와 다를 수 있다.
- writer/reader endpoint 전환이 발생하면 세션은 유지되지 않는다.
- failover 뒤에는 prepared statement, user variable, temporary table 같은 세션 상태가 모두 다시 준비되어야 한다.
- RDS Proxy나 유사 프록시를 사용할 때 세션 상태가 많으면 connection multiplexing 효율이 떨어질 수 있다.
- reader endpoint에서는 read-after-write 일관성 지연을 protocol 수준이 아니라 topology 수준 문제로 봐야 한다.
즉, Aurora에서는 protocol 자체보다 세션이 어디에 붙어 있고, 언제 끊기며, 상태가 어디까지 보존되는가가 더 중요하다.
10. 자주 생기는 오해와 주의점
10.1 “prepared statement를 쓰면 항상 더 빠르다”
반복 실행 패턴에서는 맞을 수 있다. 그러나 statement 수명 관리, pool 크기, failover 이후 warm-up 비용까지 포함하면 항상 단순 이득만 있는 것은 아니다.
10.2 “DB가 빨리 끝났으니 네트워크는 문제 아니다”
result set 전송, client buffering, serialization, TLS, 프록시 버퍼링 때문에 사용자가 체감하는 시간은 훨씬 길 수 있다.
10.3 “Packets out of order는 네트워크 패킷 유실이다”
실제로는 connector 버그, 손상된 연결 재사용, protocol state mismatch, thread-safe하지 않은 연결 공유 같은 애플리케이션/드라이버 측 원인이 더 흔할 수 있다.
10.4 “max_allowed_packet만 키우면 해결된다”
대형 packet 허용은 당장 오류를 숨길 수 있지만, 더 큰 메모리 압박과 대형 트랜잭션 비용을 초대할 수 있다. 먼저 payload 모델을 재검토해야 한다.
10.5 “프록시를 넣으면 세션 상태는 신경 쓰지 않아도 된다”
오히려 반대다. 세션 상태가 많을수록 프록시의 재사용/우회/멀티플렉싱 최적화가 제한될 수 있다.
11. 운영 체크리스트
다음 체크리스트는 MySQL protocol 관련 이슈를 점검할 때 기본 골격으로 쓸 수 있다.
-
max_allowed_packet, 문자셋 변수, 세션Bytes_received/Bytes_sent -
Packets out of order,server has gone away,packet too large
12. 결론
MySQL client/server protocol은 평소에는 잘 보이지 않는다. SQL이 실행되고 결과가 나오면 그 사이의 packet, sequence id, result set metadata, prepared statement command는 모두 배경으로 사라진다. 그러나 운영 이슈가 발생하면 바로 그 배경 계층이 전면으로 올라온다.
정리하면 다음과 같다.
- text protocol은 가장 단순하고 널리 쓰이는 기본 경로다.
- result set 문제는 SQL 계산 문제와 별개로 전송/버퍼링 문제를 만들 수 있다.
- prepared statement protocol은 파싱 비용 절감과 함께 세션 상태를 남긴다.
- connection pool, 프록시, Aurora failover는 protocol의 운영 의미를 크게 바꾼다.
다음 단계의 학습에서는 이 protocol 위에 올라가는 cursor fetch, streaming result, compression/TLS 비용, proxy multiplexing 제약을 함께 보면 좋다. 그러면 “SQL이 느리다”라는 한 문장을 더 세밀하게 쪼개어, 어디서 시간이 소비되고 어디서 상태가 남는지 구조적으로 설명할 수 있게 된다.