impyla 로 100만 행 × 100 컬럼 규모를 SELECT 하면 execute() 는 금방 끝나는데 fetchall() 에서 응답이 없는 것처럼 멈춘다. impyla 의 잘 알려진 한계이며, 특히 컬럼 수가 많을수록 심해진다.
Impala 는 스트리밍 SQL 엔진이라 테이블을 다 스캔해 어딘가 쌓아 두고 반환하는 것이 아니라 스캔과 동시에 클라이언트로 행을 흘려보낸다. 그래서 execute() 는 즉시 끝나고 실제 작업은 fetch 구간에서 일어난다.
impyla 는 HiveServer2 Thrift 로 받은 결과를 순수 파이썬으로 한 행씩 풀어 튜플을 만든다. 컬럼이 100개면 행마다 파이썬 객체를 100개 만들고, 100만 행이면 1억 번이다. 인터프리터 오버헤드와 객체 할당 비용이 여기서 폭발하며, 폭이 넓을수록 행당 오버헤드가 커진다.
"응답이 없다"는 증상은 두 가지다. 메모리가 폭증해 스와핑 중이라 멈춘 것처럼 보이는 경우와, 진짜 무한 대기인 경우다. 후자는 FETCH_ROWS_TIMEOUT_MS 가 0 이면 fetch 요청이 무한정 기다리고, 결과 스풀링이 꺼져 있으면 코디네이터가 배치 하나를 만들 때까지 계속 대기하기 때문에 발생한다.
진단은 LIMIT 10000 으로 줄여 실행해 실제로 끝나는지와 메모리를 얼마나 쓰는지 보는 것으로 한다. execute 는 빠른데 fetch 만 끝나지 않으면 위 문제로 확정이다.
가장 검증된 우회책은 서버 쪽 BATCH_SIZE 를 키우는 것이다. Impala 2.11 부터 유효 범위가 1~65536 이며 그 이전에는 사실상 1024 다. 전송 버퍼 크기까지 함께 키워 수십 분 걸리던 fetch 를 크게 줄인 사례가 있다.
SET BATCH_SIZE=65536;
SET FETCH_ROWS_TIMEOUT_MS=<적당한 값>;
클라이언트 쪽은 fetchall() 대신 fetchmany() 로 스트리밍한다. cursor.arraysize 를 키우고 배치 단위로 처리하면 메모리 폭증을 막을 수 있으며, arraysize 를 BATCH_SIZE 와 맞추면 유리하다. impyla 의 컬럼 단위 배치 fetch(fetchcbatch())는 행 단위보다 빠르고 pandas 변환에도 효율적이다.
대량 추출이 목적이라면 정공법은 CREATE TABLE AS SELECT 로 텍스트나 Parquet 테이블에 쓴 뒤 파일시스템에서 직접 내려받는 것이다. pyarrow 나 pandas 로 HDFS · S3 파일을 바로 읽으면 HiveServer2 와 impyla 경로를 통째로 우회한다. 이것이 Impala 엔지니어가 권하는 방식이다.
그 밖에 ODBC · JDBC 드라이버는 네이티브 역직렬화라 impyla 보다 빠르고, 필요한 컬럼만 선택해 폭을 줄이는 것도 효과가 직접적이다. impyla · thrift · thrift-sasl 의 호환 버전이 어긋나면 fetch 가 무한 hang 되는 사례가 있으므로(특히 Kerberos 환경) 버전을 맞춘다.
직렬화는 객체를 바이트로 바꾸는 것으로 보내는 쪽이 하고, 역직렬화는 바이트를 객체로 바꾸는 것으로 받는 쪽이 한다. SELECT 결과 기준으로 서버가 직렬화하고 클라이언트가 역직렬화한다.
네트워크 너머 DB 에 붙는 클라이언트는 모두 직렬화·역직렬화를 한다. impyla 만의 특징이 아니다. 차이는 "하느냐"가 아니라 "어떤 코드로 하느냐"와 "애초에 파이썬 객체를 만드느냐"에 있다.
python-oracledb 는 빠른 경로가 여럿이다. thick 모드는 Oracle Client 의 C 라이브러리로 역직렬화하고, thin 모드도 프로토콜과 페치가 고도로 최적화돼 있다. 결정적인 것은 Arrow · DataFrame 페치(fetch_df_batches · fetch_df_all)로, 결과를 Arrow 컬럼 버퍼로 바로 받아 행 단위 파이썬 객체 생성을 아예 건너뛴다. impyla 에는 이런 우회로가 없어 순수 파이썬 행 단위 역직렬화만 가능하다.