데이터베이스에서의 쿼리 최적화를 소형 LLM에 맡기는 연구를 진행한 결과, Postgres의 기본 쿼리 플랜을 뛰어넘는 성능을 발휘하는 쿼리 플랜을 생성할 수 있었다고 백엔드 엔지니어 로한 반살이 보고했습니다. Training a 4B model to produce 81% faster query plans than Postgres - Rohan Bansal https://rohanbansal.com/qorl 데이터베이스 관리 시스템(DBMS)의 중요한 기능 중 하나가 데이터를 보다 효율적으로 조회하기 위한 쿼리 플랜을 작성하는 "쿼리 최적화"입니다. 그러나 테이블 조인의 순서 결정과 같은 작업은 NP-난해이며, PostgreSQL과 같은 진보된 쿼리 옵티마이저조차 항상 최적의 플랜을 생성할 수 있다고는 할 수 없습니다. 쿼리 옵티마이저는 실제 데이터 행 수가 아니라 통계 정보에 기반한 추정에 의존하기 때문에, 추정이 정확한지 여부가 성능에 큰 영향을 미칠 수 있습니다. 예로서 IMDb(Internet Movie Database)의 데이터셋에 대해 생각해 보겠습니다. -- An IMDb title (movie, series, episode, etc.) [~1M rows]
title (
id integer PRIMARY KEY,
title text,
production_year integer,
kind_id integer -- FK -> kind_type
)
-- 영화 <> 회사 연결 테이블 [~200만 행]
movie_companies (
id integer PRIMARY KEY,
movie_id integer, -- FK -> title.id
company_id integer, -- FK -> company_name.id
company_type_id integer, -- FK -> company_type.id
note text
)
-- 회사 이름, 출신지 등 [~10만 행]
company_name (
id integer PRIMARY KEY,
name text,
country_code text -- '[us]', '[jp]', ...
)
번역 대상:
-- Lookup table for what a title _is_ [7 rows]
kind_type (
id integer PRIMARY KEY,
kind text -- 'movie', 'tv series', 'episode', .
.
.
)데이터베이스에 예를 들어 “2000년대에 가장 많은 타이틀을 릴리스한 일본 기업은 어디인가?”
라고 질문하는 경우, 다음과 같은 쿼리를 작성하게 됩니다.
SELECT cn.
name,
COUNT(*) AS titles
FROM title AS t,
movie_companies AS mc,
company_name AS cn
WHERE t.
id = mc.
movie_id
AND mc.
company_id = cn.
id
AND cn.
country_code = '[jp]'
AND t.
production_year BETWEEN 2000 AND 2009
GROUP BY cn.
name
ORDER BY titles DESC
LIMIT 10;이 쿼리를 실행하면, 2000년부터 2009년 사이에 릴리스한 타이틀 수가 많은 순서대로 정렬한, 10개의 일본 기업이 출력됩니다.
다만 데이터베이스가 쿼리 결과를 취득하기 위해 따라간 경로는 반드시 정해져 있는 것이 아니라 “필터링 조건”이 크게 관여합니다.
결과가 크게 좁혀지는 “필터링 조건”의 영향을 설명하기 위해, 앞선 쿼리에서 “일본 기업” 필터와 “기간” 필터를 제외하고 필터링 조건이 없는 상태로 만듭니다.
SELECT cn.name,
COUNT(*) AS titles
FROM title AS t,
movie_companies AS mc,
company_name AS cn
WHERE t.id = mc.movie_id
AND mc.company_id = cn.id
GROUP BY cn.name
ORDER BY titles DESC
LIMIT 10;
그러면 쿼리가 참조하고 있는 테이블은 “t(title)”“mc(movie_companies)”“cn(company_name)”의 3개이며, mc와 cn은 “mc.company_id = cn.id”에 의해 결합되고, t와 mc는 “t.id = mc.movie_id”에 의해 결합되고 있음을 알 수 있습니다.
따라서 최종적인 쿼리 결과를 취득하기 위해서는 어느 결합을 먼저 수행하는가에 따라 다음 중 어느 하나의 경로를 거쳐 3개의 테이블 모두를 결합시켜야 합니다.
다음으로, 테이블 또는 쿼리 결과의 카디널리티, 즉 포함된 행 수에 대해 생각해 봅시다.
최종 쿼리 결과에 관련된 테이블의 카디널리티가 다음과 같다고 가정합니다.
·cn(company_name): 10만 행
·mc(movie_companies): 200만 행
·t(title): 100만 행
조인을 고려하면 다음과 같은 카디널리티가 얻어집니다.
3개의 테이블을 조인하는 순서에 관계없이, 항상 동일한 200만 행이 두 번째 조인에 전달됩니다.
이제 필터링 조건을 다시 적용해 보겠습니다.
그러면 카디널리티는 다음과 같이 됩니다.
·cn′: 5000행(10만 개사 중 5%가 일본 기업이라고 가정한 경우)
·mc: 200만 행(필터링 조건에 의한 변화 없음)
·t′: 20만 행(100만 타이틀 중 20%가 2000년대에 제작되었다고 가정한 경우)
조인을 고려하면 다음과 같은 카디널리티가 됩니다.
첫 번째 조인 순서에서는 200만 건의 movie_companies 엔트리에서 일본 기업인 5%의 기업으로 좁혀집니다.
균일 분포를 가정하면, 이 조인에 의해 약 10만 행이 생성됩니다.
이 결과를 필터링된 title 테이블과 조인하면, 그 중 2000년대에 제작된 타이틀에 해당하는 20%만이 남습니다.
한편, 두 번째 조인 순서에서는 200만 건의 movie_companies 엔트리에서 2000년대에 제작된 타이틀의 20%로 좁혀집니다.
마찬가지로 균일 분포를 가정하면, 첫 번째 조인에 의해 약 40만 행이 생성되며, 이 40만 행이 두 번째 조인에 전달됩니다.
즉, 첫 번째 조인 순서에서는 두 번째 조인에 전달되는 중간 결과가 약 10만 행인 반면, 두 번째 조인 순서에서는 약 40만 행이 되므로, 후자를 선택하면 두 번째 조인에서 처리하는 행 수가 약 4배가 됩니다.
이야기를 더욱 복잡하게 만드는 것은, 테이블을 조인하는 알고리즘에는 다음 3가지가 있다는 점입니다.
·해시 조인·머지 조인·네스티드 루프 조인 또한 조인 알고리즘에 영향을 미치기 때문에 가환성에 대해서도 고려할 필요가 생기므로, 결과적으로 3개의 테이블을 조인하는 조합은 8가지가 됩니다.
더불어, 각 테이블을 스캔하는 방법에는 다양한 방법이 있습니다.
대표적인 것으로 다음 4가지를 생각합니다.
·시퀀셜·인덱스·인덱스 온리·비트맵 이 모든 것을 감안하면, 앞서 언급한 쿼리를 실행하는 방법은 4608가지나 됩니다.
또한, 조인하는 테이블이 늘어나면 조합의 수는 폭발적으로 증가합니다.
중요한 점은, PostgreSQL은 쿼리 플래닝 중에 실제 카디널리티를 카운트할 수 없다는 것입니다.
번역 대상:
카디널리티를 알려면 각 테이블·쿼리 결과를 실제로 결합하여 결과 행 수를 카운트해야 하지만, 쿼리 옵티마이저에 요구되는 고속성은 완전히 손상되어 버립니다.
쿼리 옵티마이저의 역할은 완벽한 비용 최소화가 아니라 많은 쿼리에서 충분한 성능을 낼 수 있게 하는 것이므로, PostgreSQL은 통계 정보를 사용하여 카디널리티를 추정하고 있습니다.
PostgreSQL은 시스템 카탈로그 "pg_statistic"에 데이터 분산도 등의 통계 데이터를 저장하고 있으며, 가장 효율적인 쿼리 플랜을 작성하기 위해 이용하고 있습니다.
다만 결합이 얽히게 되면 이야기는 복잡해집니다.
PostgreSQL은 어떤 테이블의 행이 다른 테이블에 어떻게 분포되어 있는지를 알 방법이 없으므로, 어떤 값이 첫 번째 테이블에 출현하는 비율을 그대로 두 번째 테이블에도 적용할 수 있다고 가정하는 "균일 분포 가정"이라는 사고방식을 사용합니다.
"균일 분포 가정"은 휴리스틱으로서는 문제가 없지만, 실패하면 영향은 막대한 것이 됩니다.
앞서 든 결합 순서의 예를 사용하면, 200만 건의 movie_companies 엔트리 중 5%가 일본 기업의 것이라는 가정 하에 필터링을 수행했습니다.
그러나 만약 그 5%의 일본 기업이 출시한 영화가 실제로는 전체 영화의 50%를 차지하고 있었다면, 최초의 조인에서 100만 행이 생성될 수도 있습니다.
초기 조인 처리에서 잘못된 추정치 하나만 있어도, 영향이 조인 트리 전체에 파급되어 다른 모든 추정치를 망칠 가능성이 있다는 것입니다.
PostgreSQL은 항상 비용이 가장 낮은 실행 플랜을 선택하지만, 소스 코드를 변경하지 않는 한 그 비용 모델을 바꿀 수는 없습니다.
이에 LLM으로 쿼리 플랜을 개선할 수 있지 않을까 하는 착상에서, PostgreSQL의 확장 기능 pg_hint_plan을 이용해 특정 실행 플랜을 지시하는 ‘힌트’를 LLM에 생성시키는 연구가 시작되었습니다.
pg_hint_plan을 이용하는 방법은 SQL문 바로 앞에 구조화된 ‘힌트’를 주석으로 추가하기만 하면 됩니다.
/*+
HashJoin(a b)
SeqScan(a)
*/
EXPLAIN SELECT *
FROM pgbench_branches b
JOIN pgbench_accounts a ON b.
bid = a.
bid
ORDER BY a.
번역 대상:
Sort (cost=31465.84..31715.84 rows=100000 width=197)
Sort Key: a.aid
-> Hash Join (cost=1.02..4016.02 rows=100000 width=197)
Hash Cond: (a.bid = b.bid)
-> Seq Scan on pgbench_accounts a (cost=0.00..2640.00 rows=100000 width=97)
-> Hash (cost=1.01..1.01 rows=1 width=100)
-> Seq Scan on pgbench_branches b (cost=0.00..1.01 rows=1 width=100)
(7 rows)실험에서는 먼저 소규모 4B 모델(Qwen3.8-4B-Distill)을 사용하고, 지도 파인튜닝(SFT)을 통해 inspect_relation·get_column_stats·get_plan·evaluate_candidate와 같은 도구를 제공하는 “qo-agent” 하네스의 언어를 학습시켰습니다.
한편 교사 모델에는 GPT-6 Astra 또는 Qwen 3.8 2.
4T 중 어느 것을 선택할지를 다면적으로 평가한 후 GPT-6 Astra를 채택하고 있습니다.
SFT 이후, 모델은 에이전트 기반 강화학습(RL)에 의해 추가로 훈련되었습니다.
RL에서는 모델이 생성한 쿼리 플랜을 실제 실행 시간으로 평가하고, 더 나은 플랜을 생성하도록 모델의 가중치를 업데이트했습니다.
RL 프로세스에서는 노이즈가 많은 측정 환경에서도 신뢰할 수 있는 보상 설계와, 학습을 안정시키기 위해 커스터마이즈한 GRPO(Group Relative Policy Optimization) 변형이 중요하다는 것이 밝혀졌습니다.
훈련 결과, 4B 모델은 SFT와 RL을 모두 거쳐 PostgreSQL의 기본 플랜을 크게 웃도는 쿼리 플랜을 생성하는 데 성공했습니다.
특히, 조인 부하가 높은 113개의 쿼리로 이루어진 「조인 순서 벤치마크(JOB)」 데이터셋에서 기하 평균으로 1.81배의 스피드업을 달성했습니다.
즉, 모델이 단순히 도구의 사용법을 배운 것을 넘어, 조인 순서 변경·스캔 방법 최적화·병렬 실행 활용과 같은 쿼리 최적화에서의 유효한 전략을 학습할 수 있었음을 시사합니다.
데이터베이스에 예를 들어 “2000년대에 가장 많은 타이틀을 릴리스한 일본 기업은 어디인가?
”라고 문의하는 경우, 다음과 같은 쿼리를 작성하게 됩니다.
SELECT cn.
name,
COUNT(*) AS titles
FROM title AS t,
movie_companies AS mc,
company_name AS cn
WHERE t.
id = mc.
movie_id
AND mc.
company_id = cn.
id
AND cn.
country_code = '[jp]'
AND t.
production_year BETWEEN 2000 AND 2009
GROUP BY cn.
name
ORDER BY titles DESC
LIMIT 10;이 쿼리를 실행하면, 2000년부터 2009년 사이에 릴리스한 타이틀 수가 많은 순서대로 정렬한, 10개사의 일본 기업이 출력됩니다.
다만 데이터베이스가 쿼리 결과를 취득하기 위해 거친 경로는 반드시 정해져 있는 것이 아니라 “필터링 조건”이 크게 관여합니다.
결과가 크게 좁혀지는 “필터링 조건”의 영향을 설명하기 위해, 앞선 쿼리에서 “일본 기업” 필터나 “기간” 필터를 제외하고 필터링 조건이 없는 상태로 만듭니다.
SELECT cn.
번역 대상:
name,
COUNT(*) AS titles
FROM title AS t,
movie_companies AS mc,
company_name AS cn
WHERE t.
id = mc.
movie_id
AND mc.
company_id = cn.
id
GROUP BY cn.
name
ORDER BY titles DESC
LIMIT 10;그러면 쿼리가 참조하고 있는 테이블은「t(title)」「mc(movie_companies)」「cn(company_name)」의 3개이며, mc와 cn은「mc.
company_id = cn.
id」에 의해 결합되고, t와 mc는「t.
id = mc.
movie_id」에 의해 결합되고 있음을 알 수 있습니다.
따라서, 최종적인 쿼리 결과를 얻기 위해서는 어느 결합을 먼저 수행하는가에 따라, 이하의 어느 하나의 경로를 거쳐 3개의 테이블 모두를 결합시켜야 합니다.
다음으로, 테이블 또는 쿼리 결과의 카디널리티, 즉 포함되는 행 수에 대해 생각해 봅니다.
최종적인 쿼리 결과에 관련된 테이블의 카디널리티가 다음과 같다고 가정합니다.
번역 대상:
·cn(company_name): 10만 행·mc(movie_companies): 200만 행·t(title): 100만 행 조인을 고려하면 다음과 같은 카디널리티가 얻어집니다.
3개의 테이블을 조인하는 순서에 관계없이, 항상 동일한 200만 행이 두 번째 조인에 전달됩니다.
그럼 필터 조건을 되돌려 보겠습니다.
그러면 카디널리티는 다음과 같이 됩니다.
·cn′: 5000행(10만 개 사 중 5%가 일본 기업이라고 가정한 경우)·mc: 200만 행(필터 조건에 따른 변화 없음)·t′: 20만 행(100만 타이틀 중 20%가 2000년대에 제작되었다고 가정한 경우) 조인을 고려하면 다음과 같은 카디널리티가 됩니다.
첫 번째 조인 순서에서는 200만 건의 movie_companies 엔트리에서 일본 기업인 5%의 기업으로 좁힙니다.
균일 분포를 가정하면, 이 조인에 의해 약 10만 행이 생성됩니다.
이 결과를 필터링된 title 테이블과 조인하면, 그 중 2000년대에 제작된 타이틀에 해당하는 20%만 남습니다.
한편, 두 번째 조인 순서에서는 200만 건의 movie_companies 엔트리에서 2000년대에 제작된 타이틀의 20%로 좁힙니다.
마찬가지로 균일 분포를 가정하면, 첫 번째 조인에 의해 약 40만 행이 생성되며, 이 40만 행이 두 번째 조인에 전달됩니다.
즉, 첫 번째 조인 순서에서는 두 번째 조인에 전달되는 중간 결과가 약 10만 행인 반면, 두 번째 조인 순서에서는 약 40만 행이 되므로, 후자를 선택하면 두 번째 조인에서 처리하는 행 수가 약 4배가 됩니다.
이야기를 더욱 복잡하게 만드는 것은, 테이블을 조인하는 알고리즘에는 다음 3가지가 있다는 점입니다.
• 해시 조인
• 머지 조인
• 네스티드 루프 조인
또한 조인 알고리즘에 영향을 미치기 때문에 가환성에 대해서도 고려할 필요가 생기므로, 결과적으로 3개의 테이블을 조인하는 조합은 8가지가 됩니다.
더불어, 각 테이블을 스캔하는 방법에는 다양한 방법이 있습니다.
대표적인 것으로 다음 4가지를 생각합니다.
• 시퀀셜
• 인덱스
• 인덱스 온리
• 비트맵
모두를 감안하면, 앞서의 쿼리를 실행하는 방법은 4608가지나 됩니다.
또한, 조인하는 테이블이 늘어나면 조합의 수는 폭발적으로 증가합니다.
중요한 점은, PostgreSQL은 쿼리 플래닝 중에 실제 카디널리티를 카운트할 수 없다는 것입니다.
카디널리티를 알기 위해서는 각 테이블·쿼리 결과를 실제로 조인하여 결과 행 수를 카운트해야 하지만, 쿼리 옵티마이저에 요구되는 고속성은 완전히 손상되어 버립니다.
쿼리 옵티마이저의 역할은 완벽한 비용 최소화가 아니라 많은 쿼리에서 충분한 성능을 낼 수 있도록 하는 것이므로, PostgreSQL은 통계 정보를 사용하여 카디널리티를 추정하고 있습니다.
PostgreSQL은 시스템 카탈로그「pg_statistic」에 데이터의 분산도 등의 통계 데이터를 저장하고 있으며, 가장 효율적인 쿼리 플랜을 생성하기 위해 이용하고 있습니다.
다만 조인이 얽히게 되면 이야기는 다소 복잡해집니다.
PostgreSQL은 어떤 테이블의 행이 다른 테이블에 어떻게 분포되어 있는지를 알 수단이 없으므로, 어떤 값이 첫 번째 테이블에 출현하는 비율을 그대로 두 번째 테이블에도 적용할 수 있다고 가정하는「균일 분포 가정」이라는 사고방식을 사용합니다.
「균일 분포 가정」은 휴리스틱으로서는 문제가 없지만, 실패하면 영향은 막대한 것이 됩니다.
앞서 든 조인 순서의 예를 사용하면, 200만 건의 movie_companies 엔트리 중 5%가 일본 기업의 것이라는 가정 하에 필터링을 수행했습니다.
그러나 만약 그 5%의 일본 기업이 릴리스한 영화가 실제로는 전체 영화의 50%를 차지하고 있었다면, 최초의 조인에서 100만 행이 생성될 수도 있습니다.
초기의 조인 처리에서 잘못된 추정이 하나라도 있으면, 영향이 조인 트리 전체에 파급되어 다른 모든 추정을 망칠 가능성이 있다는 것입니다.
번역 대상:
Sort (cost=31465.
84.
.
31715.
84 rows=100000 width=197)
Sort Key: a.
aid
-> Hash Join (cost=1.
02.
.
4016.
02 rows=100000 width=197)
Hash Cond: (a.
bid = b.
bid)
-> Seq Scan on pgbench_accounts a (cost=0.
00.
.
2640.
00 rows=100000 width=97)
-> Hash (cost=1.
01.
.
1.
01 rows=1 width=100)
-> Seq Scan on pgbench_branches b (cost=0.
00.
.
1.
01 rows=1 width=100)
(7 rows)실험에서는, 먼저 소규모 4B 모델(Qwen3.
8-4B-Distill)을 사용하고, 지도 파인튜닝(SFT)을 통해 inspect_relation·get_column_stats·get_plan·evaluate_candidate와 같은 도구를 제공하는 "qo-agent" 하네스의 언어를 학습시켰습니다.
또한 교사 모델에는 GPT-6 Astra 또는 Qwen 3.
8 2.
4T 중 어느 것을 선택할지를 다각적으로 평가한 후 GPT-6 Astra를 채택하고 있습니다.
SFT 이후, 모델은 에이전트 기반 강화학습(RL)으로 추가 훈련되었습니다.
RL에서는 모델이 생성한 쿼리 플랜을 실제 실행 시간으로 평가하고, 더 나은 플랜을 생성하도록 모델의 가중치를 업데이트했습니다.
RL 과정에서는 노이즈가 많은 측정 환경에서도 신뢰할 수 있는 보상 설계와, 학습을 안정화하기 위해 커스터마이즈한 GRPO(Group Relative Policy Optimization) 변형이 중요한 것으로 나타났습니다.
훈련 결과, 4B 모델은 SFT와 RL을 모두 거쳐 PostgreSQL의 기본 플랜을 크게 웃도는 쿼리 플랜을 생성하는 데 성공했습니다.
특히, 조인 부하가 높은 113개의 쿼리로 이루어진 "조인 순서 벤치마크(JOB)" 데이터셋에서 기하 평균으로 1.81배의 속도 향상을 달성했습니다.
즉, 모델이 단순히 도구의 사용법을 배운 것을 넘어, 조인 순서 변경·스캔 방법 최적화·병렬 실행 활용과 같은 쿼리 최적화에서의 유효한 전략을 학습할 수 있었음을 시사합니다.
이 쿼리를 실행하면, 2000년부터 2009년 사이에 릴리스한 타이틀 수가 많은 순서대로 정렬한 10개의 일본 기업이 출력됩니다.
데이터베이스가 쿼리 결과를 취득하기 위해 거친 경로는 반드시 정해져 있는 것이 아니라 “필터링 조건”이 크게 관여합니다.
결과가 크게 좁혀지는 “필터링 조건”의 영향을 설명하기 위해, 앞선 쿼리에서 “일본 기업” 필터와 “기간” 필터를 제외하고 필터링 조건이 없는 상태로 만듭니다.
SELECT cn.
name,
COUNT(*) AS titles
FROM title AS t,
movie_companies AS mc,
company_name AS cn
WHERE t.
id = mc.
movie_id
AND mc.
company_id = cn.
id
GROUP BY cn.
name
ORDER BY titles DESC
LIMIT 10;하면 쿼리가 참조하고 있는 테이블은 "t(title)""mc(movie_companies)""cn(company_name)"의 3개이며, mc와 cn은 "mc.
company_id = cn.
id"에 의해 결합되고, t와 mc는 "t.
id = mc.
movie_id"에 의해 결합되고 있음을 알 수 있습니다.
따라서, 최종 쿼리 결과를 얻기 위해서는 어느 조인을 먼저 수행하는가에 따라 다음 중 하나의 경로를 거쳐 3개의 테이블을 모두 조인해야 합니다.
다음으로, 테이블 또는 쿼리 결과의 카디널리티, 즉 포함된 행 수에 대해 생각해 봅니다.
최종 쿼리 결과에 관련된 테이블의 카디널리티가 다음과 같다고 가정합니다.
• cn(company_name): 10만 행 • mc(movie_companies): 200만 행 • t(title): 100만 행 조인을 고려하면 다음과 같은 카디널리티가 얻어집니다.
3개의 테이블을 조인하는 순서에 관계없이, 항상 동일한 200만 행이 두 번째 조인에 전달됩니다.
이제 필터 조건을 다시 적용해 보겠습니다.
그러면 카디널리티는 다음과 같이 됩니다.
• cn′: 5000행(10만 개 기업 중 5%가 일본 기업이라고 가정한 경우) • mc: 200만 행(필터 조건에 의한 변화 없음) • t′: 20만 행(100만 타이틀 중 20%가 2000년대에 제작되었다고 가정한 경우) 조인을 고려하면 다음과 같은 카디널리티가 됩니다.
첫 번째 조인 순서에서는 200만 건의 movie_companies 항목에서 일본 기업인 5%의 기업으로 좁혀집니다.
균일 분포를 가정하면, 이 조인에 의해 약 10만 행이 생성됩니다.
이 결과를 필터링된 title 테이블과 조인하면, 그 중 2000년대에 제작된 타이틀에 해당하는 20%만 남습니다.
반면, 두 번째 조인 순서에서는 200만 건의 movie_companies 엔트리에서 2000년대에 생성된 타이틀의 20%로 좁힙니다.
마찬가지로 균일 분포를 가정하면, 첫 번째 조인에 의해 약 40만 행이 생성되고, 이 40만 행이 두 번째 조인에 전달됩니다.
즉, 첫 번째 조인 순서에서는 두 번째 조인에 전달되는 중간 결과가 약 10만 행인 데 비해, 두 번째 조인 순서에서는 약 40만 행이 되므로, 후자를 선택하면 두 번째 조인에서 처리하는 행 수가 약 4배가 됩니다.
이야기를 더욱 복잡하게 만드는 것은, 테이블을 조인하는 알고리즘에는 다음 3가지가 있다는 점입니다.
·해시 조인
·머지 조인
·네스티드 루프 조인
또한 조인 알고리즘에 영향을 미치므로 가환성에 대해서도 고려할 필요가 생기기 때문에, 결과적으로 3개의 테이블을 조인하는 조합은 8가지가 됩니다.
게다가, 각 테이블을 스캔하는 방법에는 다양한 것들이 있습니다.
대표적인 것으로 다음 4가지를 생각합니다.
·시퀀셜
·인덱스
·인덱스 온리
·비트맵
모두를 감안하면, 앞선 쿼리를 실행하는 방법은 4608가지나 됩니다.
또한, 조인하는 테이블이 늘어나면 조합의 수는 폭발적으로 증가합니다.
중요한 점은, PostgreSQL은 쿼리 플래닝 중에 실제 카디널리티를 카운트할 수 없다는 것입니다.
카디널리티를 알려면 각 테이블·쿼리 결과를 실제로 조인하여 결과 행 수를 카운트해야 하지만, 쿼리 옵티마이저에 요구되는 고속성은 완전히 손상되어 버립니다.
쿼리 옵티마이저의 역할은 완벽한 비용 최소화가 아니라 많은 쿼리에서 충분한 성능을 낼 수 있는 것이므로, PostgreSQL은 통계 정보를 사용하여 카디널리티를 추정하고 있습니다.
PostgreSQL은 시스템 카탈로그 "pg_statistic"에 데이터의 분산도 등의 통계 데이터를 저장하고 있으며, 가장 효율적인 쿼리 플랜을 작성하기 위해 이용하고 있습니다.
다만 조인이 얽히게 되면 이야기는 다소 복잡해집니다.
PostgreSQL은 어떤 테이블의 행이 다른 테이블에 어떻게 분포되어 있는지를 알 방법이 없으므로, 어떤 값이 첫 번째 테이블에 출현하는 비율을 그대로 두 번째 테이블에도 적용할 수 있다고 가정하는 "균일 분포 가정"이라는 사고방식을 사용합니다.
"균일 분포 가정"은 휴리스틱으로서는 문제가 없지만, 실패하면 영향은 막대한 것이 됩니다.
앞서 든 조인 순서의 예를 사용하면, 200만 건의 movie_companies 엔트리 중 5%가 일본 기업의 것이라는 가정 하에 필터링을 수행했습니다.
그러나 만약 그 5%의 일본 기업이 출시한 영화가 실제로는 전체 영화의 50%를 차지하고 있었다면, 첫 번째 조인에서 100만 행이 생성될 수도 있습니다.
초기 조인 처리에서 잘못된 추정이 하나라도 있으면, 그 영향이 조인 트리 전체로 파급되어 다른 모든 추정을 망칠 수 있다는 것입니다.
PostgreSQL은 항상 비용이 가장 낮은 실행 계획을 선택하지만, 소스 코드를 변경하지 않는 한 그 비용 모델을 바꿀 수는 없습니다.
이에 LLM으로 쿼리 플랜을 개선할 수 있지 않을까 하는 착상에서, PostgreSQL의 확장 기능인 pg_hint_plan을 이용하여 특정 실행 계획을 지시하는 "힌트"를 LLM에 생성시키는 연구가 시작되었습니다.
pg_hint_plan을 이용하는 방법은 SQL문 바로 앞에 구조화된 "힌트"를 주석으로 추가하기만 하면 됩니다.
/*+
HashJoin(a b)
SeqScan(a)
*/
EXPLAIN SELECT *
FROM pgbench_branches b
JOIN pgbench_accounts a ON b.
bid = a.
bid
ORDER BY a.
번역 대상:
Sort (cost=31465.84..31715.84 rows=100000 width=197)
Sort Key: a.aid
-> Hash Join (cost=1.02..4016.02 rows=100000 width=197)
Hash Cond: (a.bid = b.bid)
-> Seq Scan on pgbench_accounts a (cost=0.00..2640.00 rows=100000 width=97)
-> Hash (cost=1.01..1.01 rows=1 width=100)
-> Seq Scan on pgbench_branches b (cost=0.00..1.01 rows=1 width=100)
(7 rows)실험에서는 먼저 소규모 4B 모델(Qwen3.8-4B-Distill)을 사용하고, 지도 파인튜닝(SFT)을 통해 inspect_relation·get_column_stats·get_plan·evaluate_candidate와 같은 도구를 제공하는 “qo-agent” 하네스의 언어를 학습시켰습니다.
참고로 교사 모델에는 GPT-6 Astra 또는 Qwen 3.8 2.
번역 대상:
4T 중 어느 것을 선택할지를 다면적으로 평가한 후 GPT-6 Astra를 채택하고 있습니다.
SFT 이후, 모델은 에이전트 기반 강화학습(RL)으로 추가 훈련되었습니다.
RL에서는 모델이 생성한 쿼리 플랜을 실제 실행 시간으로 평가하고, 더 나은 플랜을 생성하도록 모델의 가중치를 업데이트했습니다.
RL 프로세스에서는 노이즈가 많은 측정 환경에서도 신뢰할 수 있는 보상 설계와, 학습을 안정시키기 위해 커스터마이즈한 GRPO(Group Relative Policy Optimization) 변형이 중요한 것으로 나타났습니다.
훈련 결과, 4B 모델은 SFT와 RL을 모두 통해 PostgreSQL의 기본 플랜을 크게 상회하는 쿼리 플랜을 생성하는 데 성공했습니다.
특히, 조인 부하가 높은 113개의 쿼리로 구성된 “조인 순서 벤치마크(JOB)” 데이터셋에서 기하 평균으로 1.81배의 속도 향상을 달성했습니다.
즉, 모델이 단순히 도구 사용법을 배운 것을 넘어, 조인 순서 변경·스캔 방법 최적화·병렬 실행 활용과 같은 쿼리 최적화에서의 유효한 전략을 학습할 수 있었음을 시사합니다.
-- 회사 역할 테이블 조회 [4 행]
company_type (
id INTEGER PRIMARY KEY,
kind TEXT -- '생산 회사', '유통사', ...
)
aid; 위 힌트는 pgbench_accounts 및 pgbench_branches의 조인에 HashJoin을 사용하고 pgbench_accounts 테이블에 대해 순차 스캔을 수행하도록 지정했습니다.
실제 실행 계획도 지정대로 작동하고 있습니다.
QUERY PLAN
연구자 그룹의 연구를 통해, 소규모 오픈 웨이트 모델이라도 적절한 훈련과 인프라 구조로 특정 도메인 작업에서 최첨단 모델과 경쟁하거나 능가하는 성능을 보일 수 있다는 가능성이 제시되었습니다.
Google 우선 소스에 설정 Clipboard 기사 제목 및 URL 복사 XFacebookBlueskyDiscordThreads
aid; 위 힌트는 pgbench_accounts 및 pgbench_branches의 조인에 HashJoin을 사용하고 pgbench_accounts 테이블에 대해 순차 스캔을 수행하도록 지정했습니다.
실제 실행 계획도 지정대로 작동하고 있습니다.
QUERY PLAN
연구자 그룹의 연구를 통해, 소규모 오픈 웨이트 모델이라도 적절한 훈련과 인프라 구조로 특정 도메인 작업에서 최첨단 모델과 경쟁하거나 능가하는 성능을 보일 수 있다는 가능성이 제시되었습니다.
Google 우선 소스에 설정 Clipboard 기사 제목 및 URL 복사 XFacebookBlueskyDiscordThreads
PostgreSQL은 항상 가장 낮은 비용의 실행 계획을 선택하지만, 소스 코드를 변경하지 않는 한 비용 모델을 변경할 수 없습니다.
따라서 LLM으로 쿼리 계획을 개선할 수 있는지에 대한 아이디어에서 시작하여 PostgreSQL의 확장 기능 pg_hint_plan을 사용하여 특정 실행 계획을 지시하는 "힌트"를 LLM에 생성하도록 연구가 시작되었습니다.
pg_hint_plan을 사용하는 방법은 SQL 문 바로 앞에 구조화된 "힌트"를 주석으로 추가하는 것뿐입니다.
/*+
HashJoin(a b)
SeqScan(a)
*/
EXPLAIN SELECT *
FROM pgbench_branches b
JOIN pgbench_accounts a ON b.
bid = a.
bid
ORDER BY a.
aid; 위 힌트는 pgbench_accounts 및 pgbench_branches의 조인에 HashJoin을 사용하고 pgbench_accounts 테이블에 대해 순차 스캔을 수행하도록 지정했습니다.
실제 실행 계획도 지정대로 작동하고 있습니다.
QUERY PLAN
연구자 그룹의 연구를 통해, 소규모 오픈 웨이트 모델이라도 적절한 훈련과 인프라 구조로 특정 도메인 작업에서 최첨단 모델과 경쟁하거나 능가하는 성능을 보일 수 있다는 가능성이 제시되었습니다.
Google 우선 소스에 설정 Clipboard 기사 제목 및 URL 복사 XFacebookBlueskyDiscordThreads
그러므로 쿼리가 참조하는 테이블은 “t(title)”, “mc(movie_companies)”, “cn(company_name)”의 세 개이며, mc와 cn은 “mc.company_id = cn.id”에 의해 결합되고, t와 mc는 “t.id = mc.movie_id”에 의해 결합된다는 것을 알 수 있습니다. 따라서 최종 쿼리 결과 획득을 위해서는 어떤 결합을 먼저 수행할지가 중요하며, 다음 중 하나의 경로를 통해 세 개의 테이블을 모두 결합해야 합니다.
다음으로, 테이블 또는 쿼리 결과의 카디날리티, 즉 포함된 행 수에 대해 고려해 보겠습니다. 최종 쿼리 결과와 관련된 테이블의 카디날리티는 다음과 같이 가정합니다.
* cn(company_name): 10만 행
* mc(movie_companies): 200만 행
* t(title): 100만 행
결합을 고려하면 다음과 같은 카디날리티가 발생합니다.
네 개의 테이블을 결합하는 순서와 관계없이 항상 동일한 200만 행이 2차 결합에 걸쳐집니다. 따라서 필터링 조건을 원래대로 되돌려 봅시다. 그러면 카디날리티는 다음과 같이 됩니다.
• cn′: 5,000행 (10만 개 기업 중 5%가 일본 기업이라고 가정할 경우)
• mc: 200만 행 (필터링 조건에 따른 변화 없음)
• t′: 20만 행 (100만 건 중 20%가 2000년대에 제작되었다고 가정할 경우)
결합을 고려하면 다음과 같은 카디날리티가 됩니다.
1차 결합 순서에서는 200만 건의 movie_companies 엔트리에서 일본 기업인 5%의 기업으로 필터링합니다. 균일 분포를 가정하면 이 결합으로 인해 약 10만 행이 생성됩니다. 이 결과를 필터링된 title 테이블과 결합하면 그 중 2000년대에 제작된 제목에 해당되는 20%만 남게 됩니다. 반면에 2차 결합 순서에서는 200만 건의 movie_companies 엔트리에서 2000년대에 제작된 제목의 20%로 필터링합니다. 마찬가지로 균일 분포를 가정하면 첫 번째 결합으로 인해 약 40만 행이 생성되고, 이 40만 행이 2차 결합에 전달됩니다.
즉, 첫 번째 병합 순서에서는 두 번째 병합에 전달되는 중간 결과가 약 10만 행인 반면, 두 번째 병합 순서에서는 약 40만 행이 되므로, 후자를 선택하면 두 번째 병합에서 처리하는 행 수가 약 4배가 됩니다. 더하여 이야기를 복잡하게 만드는 것은 테이블을 병합하는 알고리즘에는 다음과 같은 3가지가 있다는 것입니다. ·해시 병합 ·병합 병합 ·네스트 루프 병합 또한 병합 알고리즘에 영향을 미치는 것에서부터 교환성에 대해서도 고려해야 하므로, 결과적으로 3개의 테이블을 병합하는 조합은 8가지가 됩니다.
게다가, 각 테이블을 스캔하는 방법에는 다양한 것이 있습니다. 대표적인 것으로 다음과 같은 4가지를 고려합니다. ·순차 ·인덱스 ·인덱스 ·온리 비트맵 모든 것을 합리하면, 앞서 언급한 쿼리를 실행하는 방법은 4608가지가 있습니다.
한편, 병합하는 테이블이 증가하면 조합의 수는 폭발적으로 증가합니다.
중요한 점은 PostgreSQL이 쿼리 플래닝 중에 실제 카디나リティ를 셀 수 없다는 것입니다. 카디나リティ를 알기 위해서는 각 테이블 및 쿼리 결과를 실제로 결합하여 결과의 행 수를 카운트해야 하지만, 쿼리오프티마이저에 요구되는 고성은 완전히 훼손됩니다. 쿼리오프티마이저의 역할은 완벽한 비용 최소화가 아닌 많은 쿼리에서 충분한 성능을 낼 수 있도록 하는 것이므로, PostgreSQL은 통계 정보를 사용하여 카디나リティ를 추정합니다. PostgreSQL은 시스템 카탈로그 “pg_statistic”에 데이터의 분산도 등의 통계 데이터를 저장하여 가장 효율적인 쿼리 플랜을 생성하기 위해 활용합니다. 그러나 결합이 얽히면 이야기는 다소 복잡해집니다. PostgreSQL은 특정 테이블의 행이 다른 테이블에 어떻게 분포하는지 알 수 있는 방법이 없으므로, 특정 값이 1차 테이블에 나타나는 비율을 그대로 2차 테이블에도 적용할 수 있다고 가정하는 “일등분포의 가정”이라는 개념을 사용합니다. “일등분포의 가정”은 휴리스틱으로서는 문제가 없지만, 실패하면 영향은 엄청난 수준이 됩니다. 앞서 언급한 결합 순서의 예시를 사용하면, 200만 건의 movie_companies 엔트리 중 5%가 일본 기업의 것이라고 가정하고 필터링을 수행했습니다.
하지만 만약 그 5%의 일본 기업이 발표한 영화가 실제로는 전체 영화의 50%를 차지했다면, 첫 번째 병합 시 100만 줄이 생성될 수 있습니다. 초기 병합 처리에서 잘못된 추정이 1개라도 발생하면, 그 영향이 병합 트리의 전체에 파급되어 다른 모든 추정을 망칠 수 있습니다.
정렬 (비용=31465.84, 행=100000, 폭=197)
정렬 키: a. aid
-> 해시 결합 (비용=1.02, 4016.02 행=100000, 폭=197)
해시 조건: (a. bid = b. bid)
-> pgbench_accounts 테이블 스캔 (비용=0.00, 2640.00 행=100000, 폭=97)
-> 해시 (비용=1.01, 1.01 행=1 폭=100)
-> pgbench_branches 테이블 스캔 (비용=0.00, 1.01 행=1 폭=100)
(7행)실험에서는 먼저 소규모의 4B 모델(Qwen3.8-4B-Distill)을 사용하고, 지도 학습을 통해 inspect_relation, get_column_stats, get_plan, evaluate_candidate와 같은 도구를 제공하는 “qo-agent” 하르네스의 언어를 학습시켰습니다.
덧붙여 교사 모델에는 GPT-6 아스트라 또는 Qwen 3.8 2.
4T 모델 중 어느 것을 선택할지 다각적으로 평가한 후 GPT-6 Astra를 채택하고 있습니다.
SFT(선형 방사형 표현 학습) 이후, 모델은 에이전트 기반의 강화 학습(RL)에 의해 더욱 추가적으로 훈련되었습니다.
RL(강화 학습)에서는 모델이 생성한 쿼리 플랜을 실제 실행 시간으로 평가하고, 더 나은 플랜을 생성하도록 모델의 가중치를 업데이트했습니다.
RL(강화 학습) 프로세스에서는 노이즈가 많은 측정 환경에서도 신뢰성 있는 보상 설계와 학습을 안정화하기 위해 맞춤화된 GRPO(Group Relative Policy Optimization) 변종이 중요함이 확인되었습니다.
훈련 결과, 4B 모델은 SFT와 RL 양쪽 모두를 통해 PostgreSQL의 기본 플랜을 훨씬 능가하는 쿼리 플랜을 생성하는 데 성공했습니다.
특히, 조인 부하가 높은 113개의 쿼리에서 구성된 “조인 순서 벤치마크(JOB)” 데이터셋에서 기하 평균적으로 1.81배의 속도 향상을 달성했습니다.
즉, 모델이 단순히 도구 사용법을 학습한 것뿐만 아니라, 조인 순서 변경, 스캔 방법 최적화, 병렬 실행 활용과 같은 쿼리 최적화에 있어서 유효한 전략을 학습할 수 있었음을 시사합니다.
PostgreSQL는 항상 가장 낮은 비용의 실행 계획을 선택하지만, 소스 코드를 변경하지 않는 한 그 비용 모델을 바꿀 수 없습니다. 따라서 LLM에 의해 쿼리 계획을 개선할 수 있는지에 대한 아이디어에서 시작하여 PostgreSQL의 확장 기능 pg_hint_plan을 활용하고, 특정 실행 계획을 지시하는 “힌트”를 LLM에 생성하도록 하는 연구가 시작되었습니다. pg_hint_plan을 사용하는 방법은 SQL 문 직전에 구조화된 “힌트”를 주석으로 추가하는 것뿐입니다. /*+
HashJoin(a b)
SeqScan(a)
*/
EXPLAIN SELECT *
FROM pgbench_branches b
JOIN pgbench_accounts a ON b.bid = a.bid
ORDER BY a.aid; 위의 힌트는 pgbench_accounts 및 pgbench_branches의 조인에 HashJoin을 사용하고, pgbench_accounts 테이블에 대해 시퀀シャル 스캔을 수행하도록 지정합니다. 실제 실행 계획도 지정된 대로 작동합니다. 쿼리 계획
반상 씨의 연구를 통해, 소규모의 오픈 웨이트 모델이라도 적절한 훈련과 인프라 구조로 인해 특정 도메인 작업에서 최첨단 모델에 근접하거나 능가하는 성능을 발휘할 수 있는 가능성이 제시되었습니다.
Google 우선 소스에 설정 클립보드 기사 제목과 URL 복사 X Facebook Bluesky Discord Threads
정렬 (비용=31465.84, 행=100000, 폭=197)
정렬 키: a. aid
-> 해시 결합 (비용=1.02, 행=100000, 폭=197)
해시 조건: (a. bid = b. bid)
-> pgbench_accounts 테이블에 대한 순차 스캔 (비용=0.00, 행=100000, 폭=97)
-> 해시 (비용=1.01, 1.01 행=1 폭=100)
-> pgbench_branches 테이블에 대한 순차 스캔 (비용=0.00, 1.01 행=1 폭=100)
(7행)실험에서는 먼저 소규모의 4B 모델(Qwen3.8-4B-Distill)을 사용하고, 지도 학습을 통해 inspect_relation, get_column_stats, get_plan, evaluate_candidate와 같은 도구를 제공하는 “qo-agent” 하르네스의 언어를 학습시켰습니다.
물론입니다. 다음은 번역된 한국어 텍스트입니다.
한편, 교사 모델에는 GPT-6 Astra 또는 Qwen 3.8 2.가 포함됩니다.
4T 모델 중 어느 것을 선택할지 다각적으로 평가한 후 GPT-6 Astra를 채택하고 있습니다.
SFT(선형 방사형 표현 학습) 이후, 모델은 에이전트 기반의 강화 학습(RL)에 의해 더욱 추가적으로 훈련되었습니다.
RL(강화 학습)에서는 모델이 생성한 쿼리 플랜을 실제 실행 시간으로 평가하고, 더 나은 플랜을 생성하도록 모델의 가중치를 업데이트했습니다.
RL(강화 학습) 프로세스에서는 노이즈가 많은 측정 환경에서도 신뢰성 있는 보상 설계와 학습을 안정화하기 위해 맞춤화된 GRPO(Group Relative Policy Optimization) 변종이 중요함이 확인되었습니다.
훈련 결과, 4B 모델은 SFT와 RL 양쪽 모두를 통해 PostgreSQL의 기본 플랜을 훨씬 능가하는 쿼리 플랜을 생성하는 데 성공했습니다.
특히, 조인 부하가 높은 113개의 쿼리에서 구성된 “조인 순서 벤치마크(JOB)” 데이터셋에서 기하 평균적으로 1.81배의 속도 향상을 달성했습니다.
즉, 모델이 단순히 도구 사용법을 학습한 것뿐만 아니라, 조인 순서 변경, 스캔 방법 최적화, 병렬 실행 활용과 같은 쿼리 최적화에 있어서 유효한 전략을 학습할 수 있었음을 시사합니다.
위 힌트는 pgbench_accounts와 pgbench_branches의 결합에 HashJoin을 사용하고, pgbench_accounts 테이블에 대해 시퀀シャル 스캔을 수행하도록 지정하고 있습니다. 실제 실행 계획 또한 지정된 대로 동작하고 있습니다. QUERY PLAN
연구자 그룹의 연구를 통해, 소규모의 오픈 웨이트 모델이라도 적절한 훈련과 인프라 구조에 의해 특정 도메인 태스크에서 최첨단 모델에 근접하거나 능가하는 성능을 발휘할 가능성이 제시되었습니다.
Google 우선 소스에 설정, 클립보드 기사 제목과 URL을 복사, XFacebookBlueskyDiscordThreads
실험에서는 먼저 소규모의 4B 모델(Qwen3.8-4B-Distill)을 사용하고, 지도 학습 기반 미세 조정(SFT)을 통해 inspect_relation, get_column_stats, get_plan, evaluate_candidate와 같은 툴을 제공하는 “qo-agent” 하르네스의 언어를 학습시켰습니다. 한편, 지도 모델에는 GPT-6 Astra 또는 Qwen 3.8 2.4T 중 하나를 선택하는 것을 다각적으로 평가한 후 GPT-6 Astra를 채택하고 있습니다.
SFT 이후, 모델은 에이전트 기반의 강화학습(RL)에 의해 더욱 추가적으로 훈련되었습니다. RL에서는 모델이 생성한 쿼리 플랜을 실제 실행 시간으로 평가하고, 더 나은 플랜을 생성하도록 모델의 가중치를 업데이트했습니다. RL 프로세스에서는 노이즈가 많은 측정 환경에서도 신뢰성 있는 보상 설계와 학습을 안정화하기 위해 맞춤화된 GRPO(Group Relative Policy Optimization) 변종이 중요함이 확인되었습니다.
훈련 결과, 4B 모델은 SFT와 RL 양쪽 모두를 통해 PostgreSQL의 기본 플랜을 훨씬 능가하는 쿼리 플랜을 생성하는 데 성공했습니다. 특히, 조인 부하가 높은 113개의 쿼리에서 구성된 “조인 순서 벤치마크(JOB)” 데이터셋에서 기하 평균으로 1.81배의 속도 향상을 달성했습니다. 즉, 모델이 단순히 도구 사용법을 학습한 것뿐만 아니라, 조인 순서 변경, 스캔 방법 최적화, 병렬 실행 활용과 같은 쿼리 최적화에 있어서의 유효한 전략을 학습할 수 있음을 시사합니다.
반스(Bansal) 씨의 연구를 통해, 소규모의 오픈 웨이트 모델이라도 적절한 훈련과 인프라 구조를 갖추어 특정 도메인 태스크에서 최첨단 모델에 필적하거나 능가하는 성능을 발휘할 수 있는 가능성이 제시되었습니다.
원문 보기 | 출처: Gigazine