ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SQL 안티패턴 감지
    DB 2026. 4. 28. 01:48

    목표

    사용자의 SQL 쿼리를 실행 전/후로 분석하여 안티패턴을 감지하고 튜닝 포인트를 제공합니다.

    1. Dry-run — 실제 실행 없이 쿼리 구조를 정적 분석
    2. 필수 규칙 강제 — Critical 안티패턴은 차단 또는 강한 경고
    3. 실행 전 튜닝 포인트 — 정적 분석 / EXPLAIN 기반 개선 제안
    4. 실행 후 튜닝 포인트 — 실행 통계 기반 병목 진단

    참고 도구 분석

    도구 안티패턴 수 플랫폼 접근 방식

    BigQuery Anti-Pattern Recognition 11개 BigQuery 전용 AST 정적 분석 (ZetaSQL 파서)
    sqlcheck 29개 범용 RDBMS CLI 정적 분석 (C++)
    AltimateAI / altimate-code 19개 10개 웨어하우스 (Snowflake·BQ·Databricks 등) AI 기반 + 정적 분석
    sqlglot 31개 방언 (Trino·Databricks·Snowflake 등) Python 파서·트랜스파일러 (자체 구현 기반)

    BigQuery Anti-Pattern Recognition

    구글이 공식 오픈소스로 제공한 도구입니다. ZetaSQL 파서로 AST를 분석하고, 실제 쿼리 실행 없이 안티패턴을 탐지합니다.

    감지 안티패턴 11개

    # 이름 설명

    1 SimpleSelectStar SELECT * — 불필요한 전체 컬럼 조회
    2 SemiJoinWithoutAggregation IN 필터 서브쿼리에서 DISTINCT 누락
    3 MultipleCTEReferences 동일 CTE를 2회 이상 참조 (재연산 발생)
    4 OrderByWithoutLimit LIMIT 없는 ORDER BY
    5 RegexpContainsUsage REGEXP_CONTAINS 대신 LIKE 사용 권고
    6 LatestRecordWithAnalyticFun ROW_NUMBER()로 최신 레코드 필터링
    7 DynamicPredicate 서브쿼리 필터 → 정적 술어로 변환 권고
    8 WhereOrder WHERE 절 필터 순서 — 선택도 높은 조건 우선
    9 JoinOrder 테이블 크기 기반 JOIN 순서 최적화
    10 MissingDropStatement TEMP 테이블 생성 후 DROP 누락
    11 DroppedPersistentTable 스크립트 끝에서 영구 테이블 삭제 감지

    설치

    # 사전 조건: JDK 11+, Maven, Docker, gcloud CLI
    gcloud auth application-default login
    git clone <https://github.com/GoogleCloudPlatform/bigquery-antipattern-recognition.git>
    cd bigquery-antipattern-recognition
    
    # Docker 이미지 빌드
    mvn clean package jib:dockerBuild -DskipTests
    
    # 또는 JAR 직접 다운로드
    wget <https://github.com/GoogleCloudPlatform/bigquery-antipattern-recognition/releases/download/v1.0.0/bigquery-antipattern-recognition.jar>
    

    입력 옵션 (Input Flags)

    플래그 설명

    --query "SELECT ..." CLI에서 SQL 문자열 직접 입력
    --read_from_info_schema INFORMATION_SCHEMA.JOBS에서 쿼리 읽기
    --read_from_info_schema_days N 읽을 기간 (기본값: 1일)
    --info_schema_project <project> 분석 대상 프로젝트
    --info_schema_region us 리전 지정
    --read_from_info_schema_start_time "timestamp" 시작 시간 지정
    --read_from_info_schema_end_time "timestamp" 종료 시간 지정
    --info_schema_top_n_percentage_of_jobs N 상위 슬롯 소비 비율 (0~1, 예: 0.1 = 상위 10%)
    --input_bq_table project.dataset.table BigQuery 테이블에서 읽기 (컬럼: id, query)
    --input_file_path /path/to/file.sql 로컬 또는 GCS 파일에서 읽기
    --input_folder_path /path/to/folder 폴더 내 전체 .sql 파일 파싱
    --input_csv_file_path /path/to/file.csv CSV 파일에서 읽기 (컬럼: id, query)

    출력 옵션 (Output Flags)

    플래그 설명

    --output_file_path /path/to/output.csv CSV 파일로 출력
    --output_table "project.dataset.table" BigQuery 테이블에 결과 저장 (사전 생성 필요)
    --rewrite_sql Vertex AI로 최적화된 SQL 자동 재작성 (실험 기능)
    --processing_project_id <project> INFORMATION_SCHEMA 조회 및 결과 저장 프로젝트

    실행 예시

    인라인 쿼리 분석 (Docker)

    docker run -i bigquery-antipattern-recognition \\
      --query "SELECT * FROM \\`project.dataset.orders\\` WHERE YEAR(created_at) = 2024"
    
    # 출력 예시
    SimpleSelectStar: SELECT * at line 1. All columns on table are being selected.
    Non-sargable predicate: YEAR(created_at) at line 1 disables partition pruning.
    

    인라인 쿼리 분석 (JAR)

    java -jar bigquery-antipattern-recognition.jar \\
      --query "SELECT * FROM \\`project.dataset.table1\\`"
    

    INFORMATION_SCHEMA → BigQuery 테이블 저장

    docker run -v ~/.config:/root/.config -i bigquery-antipattern-recognition \\
      --read_from_info_schema \\
      --info_schema_project my-project \\
      --info_schema_region us \\
      --read_from_info_schema_days 7 \\
      --info_schema_top_n_percentage_of_jobs 0.1 \\
      --processing_project_id my-project \\
      --output_table "my-project.dataset.antipattern_output"
    

    AI 기반 SQL 자동 재작성

    docker run -v ~/.config:/root/.config -i bigquery-antipattern-recognition \\
      --query "SELECT col1 FROM table1 WHERE col2 LIKE '%abc%' AND col3 = 1" \\
      --rewrite_sql \\
      --processing_project_id my-project
    

    로컬 파일 → CSV 출력

    docker run -v /local/path:/data -i bigquery-antipattern-recognition \\
      --input_file_path /data/query.sql \\
      --output_file_path /data/output.csv
    

    폴더 내 전체 SQL 파일 분석

    docker run -v /local/path:/data -i bigquery-antipattern-recognition \\
      --input_folder_path /data/sql_files \\
      --output_file_path /data/output.csv
    

    출력 테이블 DDL

    CREATE OR REPLACE TABLE `project.dataset.antipattern_output_table` (
      job_id            STRING,
      user_email        STRING,
      query             STRING,
      recommendation    ARRAY<STRUCT<name STRING, description STRING>>,
      slot_hours        FLOAT64,
      optimized_sql     STRING,
      process_timestamp TIMESTAMP
    );
    

    결과 조회 쿼리

    SELECT job_id, user_email, query, recommendation, slot_hours
    FROM `project.dataset.antipattern_output_table`
    ORDER BY slot_hours DESC
    LIMIT 100;
    

    sqlcheck

    범용 SQL 안티패턴 탐지 CLI 도구 (C++)입니다. 29개 패턴을 4개 카테고리로 분류하여 분석합니다.

    쿼리 안티패턴 16개 (실무 관련도 높음)

    코드 이름 설명

    3001 SELECT * 전체 컬럼 조회
    3002 NULL Usage NULL 오용
    3004 String Concatenation 문자열 연결 성능
    3005 GROUP BY Usage GROUP BY 오용
    3006 ORDER BY RAND RAND() 정렬 — 전체 스캔
    3007 Pattern Matching 앞자리 와일드카드 LIKE '%...'
    3008 Spaghetti Query 지나치게 복잡한 단일 쿼리
    3009 Reduce JOINs 과도한 JOIN
    3010 Unnecessary DISTINCT 불필요한 DISTINCT
    3012 HAVING Clause HAVING 절 오용 (WHERE로 대체 가능)
    3013 Nested Subqueries 중첩 서브쿼리
    3014 OR Usage OR 연산자 — 인덱스 미활용
    3015 UNION Usage UNION (UNION ALL 권고)
    3016 DISTINCT & JOIN DISTINCT + JOIN 조합

    설치

    # macOS (DMG)
    wget <https://github.com/jarulraj/sqlcheck/releases/download/v1.3/sqlcheck-x86_64.dmg>
    cp /Volumes/sqlcheck-x86_64/bin/sqlcheck /usr/local/bin/
    
    # Ubuntu/Debian (.deb)
    wget <https://github.com/jarulraj/sqlcheck/releases/download/v1.3/sqlcheck-x86_64.deb>
    dpkg -i sqlcheck-x86_64.deb
    
    # CentOS/Fedora (.rpm)
    wget <https://github.com/jarulraj/sqlcheck/releases/download/v1.3/sqlcheck-x86_64.rpm>
    yum --nogpgcheck localinstall sqlcheck-x86_64.rpm
    
    # 소스 빌드 (g++ 4.9+, CMake 필요)
    git clone --recursive <https://github.com/jarulraj/sqlcheck.git>
    ./bootstrap
    cd build && cmake -DCMAKE_BUILD_TYPE=RELEASE .. && make && make install
    

    CLI 플래그

    플래그 설명

    -f, --file_name 분석할 SQL 파일 경로
    -r, --risk_level 탐지 위험도 필터 (1: 전체, 2: MEDIUM 이상, 3: HIGH만)
    -c, --color_mode 컬러 출력 활성화
    -v, --verbose_mode 상세 정보 출력
    --stdin 표준 입력에서 SQL 읽기

    실행 예시

    SQL 파일 분석

    sqlcheck -f my_query.sql
    
    -------------------------------------------------
    SQL ANTI-PATTERN REPORT :: my_query.sql
    -------------------------------------------------
    
    [my_query.sql]: (HIGH RISK) (QUERY ANTI-PATTERN) SELECT *
    Never use * in a SELECT. Always list out the columns needed.
    Pattern: SELECT *
    
    [my_query.sql]: (LOW RISK) (QUERY ANTI-PATTERN) SPAGHETTI QUERY ALERT
    This query is too complex. Consider splitting into simpler queries.
    
    ======================
    Total Anti-Patterns: 2
    High Risk: 1  Medium Risk: 0  Low Risk: 1
    

    상세 출력 + 색상 모드

    sqlcheck -f my_query.sql -v -c
    

    HIGH 위험도만 탐지

    sqlcheck -f my_query.sql -r 3
    

    파이프 입력 (stdin)

    echo "SELECT * FROM orders JOIN customers ON 1=1" | sqlcheck --stdin
    

    여러 파일 일괄 분석

    for f in ./queries/*.sql; do
      echo "=== $f ==="
      sqlcheck -f "$f" -r 2
    done
    

    CI/CD 파이프라인 연동

    # 안티패턴 발견 시 비정상 종료 → CI 실패 처리 가능
    sqlcheck -f migration.sql -r 3 || exit 1
    

    위험도 레벨

    레벨 범위 언제 사용

    1 LOW 이상 전체 코드 리뷰 시
    2 MEDIUM 이상 일반 개발 환경
    3 HIGH만 CI/CD 게이트, 배포 전 필수 체크

    AltimateAI / altimate-code

    AI 기반 데이터 엔지니어링 도구입니다. 10개 웨어하우스를 지원하며, Claude Code 통합이 가능합니다.

    탐지 안티패턴 (19개 규칙, 신뢰도 점수 포함)

    • SELECT *
    • 카르테시안 조인 (조건 없는 JOIN)
    • Non-sargable 조건절 (함수가 필터 컬럼에 적용)
    • 상관 서브쿼리 (Correlated Subquery)
      • 15개 추가 규칙

    지원 플랫폼

    Snowflake, BigQuery, Databricks, PostgreSQL, Redshift, ClickHouse, DuckDB, MySQL, SQL Server, Oracle, SQLite, MongoDB

    주요 기능

    • FinOps 분석: 비용 소비 쿼리 탐지, 웨어하우스 크기 최적화
    • 열 수준 계보: 조인/CTE/서브쿼리를 통해 컬럼 추적
    • dbt 통합: 매니페스트 파싱, 테스트 생성, 영향 분석
    • 교차 방언 변환: Snowflake ↔ BigQuery ↔ Databricks SQL 자동 변환
    • PII 감지: 30+ 패턴으로 민감 정보 스캔

    설치

    npm install -g altimate-code
    
    # LLM API 키 설정 (하나 이상 필요)
    export ANTHROPIC_API_KEY=your_key
    export OPENAI_API_KEY=your_key
    

    초기 설정

    altimate
    /connect        # LLM 공급자 선택 및 API 키 입력
    /discover       # dbt 프로젝트, 웨어하우스 연결 자동 감지
    /configure-claude  # Claude Code와 연동
    

    데이터베이스 연결 방식

    # ~/.dbt/profiles.yml 자동 감지 또는 직접 설정
    my_snowflake:
      type: snowflake
      account: myorg.us-east-1
      user: myuser
      password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
      database: MY_DB
      schema: PUBLIC
      warehouse: MY_WH
    

    에이전트 모드

    모드 역할 권한 권장 환경

    Analyst 데이터 탐색, SELECT 쿼리 읽기 전용 프로덕션 (안전)
    Builder dbt 모델/SQL 파이프라인 생성 읽기/쓰기 (DROP 금지) 개발 환경
    Plan 접근 방식 수립만 파일 읽기만 설계 단계

    주요 CLI 명령어 사용 예시

    안티패턴 탐지

    > Analyze this query for issues:
      SELECT * FROM orders o
      JOIN customers c ON o.id = c.order_id
      WHERE YEAR(o.created_at) = 2024
      ORDER BY o.total_amount
    
    [HIGH] SELECT * detected — specify required columns (confidence: 98%)
    [HIGH] Non-sargable predicate: YEAR(created_at)
      → Fix: created_at >= '2024-01-01' AND created_at < '2025-01-01'
    [MAJOR] ORDER BY without LIMIT — full sort on potentially large dataset
      → Fix: add LIMIT clause or use window function
    

    SQL 방언 변환

    > /sql-translate this Snowflake query to Databricks:
      SELECT DATEADD(day, 7, CURRENT_DATE()), ZEROIFNULL(revenue) FROM sales
    

    dbt 테스트 자동 생성

    > /generate-tests for models/staging/stg_orders.sql
    

    비용 리포트 조회

    > /cost-report
    

    컬럼 계보 추적

    > Trace column lineage for customer_id in fact_orders
    

    PII 탐지

    > Scan schema my_schema for PII columns
    

    지원 LLM

    Anthropic Claude · OpenAI · Google Gemini · Amazon Bedrock · Azure OpenAI · Mistral · Groq · Ollama


    sqlglot (자체 구현 시 파서 기반)

    Python SQL 파서 및 트랜스파일러입니다. Trino·Databricks·Snowflake 등 31개 방언을 공식 지원하며, 자체 안티패턴 엔진 구현 시 기반으로 활용합니다.

    설치

    pip install sqlglot
    
    # C 확장 버전 (성능 향상)
    pip install "sqlglot[c]"
    

    지원 방언 (주요)

    방언 지정 키워드

    Trino "trino"
    Databricks "databricks"
    Snowflake "snowflake"
    BigQuery "bigquery"
    Spark SQL "spark"
    Presto "presto"
    DuckDB "duckdb"
    PostgreSQL "postgres"

    기본 파싱

    import sqlglot
    from sqlglot import exp, parse_one
    
    # 방언을 명시해야 정확한 파싱이 가능합니다
    ast = parse_one(
        "SELECT * FROM orders WHERE YEAR(created_at) = 2024 ORDER BY id",
        dialect="trino"
    )
    

    AST 순회 방법

    # find: 첫 번째 매칭 노드 반환
    order = ast.find(exp.Order)
    
    # find_all: 모든 매칭 노드 반환
    for col in ast.find_all(exp.Column):
        print(col.alias_or_name)
    
    # walk: 전체 노드 순회
    for node in ast.walk():
        if isinstance(node, exp.Star):
            print("SELECT * 발견")
    

    안티패턴 탐지 예시 코드

    from sqlglot import exp, parse_one
    from dataclasses import dataclass
    from typing import List
    
    @dataclass
    class Issue:
        severity: str   # CRITICAL / MAJOR / MINOR
        message: str
        suggestion: str
    
    def detect_antipatterns(query: str, dialect: str = "trino") -> List[Issue]:
        ast = parse_one(query, dialect=dialect)
        issues = []
    
        # [CRITICAL] SELECT *
        for node in ast.find_all(exp.Star):
            issues.append(Issue(
                severity="CRITICAL",
                message="SELECT * 사용 — 불필요한 전체 컬럼 조회",
                suggestion="필요한 컬럼만 명시하세요"
            ))
    
        # [CRITICAL] Cartesian JOIN (조건 없는 JOIN)
        for join in ast.find_all(exp.Join):
            if not join.args.get("on") and not join.args.get("using"):
                if str(join.args.get("kind", "")).upper() not in ("CROSS",):
                    issues.append(Issue(
                        severity="CRITICAL",
                        message="조건 없는 JOIN 감지 — Cartesian product 발생 가능",
                        suggestion="ON 또는 USING 조건을 추가하세요"
                    ))
    
        # [CRITICAL] Non-sargable predicate (WHERE 절 함수 적용)
        where = ast.find(exp.Where)
        if where:
            for func in where.find_all(exp.Func):
                if any(isinstance(c, exp.Column) for c in func.find_all(exp.Column)):
                    issues.append(Issue(
                        severity="CRITICAL",
                        message=f"WHERE 절 함수 적용: {func.sql()} — 파티션 프루닝 불가",
                        suggestion="함수를 우변으로 이동하거나 범위 조건으로 변환하세요"
                    ))
    
        # [MAJOR] ORDER BY without LIMIT
        if ast.find(exp.Order) and not ast.find(exp.Limit):
            issues.append(Issue(
                severity="MAJOR",
                message="ORDER BY에 LIMIT 없음 — 전체 데이터 정렬 발생",
                suggestion="LIMIT 절을 추가하세요"
            ))
    
        # [MAJOR] UNION (UNION ALL 권고)
        for union in ast.find_all(exp.Union):
            if not isinstance(union, exp.UnionAll):
                issues.append(Issue(
                    severity="MAJOR",
                    message="UNION 사용 — 중복 제거 정렬 비용 발생",
                    suggestion="중복 허용 시 UNION ALL로 변경하세요"
                ))
    
        return issues
    
    # 실행 예시
    query = """
        SELECT *
        FROM orders o
        JOIN customers c ON 1=1
        WHERE YEAR(o.created_at) = 2024
        ORDER BY o.id
    """
    
    for issue in detect_antipatterns(query, dialect="trino"):
        print(f"[{issue.severity}] {issue.message}")
        print(f"  → {issue.suggestion}")
        print()
    
    [CRITICAL] SELECT * 사용 — 불필요한 전체 컬럼 조회
      → 필요한 컬럼만 명시하세요
    
    [CRITICAL] 조건 없는 JOIN 감지 — Cartesian product 발생 가능
      → ON 또는 USING 조건을 추가하세요
    
    [CRITICAL] WHERE 절 함수 적용: YEAR(o.created_at) — 파티션 프루닝 불가
      → 함수를 우변으로 이동하거나 범위 조건으로 변환하세요
    
    [MAJOR] ORDER BY에 LIMIT 없음 — 전체 데이터 정렬 발생
      → LIMIT 절을 추가하세요
    

    방언 간 SQL 변환

    import sqlglot
    
    # Snowflake → Trino 변환
    result = sqlglot.transpile(
        "SELECT DATEADD(day, 7, CURRENT_DATE()), ZEROIFNULL(revenue) FROM sales",
        read="snowflake",
        write="trino"
    )[0]
    print(result)
    # → SELECT DATE_ADD('day', 7, CURRENT_DATE), COALESCE(revenue, 0) FROM sales
    
    # Databricks → Snowflake 변환
    result = sqlglot.transpile(
        "SELECT DATE_FORMAT(order_date, 'yyyy-MM-dd') FROM orders",
        read="databricks",
        write="snowflake"
    )[0]
    print(result)
    

    구문 오류 탐지

    import sqlglot
    
    try:
        sqlglot.transpile("SELECT foo FROM (SELECT baz FROM t")
    except sqlglot.errors.ParseError as e:
        for err in e.errors:
            print(f"Line {err['line']}, Col {err['col']}: {err['description']}")
            print(f"Context: ...{err['start_context']}{err['highlight']}...")
    
    Line 1, Col 34: Expecting )
    Context: ...SELECT foo FROM (SELECT baz FROM t...
    

    Scope API (서브쿼리·CTE 정확한 분석)

    from sqlglot import parse_one, exp
    from sqlglot.optimizer.scope import build_scope
    
    query = """
    WITH cte AS (SELECT id FROM orders)
    SELECT * FROM cte JOIN cte AS cte2 ON cte.id = cte2.id
    """
    
    ast = parse_one(query, dialect="trino")
    root = build_scope(ast)
    
    # CTE 중복 참조 탐지
    for scope in root.traverse():
        cte_refs = {}
        for name, (node, source) in scope.selected_sources.items():
            if isinstance(source, exp.Subquery):
                cte_name = source.alias
                cte_refs[cte_name] = cte_refs.get(cte_name, 0) + 1
    
        for cte_name, count in cte_refs.items():
            if count >= 2:
                print(f"[MAJOR] CTE '{cte_name}' 중복 참조 ({count}회) — 반복 연산 발생")
    

    통합 안티패턴 카탈로그

    🔴 Critical — 반드시 수정 (실행 차단 권고)

    안티패턴 설명 영향

    SELECT * 전체 컬럼 조회 스캔 비용 폭증, 컬럼 추가 시 부작용
    Cartesian JOIN 조건 없는 JOIN (CROSS JOIN) 행 수 곱 증가 → OOM·무한 실행
    Non-sargable predicate 필터 컬럼에 함수 적용: WHERE YEAR(date) = 2024 파티션 프루닝·인덱스 무력화
    Correlated subquery in WHERE 행마다 서브쿼리 재실행 O(N²) 복잡도

    🟠 Major — 강하게 권고

    안티패턴 설명 영향

    ORDER BY without LIMIT 전체 정렬 후 페이지네이션 없음 전체 데이터 메모리 적재
    CTE 중복 참조 동일 CTE를 여러 번 참조 반복 연산 (DB에 따라 materialized 미보장)
    IN 서브쿼리 without DISTINCT IN (SELECT id FROM ...) 중복 비교로 성능 저하
    DISTINCT + JOIN 조합 JOIN 후 DISTINCT로 중복 제거 불필요한 해시 빌드
    중첩 서브쿼리 SELECT·FROM 절 내 서브쿼리 중첩 최적화 불가 구간 생성
    LIKE '%keyword' 앞자리 와일드카드 전체 스캔 강제
    UNION (UNION ALL 대신) 중복 제거 정렬 포함 추가 정렬 비용

    🟡 Minor — 권고

    안티패턴 설명

    HAVING (WHERE로 대체 가능) 집계 전 필터는 WHERE로 이동
    ORDER BY RAND() 전체 스캔 후 랜덤 정렬
    불필요한 DISTINCT 중복이 없는 상황에서 DISTINCT 사용
    지나치게 복잡한 단일 쿼리 CTE로 분리 권고
    OR 연산자 남용 UNION ALL 또는 IN으로 대체 가능
    REGEXP_CONTAINS (BigQuery) 단순 패턴은 LIKE로 충분

    플랫폼별 특화 안티패턴

    Trino

    안티패턴 설명 탐지 시점

    소파일 과다 (Too many splits) 파티션당 소파일 → 과도한 split 생성 EXPLAIN 분석
    Dynamic filtering 미활용 브로드캐스트 조인 없이 대형 테이블 풀스캔 EXPLAIN
    Predicate pushdown 비활성 커넥터에 필터가 내려가지 않음 EXPLAIN
    JOIN 순서 비최적 큰 테이블이 build side가 됨 EXPLAIN
    컬럼형 포맷 미사용 ORC/Parquet 대신 CSV/JSON 정적 분석

    Databricks / Spark

    안티패턴 설명 탐지 시점

    UDF 남용 Python UDF → Spark native 함수로 대체 정적 분석
    collect() 호출 드라이버에 전체 데이터 수집 → OOM 정적 분석
    explode() 후 집계 없는 JOIN 행 폭발 후 바로 조인 정적 분석
    Shuffle partition 미조정 기본 200 파티션 그대로 사용 EXPLAIN EXTENDED
    autoMerge 스키마 진화 예상 못한 스키마 변경 정적 분석
    Liquid Clustering 미사용 Z-order 대신 Liquid Clustering 미전환 메타데이터 분석
    Small file 누적 OPTIMIZE/VACUUM 미실행 메타데이터 분석

    Snowflake

    안티패턴 설명 탐지 시점

    필터 컬럼에 함수 적용 WHERE DATE_TRUNC(...) → 파티션 프루닝 불가 정적 분석
    Clustering key 미사용 대형 테이블 파티션 pruning 없음 EXPLAIN / 메타데이터
    VARIANT/JSON 전체 스캔 반구조 데이터 전체 파싱 정적 분석
    Small file INSERT COPY INTO 대신 단건 INSERT 반복 정적 분석
    Warehouse 과대 설정 쿼리 복잡도 대비 X-Large 사용 실행 후 통계
    RESULT_SCAN 미활용 동일 쿼리 반복 실행 (캐시 미활용) 히스토리 분석

    구현 방향

    전체 흐름

    쿼리 입력
       ↓
    [1] 정적 분석 (AST)         → Critical/Major 즉시 차단·경고
       ↓
    [2] EXPLAIN 분석            → 실행 계획 기반 추가 경고
       ↓
    [3] 실행 (선택)
       ↓
    [4] 실행 후 프로파일 분석    → 실제 비용 기반 튜닝 포인트
    

    Pre-execution: 정적 분석 (Dry-run)

    실제 실행 없이 SQL 텍스트 → AST 파싱 → 패턴 매칭 방식으로 동작합니다.

    • 추천 도구: sqlglot (Python) — Trino·Databricks·Snowflake 방언 모두 지원, AST 변환 가능
    • 탐지 가능: SELECT *, Cartesian JOIN, Non-sargable predicate, UNION vs UNION ALL, 중첩 서브쿼리, ORDER BY without LIMIT 등

    Pre-execution: EXPLAIN 분석

    DB에 EXPLAIN을 실행하여 (데이터 스캔 없음) 실행 계획을 파싱합니다.

    플랫폼 명령어 얻을 수 있는 정보

    Trino EXPLAIN query 논리/분산 실행 계획, 예상 행 수
    Databricks EXPLAIN EXTENDED query 논리/물리 플랜, AQE 정보
    Snowflake EXPLAIN USING TABULAR query 파티션 프루닝 여부, 예상 스캔 크기
    BigQuery dry_run=True (API) 예상 바이트 스캔 비용

    EXPLAIN으로 탐지 가능한 항목: Full scan (파티션 프루닝 없음), 비효율적 JOIN 순서, 브로드캐스트 JOIN 미활용, 동적 필터 미적용입니다.

    Post-execution: 프로파일 분석

    실행 완료 후 통계 데이터를 수집하여 실제 병목을 진단합니다.

    플랫폼 데이터 소스 분석 포인트

    Trino system.runtime.queries 실제 스캔 바이트, CPU 시간, 스필 여부
    Databricks Spark UI / DESCRIBE HISTORY 스테이지별 실행 시간, 셔플 크기
    Snowflake Query Profile / QUERY_HISTORY 파티션 스캔율, Spill to disk, 원격 스필
    BigQuery INFORMATION_SCHEMA.JOBS 실제 바이트 처리, Slot ms

    우선순위 체계

    심각도 액션 예시

    🔴 Critical 실행 차단 또는 명시적 확인 요구 Cartesian JOIN, Non-sargable
    🟠 Major 경고 + 수정 제안 코드 제공 ORDER BY without LIMIT, CTE 중복 참조
    🟡 Minor 선택적 최적화 제안 UNION → UNION ALL, OR → IN

    추천 구현 스택

    • SQL 파싱: sqlglot (Python) — 다방언 지원, AST 변환, 방언 간 변환 가능
    • 안티패턴 규칙 엔진: 규칙 기반 (Rule-based) + LLM 보완
    • EXPLAIN 파싱: 플랫폼별 EXPLAIN 출력 파서 (JSON 형태 권장)
    • 레퍼런스 아키텍처: BigQuery Anti-Pattern Recognition (오픈소스) 참고

    도구 선택 가이드

    상황 추천 도구

    BigQuery 환경에서 전체 쿼리 자동 감사 BigQuery Anti-Pattern Recognition
    DB 무관한 SQL 파일 빠른 CLI 검사 sqlcheck
    Snowflake·Databricks 멀티 웨어하우스 + AI 제안 AltimateAI/altimate-code
    자체 안티패턴 엔진 개발, 다방언 지원 필요 sqlglot
    CI/CD 게이트에서 HIGH 패턴만 차단 sqlcheck (-r 3)
    방언 간 SQL 마이그레이션 sqlglot (transpile)

    참고 링크

    댓글