PostgreSQL용 AlloyDB 데이터베이스 쿼리 성능을 최적화하려면 AI 기반 인사이트를 사용하여 SQL 쿼리를 조정하면 됩니다. 대규모 언어 모델(LLM)을 기반으로 하는 ai.tune_sql 함수는 쿼리 실행 계획을 분석하여 복잡한 병목 현상을 식별하고 최적화를 위해 실행할 수 있는 권장사항을 제공합니다.
ai.tune_sql 함수를 호출하면 쿼리를 분석하고 다음 영역에서 쿼리 통계 및 권장사항을 제공합니다.
- 쿼리 요약: 쿼리 성능 병목 현상을 분석합니다.
- 쿼리 재작성: 원래 쿼리를 동일하고 더 효율적인 쿼리로 재작성하는 최적화 기법을 제안합니다.
- 색인 추천: 표현식의 색인, 일반화된 역색인 (GIN) 색인, 확장 가능한 최근접 이웃 (ScaNN) 색인과 같은 색인 유형을 추천합니다.
- 통계 수집 권장사항: 쿼리 실행 계획을 기반으로 여러 열에 대한 테이블
ANALYZE및 통계 수집을 권장합니다. - GUC (Grand Unified Configuration) 플래그 조정: 실행 계획과 메모리, 버퍼 사용량, 크기와 같은 시스템 정보를 분석하여 새로운 GUC 값을 추천합니다.
- 분석 및 진공: 분석 및 진공 기록과 라이브 및 데드 튜플 수를 기반으로 테이블에서
ANALYZE및VACUUM를 실행하는 것이 좋습니다.
제한사항
SQL 쿼리 조정에는 다음과 같은 제한사항이 있습니다.
- 준비된 문 및 파라미터화된 문: 준비된 문 및 파라미터화된 문의 경우
ai.tune_sql는 쿼리 재작성 권장사항을 제공하지 않지만 다른 조정 권장사항은 계속 제공합니다. 쿼리 통계 또는pg_stat_statements에서 복사한$1또는$2와 같은 파라미터 자리표시자가 포함된 쿼리에 대한 쿼리 재작성 추천을 받으려면ai.tune_sql를 실행하기 전에 자리표시자를 대표 리터럴 값으로 바꿉니다. explain_analyze가TRUE인 경우에만 읽기 전용SELECT문:explain_analyze가TRUE로 설정된 경우 단일 읽기 전용SELECT문만 지원됩니다.INSERT,UPDATE,DELETE,SELECT INTO또는SELECT FOR UPDATE와 같은 다른 문에서explain_analyze를TRUE로 설정하여ai.tune_sql를 실행하면 오류 메시지가 반환됩니다.
시작하기 전에
다음 항목이 있는지 확인합니다.
- PostgreSQL 18 이상을 실행하는 AlloyDB 인스턴스 자세한 내용은 클러스터 및 클러스터의 기본 인스턴스 만들기를 참조하세요.
psql클라이언트가 인스턴스에 연결되거나 AlloyDB Studio에 액세스합니다.- Vertex AI 통합이 사용 설정됨.
google_ml_integration확장 프로그램 버전 1.6.2 이상이 설치되고google_db_advisor및hypopg확장 프로그램이 사용 설정되어 있습니다.ai.tune_sql에서 색인 추천을 평가하고 생성하는 데 사용하는hypopg확장 프로그램을 사용 설정하려면CREATE EXTENSION IF NOT EXISTS hypopg;를 실행합니다.
SQL 조정 사용 설정
LLM을 등록하고 필수 데이터베이스 플래그를 사용 설정합니다.
인스턴스 리전에 전역 모델 엔드포인트 또는 리전 모델 엔드포인트를 등록하여
ai.tune_sql함수에 대한 Gemini 모델 엔드포인트를 만듭니다.인스턴스 리전에서 모델을 사용할 수 없는 경우(예:
locations/global의gemini-3.5-flash) 전역 모델 엔드포인트를 등록하려면 다음 문을 실행합니다.CALL google_ml.create_model( model_id => 'gemini-3.5-flash-global', model_request_url => 'https://aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/global/publishers/google/models/gemini-3.5-flash:generateContent', model_qualified_name => 'gemini-3.5-flash', model_provider => 'google', model_type => 'llm', model_auth_type => 'alloydb_service_agent_iam');PROJECT_ID를 모델을 사용할 수 있는 프로젝트의 ID로 바꿉니다.인스턴스 리전(예:
locations/REGION_ID의gemini-2.5-pro)에 리전 모델 엔드포인트를 등록하려면 다음 문을 실행합니다.CALL google_ml.create_model( model_id => 'gemini-2.5-pro', model_request_url => 'https://REGION_ID-aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/REGION_ID/publishers/google/models/gemini-2.5-pro:generateContent', model_qualified_name => 'gemini-2.5-pro', model_provider => 'google', model_type => 'llm', model_auth_type => 'alloydb_service_agent_iam');다음을 바꿉니다.
REGION_ID: AlloyDB 인스턴스의 리전(예:us-central1)PROJECT_ID: 모델을 사용할 수 있는 프로젝트의 ID입니다.
현재 데이터베이스 세션에서 SQL 쿼리 조정 추천의 모델 ID를 설정합니다. 이전 단계에서 등록한
model_id를 지정합니다(예:'gemini-3.5-flash-global'또는'gemini-2.5-pro').SET google_db_advisor.llm_model_id = 'gemini-3.5-flash-global';google_db_advisor.enable_ai_tune_sql플래그를 설정하여 현재 데이터베이스 세션에 SQL 쿼리 조정을 사용 설정합니다.SET google_db_advisor.enable_ai_tune_sql = on;SET는 현재 데이터베이스 세션에만 적용되므로 두 플래그 모두에 대해ALTER DATABASE DATABASE_NAME SET FLAG_NAME = VALUE;를 실행하거나 인스턴스의 데이터베이스 플래그를 구성하여 세션 간에 이러한 설정을 유지할 수 있습니다.
SQL 쿼리 수동 조정
SQL 쿼리를 수동으로 조정하려면 SELECT 문을 사용하여 ai.tune_sql 함수를 실행합니다.
psql클라이언트를 AlloyDB 인스턴스에 연결하거나 AlloyDB Studio를 엽니다.SET를 사용하여google_db_advisor.llm_model_id및google_db_advisor.enable_ai_tune_sql를 구성한 경우ai.tune_sql를 호출하기 전에 동일한 데이터베이스 세션에서 해당SET문을 실행합니다.SQL 쿼리를 조정하려면
ai.tune_sql함수를 실행합니다. 이 함수는 쿼리 관련 스키마를 검색하고 쿼리와 함께 성능 분석을 위해 LLM에 전송합니다.SELECT ai.tune_sql(query => 'SQL_QUERY', explain_analyze => TRUE);SQL_QUERY를 분석하려는 SQL 쿼리로 바꿉니다.기본적으로 실행 매개변수 (
explain_analyze)는TRUE로 설정됩니다.TRUE로 설정하면 함수가EXPLAIN ANALYZE를 실행하여 쿼리를 실행하고 실행 계획을 캡처합니다. 이 접근 방식은 보다 정확하고 유용한 쿼리 조정 조언을 제공합니다.explain_analyze를FALSE로 설정하면 함수는 쿼리를 실행하지 않고 계획을 생성하기 위해EXPLAIN만 실행합니다.
성능 권장사항이 포함된 쿼리 계획 보기
출력에는 먼저 표준 쿼리 계획이 표시되고 그 뒤에 AI 생성 추천이 자세히 표시됩니다. 추천에는 쿼리 요약, 추천 재작성, 통계 수집, GUC 플래그 조정, 비우기에 대한 추천이 포함됩니다. ai.tune_sql 함수가 추천을 생성하는 데 2~4분이 걸릴 수 있습니다.
다음 예시에서는 안티 조인 쿼리의 ai.tune_sql 출력을 보여줍니다.
SELECT ai.tune_sql(
'SELECT *
FROM store_sales
WHERE ss_customer_sk NOT IN (
SELECT sr_customer_sk
FROM store_returns
WHERE sr_item_sk = ss_item_sk)');
tune_sql
--------------------------------------------------
NOTE
--------------------------------------------------
Advices generated with the assistance of an LLM. Independent verification of these advices is required due to the potential for inaccuracies.
--------------------------------------------------
ORIGINAL QUERY PLAN
--------------------------------------------------
Seq Scan on store_sales (cost=0.00..111292742.52 rows=1440384 width=115) (actual time=0.092..103093.207 rows=1306312.00 loops=1)
Filter: (NOT (ANY (ss_customer_sk = (SubPlan 1).col1)))
Rows Removed by Filter: 1574198
Buffers: shared hit=72987539
SubPlan 1
-> Index Scan using idx_sr_item_sk on store_returns (cost=0.42..76.74 rows=18 width=4) (actual time=0.005..0.034 rows=19.38 loops=2880510)
Index Cond: (sr_item_sk = store_sales.ss_item_sk)
Index Searches: 2880510
Buffers: shared hit=72933670
Planning Time: 0.257 ms
--------------------------------------------------
QUERY SUMMARY
--------------------------------------------------
The original query plan shows a Sequential Scan on store_sales with a correlated SubPlan for the NOT IN condition. For each of the 1.44 million rows in store_sales, it executes an Index Scan on store_returns, resulting in over 2.88 million index searches. This nested loop execution is the primary bottleneck, taking over 103 seconds. Converting the correlated NOT IN subquery to an anti-join (NOT EXISTS) will allow the optimizer to use a Hash Anti Join, processing both tables in bulk and significantly reducing execution time.
--------------------------------------------------
AI SUGGESTED QUERY 1
--------------------------------------------------
SELECT * FROM store_sales WHERE NOT EXISTS (SELECT 1 FROM store_returns WHERE store_returns.sr_item_sk = store_sales.ss_item_sk AND (store_returns.sr_customer_sk = store_sales.ss_customer_sk OR store_returns.sr_customer_sk IS NULL OR store_sales.ss_customer_sk IS NULL));
Reason: The original query uses a NOT IN subquery which is executed as a correlated subquery (SubPlan), leading to millions of index scans. Rewriting to NOT EXISTS allows the optimizer to use a Hash Anti Join, which is much more efficient for large datasets. The OR IS NULL conditions ensure strict semantic equivalence with NOT IN when dealing with NULL values.
--------------------------------------------------
REWRITTEN QUERY PLAN
--------------------------------------------------
Gather (cost=8541.96..139088.40 rows=154194 width=115)
Workers Planned: 2
-> Parallel Hash Anti Join (cost=7541.96..122669.00 rows=64248 width=115)
Hash Cond: (store_sales.ss_item_sk = store_returns.sr_item_sk)
Join Filter: ((store_returns.sr_customer_sk = store_sales.ss_customer_sk) OR (store_returns.sr_customer_sk IS NULL) OR (store_sales.ss_customer_sk IS NULL))
-> Parallel Seq Scan on store_sales (cost=0.00..65872.20 rows=1200320 width=115)
-> Parallel Hash (cost=6044.76..6044.76 rows=119776 width=8)
-> Parallel Seq Scan on store_returns (cost=0.00..6044.76 rows=119776 width=8)
--------------------------------------------------
AI SUGGESTED INDEX 1
--------------------------------------------------
CREATE INDEX idx_sr_item_customer ON store_returns (sr_item_sk, sr_customer_sk);
Reason: A composite index on (sr_item_sk, sr_customer_sk) allows index-only scans for the subquery or early aggregation, reducing table access and speeding up the anti-join condition evaluation.
Note: Indexes are suggested solely based on the given query, but they don't consider the overall system workload.
The suggested indexes improve the performance of the original query.
--------------------------------------------------
GUC FLAGS TUNING
--------------------------------------------------
SET work_mem = '64MB'; The current work_mem of 4MB is too small for a Hash Anti Join on tables with hundreds of thousands of rows. Increasing it to 64MB allows the hash table for store_returns to fit entirely in memory, preventing costly disk spills during the join.
Execution Time: 103167.475 ms
(50 rows)
다음 단계
- 쿼리 통계 알아보기
- 색인 권장사항 적용