#request-processing
글
-
Kafka 소스 파헤치기 3: 와이어 프로토콜 — JSON 스키마에서 만들어지는 요청과 응답
프로듀서와 브로커가 TCP 위에서 주고받는 바이트는 [4바이트 길이][요청 헤더][본문] 한 가지 모양입니다. 그 모양을 정하는 JSON 스키마 203개가 빌드 때 자바 클래스 391개가 되는 과정, 판(version)을 고르는 ApiVersions 협상, 옛 판과 새 판이 함께 사는 유연한 판과 태그 필드를 소스로 따라가고, 생성된 클래스만으로 맨 소켓에 요청을 보내는 예제로 바이트를 직접 확인합니다.
-
Kafka 소스 파헤치기 4: 브로커의 네트워크 계층 — Acceptor, Processor, 요청 처리 스레드
브로커의 jstack 에 보이는 socket-acceptor, network-thread, request-handler 세 종류의 스레드가 요청 하나를 어떻게 나눠 맡는지 SocketServer.scala 로 따라갑니다. 리스너마다 Acceptor 하나, Processor 가 바이트를 읽어 요청 큐에 넣고 그 연결을 mute 하면, 요청 처리 스레드가 KafkaApis.handle() 의 match 로 갈래를 나누고, 응답은 요청을 읽은 Processor 로 돌아갑니다. 한 연결의 요청을 하나씩만 처리해 순서를 지키는 mute/unmute, num.network.threads·num.io.threads·queued.max.requests 가 걸리는 자리를 jstack·맨 소켓·요청 로그 예제로 확인하고 Tomcat 의 NIO 커넥터와 비교합니다.
-
Kafka 소스 파헤치기 6: 브로커가 레코드를 받아 쓰기까지 — 레코드 배치 형식과 Produce 요청 처리
프로듀서가 보낸 레코드 배치는 61바이트 헤더와 varint 로 줄인 레코드로 되어 있습니다. 그 바이트를 파일에서 직접 한 칸씩 읽어 보고, Produce 요청 하나가 KafkaApis → ReplicaManager → Partition → UnifiedLog → LogValidator → LogSegment 를 지나 파일에 닿는 길을 소스와 브로커 TRACE 로그로 따라갑니다. 브로커가 배치를 풀지 않고 헤더 몇 칸만 고쳐 그대로 쓰는 설계(CRC 가 그대로인 것을 확인), 토픽의 compression.type 이 다르면 다시 압축하는 경우, acks=0/1/all 이 응답 시점을 바꾸는 자리까지 손으로 만든 Produce 요청으로 확인합니다.
-
Kafka 소스 파헤치기 8: 읽기 경로 — Fetch 요청, 롱 폴링, 그리고 제로 카피
컨슈머의 poll() 이 보낸 Fetch 요청이 브로커 안에서 KafkaApis.handleFetchRequest() → ReplicaManager.fetchMessages() → UnifiedLog.read() → LogSegment.read() 를 지나 파일의 한 조각(FileRecords)이 되고, 데이터가 모자라면 fetch.max.wait.ms 동안 퍼거토리(DelayedFetch)에서 기다렸다가, 마지막에 FileChannel.transferTo() 로 JVM 을 거치지 않고 소켓으로 나가는 길을 Kafka 4.5.0-SNAPSHOT 소스로 따라갑니다. 증분 fetch 세션, 컨슈머와 팔로워가 같은 Fetch 를 쓰는 이유, TLS 에서 제로 카피가 사라지는 이유를 요청 로그·JMX·JFR 예제로 확인합니다.
-
MariaDB 소스 파헤치기 5: 프로토콜과 명령 처리 — 패킷 하나가 dispatch_command에 닿기까지
클라이언트가 보낸 바이트가 어떻게 패킷이 되고, 첫 바이트 하나로 COM_QUERY·COM_PING·COM_STMT_EXECUTE가 갈리고, 결과가 다시 패킷이 되어 나가는지를 sql/net_serv.cc, sql_parse.cc의 do_command()·dispatch_command(), protocol.cc로 따라갑니다. bash의 /dev/tcp로 클라이언트 라이브러리 없이 서버와 직접 말을 주고받고, 쿼리 캐시가 왜 파싱보다 먼저 문자열로 찾는지도 실행해서 확인합니다.
-
MariaDB 소스 파헤치기 6: 파서 — SQL 문장이 LEX와 Item 나무가 되기까지
문자열로 들어온 SQL 한 문장이 렉서(Lex_input_stream)에서 토큰이 되고, Bison이 만든 파서(MYSQLparse·ORAparse)의 규칙 액션에서 LEX·SELECT_LEX·TABLE_LIST·Item 나무가 되는 과정을 소스로 따라갑니다. 문법 파일 하나에서 파서 두 개가 생기는 빌드 규칙, 키워드 표와 state_map, "near '...' at line N" 오류가 만들어지는 곳을 보고, Bison 추적과 gdb로 파서가 남긴 나무를 직접 찍어 봅니다.
-
MariaDB 소스 파헤치기 7: 준비 단계 — 권한 확인, 테이블 열기, 이름 찾기
파서가 만든 LEX를 받은 서버가 실행 계획을 세우기 전에 하는 세 가지 일을 소스로 따라갑니다. mysql_execute_command()의 큰 switch에서 check_table_access()로 권한을 보고, open_and_lock_tables()로 메타데이터 잠금을 얻고 TABLE_SHARE와 TABLE을 캐시에서 꺼내고, JOIN::prepare()에서 열 이름을 Field에 잇습니다. 열 권한 오류 1142·1143, Opened_tables와 Table_open_cache_hits의 변화, ALTER TABLE이 열린 트랜잭션 뒤에서 기다리고 그 뒤 SELECT까지 줄을 서는 모습, 1054·1052 오류가 나는 호출 스택을 직접 확인합니다.
-
MariaDB 소스 파헤치기 9: 실행과 handler API — 중첩 루프 조인이 엔진을 부르는 법
옵티마이저가 고른 계획이 실제로 행을 읽어 내는 곳을 소스로 따라갑니다. JOIN::exec()에서 do_select()와 sub_select()·evaluate_join_record()의 중첩 루프로, EXPLAIN의 type마다 정해지는 읽기 함수로, 조인 버퍼로, handler의 ha_ 감싸개를 지나 ha_innobase::rnd_next()와 write_row()까지 내려갑니다. Handler_read_* 상태 변수로 호출 수를 세고, gdb 백트레이스 한 장으로 do_command부터 엔진까지를 확인하고, 엔진을 MEMORY·Aria·BLACKHOLE로 바꿔도 같은 자리를 지나는 것을 봅니다.
-
nginx 소스 파헤치기 8: HTTP 요청 읽기 — 연결이 ngx_http_request_t가 되기까지
accept한 소켓 하나가 nginx 안에서 ngx_http_request_t가 되기까지를 소스로 따라갑니다. 데이터가 올 때까지 요청 구조체를 만들지 않는 이유, switch 상태 기계로 된 요청 줄·헤더 파서, client_header_buffer_size와 large_client_header_buffers가 쓰이는 자리, Host 헤더로 server 블록을 고르는 순서, keep-alive와 파이프라인을 디버그 로그·gdb·nc로 직접 확인합니다.
-
nginx 소스 파헤치기 9: 11단계 phase와 location 찾기 — 요청은 어떤 순서로 처리되나
헤더를 다 읽은 요청은 nginx 안에서 11개의 phase를 차례로 지납니다. 모듈들이 등록한 핸들러가 설정 때 한 줄짜리 ph[] 배열로 펴지고, ngx_http_core_run_phases()가 그 배열을 checker 함수로 돌리는 구조를 소스에서 확인합니다. location 블록이 접두사 트리로 바뀌어 =, ^~, 정규식, 1.31.x의 predicate location 순서로 골라지는 과정과 rewrite last·break, 내부 리다이렉트, named location이 배열의 어느 칸으로 되돌아가는지도 디버그 로그와 gdb로 직접 봅니다.
-
Apache httpd 소스 파헤치기 10: 요청 처리 단계 — URL에서 파일과 핸들러까지
GET /hello.html 한 줄이 httpd 안에서 어떤 단계를 거쳐 디스크의 파일과 그것을 내보낼 핸들러로 바뀌는지, ap_process_request_internal()의 단계 순서를 따라 소스에서 확인합니다. Location·Directory·Files 설정이 합쳐지는 세 가지 걷기, mod_dir의 서브요청, ErrorDocument의 내부 리다이렉트도 모든 단계에 로그를 찍는 모듈로 직접 봅니다.
-
Tomcat 소스 파헤치기 6: HTTP/1.1 요청 읽기 — 바이트가 coyote Request가 되기까지
소켓에서 읽은 바이트가 Http11Processor의 service() 루프 안에서 요청 줄과 헤더로 나뉘어 coyote Request가 되는 과정을 소스로 따라갑니다. 문자열을 늦게 만드는 MessageBytes, maxHttpHeaderSize와 maxHeaderCount 한도, 잘못된 요청이 HTML 400 페이지가 되는 길, keep-alive와 chunked 응답이 정해지는 자리를 날 요청을 보내 직접 확인합니다.
-
Tomcat 소스 파헤치기 7: CoyoteAdapter와 Mapper — 요청은 어느 서블릿으로 가나
다 읽은 HTTP 요청이 Coyote에서 Catalina로 넘어가는 다리인 CoyoteAdapter, 그 안에서 URI를 디코딩하고 정규화하는 postParseRequest(), Host·Context·Wrapper를 고르는 Mapper의 규칙 순서를 소스로 따라갑니다. 서블릿 여섯 개로 매핑 우선순위를, curl로 %2F·..·;jsessionid가 어떻게 처리되는지를, 붙잡아 둔 요청 객체로 RequestFacade가 왜 있는지를 직접 확인합니다.
-
Tomcat 소스 파헤치기 8: 파이프라인과 밸브 — 컨테이너마다 걸린 처리 사슬
server.xml의 <Valve> 한 줄은 소스에서 무엇이 될까요. Engine·Host·Context·Wrapper가 저마다 가진 파이프라인, 그 끝에서 다음 컨테이너를 고르는 기본 밸브 넷, 다음 밸브를 부르지 않아 요청을 끊는 법, 오류 페이지를 그리는 ErrorReportValve, 응답이 끝난 뒤에야 쓰이는 접근 로그와 RemoteIpValve의 함정까지, 직접 쓴 밸브를 세 컨테이너에 걸어 소스와 함께 확인합니다.
-
Tomcat 소스 파헤치기 9: 필터 체인과 서블릿 호출 — service()가 불리기까지
밸브 사슬의 맨 끝에서 Tomcat이 서블릿 인스턴스를 꺼내고, 이 요청에 맞는 필터를 골라 배열로 묶고, 그 배열을 한 칸씩 넘기며 마지막에 service()를 부르는 과정을 소스로 따라갑니다. 서블릿 init()이 불리는 시점, 필터 순서가 정해지는 규칙, forward와 include의 차이, 예외가 오류 페이지로 바뀌는 길도 예제로 확인합니다.