본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 25. 15:01

HIPAA 규정 때문에 ChatGPT나 Claude에 실행 계획을 붙여넣을 수 없어서 로컬 Postgres 트리아지(Triage) 코파일럿을

요약

HIPAA 규정으로 인해 클라우드 LLM을 사용할 수 없는 에어갭 환경을 위해, 로컬에서 실행되는 Postgres 트리아지 도구인 Plansmith를 개발했습니다. Gemma 4를 활용하여 실행 계획의 차이점을 분석하고 인간이 이해할 수 있는 조치 런북을 생성합니다.

핵심 포인트

  • HIPAA 규정 준수를 위한 완전 로컬 실행 파이프라인 구축
  • Gemma 4를 활용한 실행 계획 분석 및 런북 생성
  • Python을 통한 결정론적 차이점 추출로 모델 부하 감소
  • Ollama를 이용한 로컬 LLM 추론 환경 구현

이 게시물은 Gemma 4 Challenge: Build with Gemma 4를 위한 제출물입니다.

내가 만든 것

저는 대형 공공 의료 기업의 Staff DBA(데이터베이스 관리자)입니다. 저는 MS SQL Server와 PostgreSQL 플릿(Fleet)을 관리하며, 우리의 모든 데이터베이스 서버는 온프레미스(On-prem) 환경이며 에어갭(Air-gapped) 상태로 운영됩니다. HIPAA 규정 때문에 운영 업무에 프론티어 LLM(Frontier LLM)을 사용할 수 있는 옵션이 없습니다. 쿼리 실행 계획(Query plans), 테이블 이름, 필터 리터럴(Filter literals) 등 그 어떤 것도 우리 네트워크를 벗어날 수 없습니다.

즉, 새벽 3시에 Postgres 인스턴스 중 하나가 오작동하기 시작하더라도, 저는 EXPLAIN 실행 계획을 ChatGPT나 Claude 같은 프론티어 모델에 붙여넣고 무엇이 변했는지 물어볼 수 없습니다. 저는 2015년에 했던 방식 그대로 JSON을 수동으로 읽습니다. 제 동료들 대부분도 마찬가지입니다.

그래서 저는 저 자신을 위해 Plansmith를 만들었습니다. 쿼리가 정상이었을 때의 베이스라인(Baseline)과 현재의 장애 발생 시점의 실행 계획, 이렇게 두 개의 EXPLAIN (ANALYZE, FORMAT JSON) 출력값을 입력하면, Plansmith는 평이한 영어로 된 트리아지(Triage) 런북(Runbook)을 제공합니다. 즉, 실행 계획에서 무엇이 변했는지, 순위가 매겨진 근본 원인 가설, 향후 5분 내에 시도해 볼 수 있는 조치 사항, 그리고 영구적인 해결책을 알려줍니다. 모델을 포함한 전체 파이프라인은 로컬에서 실행됩니다. Plansmith가 수행하는 유일한 외부 네트워크 호출은 동일한 노트북에 있는 Ollama인 127.0.0.1:11434로의 호출뿐입니다.

이 작업을 소규모 로컬 모델로 가능하게 만든 핵심은 Gemma 4에게 원본 실행 계획 JSON을 읽으라고 요청하지 않는다는 점입니다. 결정론적인(Deterministic) Python 패스(Pass)가 먼저 두 실행 계획 트리를 모두 탐색하여 구조화된 차이점(Diff)을 추출합니다. 예를 들어, 스캔 방식이 Index Scan에서 Seq Scan으로 변경됨, 플래너 행 추정치(Planner row estimate)가 124,000배 차이 남, 정렬(Sort)이 디스크로 스필(Spilled)됨, 해시 조인(Hash join)에 8개의 배치가 필요함, 이전의 병렬 워커(Parallel workers)를 상실함 등의 정보입니다. 이미 수치가 측정된 이 압축된 명명된 결과 세트만이 모델로 전달됩니다. Gemma의 역할은 이미 추출된 증거를 바탕으로 인간이 이해할 수 있는 설명과 순위가 매겨진 런북을 생성하는 것, 즉 모델이 실제로 잘하는 부분에 집중하는 것입니다.

데모

레포지토리에 포함된 실제 샘플(고객 이력 쿼리에서의 파라미터 스니핑(Parameter-sniffing) 회귀 현상)을 실행한 CLI 결과입니다:

$ plansmith analyze \
    --baseline samples/baseline_q1.json \
    --incident samples/incident_q1.json \
...

그러면 Gemma 4가 런북(Runbook)을 스트리밍하여 가져옵니다. 다음은 제 노트북에서 로컬로 실행한 결과를 편집 없이 그대로 옮긴 것입니다:

요약 (TL;DR)

통계 정보가 오래되어(stale) 플래너(Planner)가 orders 테이블에 대해 잘못된 순차 스캔(Sequential Scan)으로 전환했습니다. 이로 인해 행 수(row)가 과도하게 과대평가되었고, 결과적으로 조인 폭발(join blowup)이 발생했습니다.

예상되는 근본 원인 (순위별)

  1. orders 테이블의 오래된 통계 정보로 인해, 플래너가 필터링된 결과 집합의 카디널리티(Cardinality)를 급격히 과소평가함. (row_estimate_miss 발견으로 뒷받침됨)
  2. WHERE 절의 컬럼(customer_id, placed_at) 또는 조인 키를 커버하는 적절한 복합 인덱스(Composite index)의 부재. (scan_method_flip 발견으로 뒷받침됨)
  3. 매우 부풀려진 행 추정치로 인해 플래너가 잘못된 중첩 루프 조인(Nested Loop join)을 선택함. (nested_loop_blowup 발견으로 뒷받침됨)

즉각적인 완화 조치 (수 분 내)

  • VACUUM ANALYZE orders;를 실행하여 해당 테이블의 통계 정보를 강제로 업데이트하십시오.
  • 인덱스가 올바른 것으로 확인된 경우, 일시적으로 플래너를 강제하십시오: SET LOCAL enable_seqscan = off;
  • 수정 사항을 배포하기 전에 EXPLAIN (ANALYZE, BUFFERS) SELECT …를 실행하여 실행 계획(Plan)의 변경을 확인하십시오.

영구적인 해결책 (수 일 내)

  • 필터 컬럼을 커버하는 orders 테이블의 복합 인덱스를 생성하십시오: CREATE INDEX idx_orders_customer_time ON orders (customer_id, placed_at);
  • 재발을 방지하기 위해 orders.customer_idorders.placed_at에 대한 default_statistics_target을 높이십시오.
  • 고객 기반이 심하게 왜곡(skewed)되어 있다면, customer_id를 기준으로 orders 테이블을 파티셔닝(Partitioning)하는 것을 고려하십시오.

이것은 제가 직접 작성했다면 아마 20분이 아닌 2분 만에 끝냈을 내용과 거의 비슷합니다. 레포지토리의 두 번째 샘플은 다른 형태의 버그로, 분석 롤업(Analytics rollup)에서의 통계 드리프트(Statistics drift)로 인해 정렬 스필(Sort spill) 및 해시 스필(Hash spill)이 발생하며 결과적으로 92배 느려지는 현상인데, 그에 대한 런북 역시 매우 훌륭합니다.

또한 두 개의 텍스트 영역(textarea), 버튼 하나, 그리고 스트리밍되는 Markdown 패널을 제공하는 작은 Flask 웹 UI인 plansmith serve가 있습니다. 이는 동일한 코드 경로를 사용하지만, Server-Sent Events (SSE)를 통해 작동하므로 Gemma가 생성하는 대로 보고서가 나타납니다. 동료 옆에 앉아 모델이 제안하는 내용을 함께 논의하고 싶을 때 유용합니다.

코드 (Code)

GitHub: github.com/sireesha-chavvakula/plansmith, Apache-2.0.

plansmith/
├── plansmith/
│   ├── plan_diff.py     # 결정론적(deterministic) EXPLAIN JSON diff
...

약 640줄의 Python 코드와 240줄의 HTML 및 CSS로 구성되어 있습니다. 의존성(dependencies)은 flask, requests, rich 세 가지뿐입니다. 외부 서비스는 사용하지 않습니다.

plan_diff.py가 현재 감지하는 실행 계획 퇴보(plan regression) 카테고리는 다음과 같습니다:

발견 사항 (Finding)트리거 조건 (What triggers it)
runtime_regression최상위 실행 시간(top-line execution time)이 2배 이상 증가함
...

이것들은 제가 실제로 운영 환경에서 트리아지(triage)했던 실행 계획 형태 퇴보(plan-shape regressions)의 대다수를 차지하는 패턴들입니다. 목록을 의도적으로 짧게 유지했습니다. 노이즈에 반응하는 긴 규칙 목록을 갖기보다는, 흔한 버그들을 깔끔하게 잡아내는 쪽을 택했습니다.

Gemma 4 사용 방법

왜 E4B인가

세 가지 Gemma 4 변체(variants)가 있으며, 심사위원들은 제가 왜 특정 모델을 선택했는지 알고 싶어 할 것입니다. Plansmith의 경우 그 답은 간단합니다.

31B Dense 모델은 약간 더 세련된 산문을 작성할 것입니다. 하지만 제가 당직(on call) 중일 때 옆에 놓여 있는 노트북에서는 실행할 수 없으며, 에어갭(air-gapped) DB 네트워크에 접속하기 위해 반드시 거쳐야 하는 배스천 호스트(bastion host)에서도 절대 실행할 수 없습니다. 26B MoE 모델은 본질적으로 "이 8가지 조사 결과(findings)를 증거로 사용하여 6개 섹션의 템플릿을 채우는" 작업에 비해 과합니다. Q4_K_M 양자화가 적용된 E4B 모델은 디스크 용량이 약 8GB이며, 노트북 RAM에 적합하고, 런북(runbook)을 응답성이 느껴질 만큼 충분히 빠르게 스트리밍하며, 128K 컨텍스트 윈도우(context window) 덕분에 파티션이 많은 광범위한 실행 계획이라도 절대로 잘라낼(truncate) 필요가 없습니다. 이는 제가 승인할 용의가 있는 출력을 생성하는 가장 작은 Gemma 4 변체(variant)이며, 에어갭 DBA 도구로서 정확히 적절한 절충안(tradeoff)입니다.

프롬프트가 구성되는 방식

Gemma는 가공되지 않은 EXPLAIN JSON을 절대 보지 않습니다. plan_diff.py에 의해 생성된 구조화된 차이점(diff)이 사용자 메시지의 전부입니다:

{
  "baseline_runtime_ms": 14.91,
  "incident_runtime_ms": 7421.41,
...

시스템 프롬프트는 두 가지 역할을 수행합니다. 모델에게 주어진 숫자를 신뢰하고 새로운 숫자를 지어내지 말라고 지시하며, 출력을 정확한 6개 섹션의 마크다운(Markdown) 골격(TL;DR, 실행 계획의 내용, 순위가 매겨진 예상 근본 원인, 즉각적인 완화 조치, 영구적인 수정 사항, 검증)으로 고정합니다.

이러한 분리는 프롬프트의 문구 자체보다 더 중요합니다. 결정론적 패스(deterministic pass)가 산술 연산과 패턴 인식을 수행합니다. Gemma는 판단, 순위 매기기, 그리고 산문을 담당합니다. 4B 모델은 두 번째 작업을 수행하기에 충분하며, 계산을 요청받은 적이 없기 때문에 행 수(row counts)를 환각(hallucinate)할 수도 없습니다.

Plansmith의 향후 계획

제 개인적인 로드맵에 있는 두 가지 사항은 다음과 같습니다:

  1. SQL Server 지원. 제 업무 시간의 절반은 MSSQL에서 보냅니다. XML 실행 계획(showplan) 형식은 Postgres JSON보다 더 장황하지만, 구조적 차이(structural diff)를 구하는 개념은 동일하며 단지 파서(parser)만 다를 뿐입니다. Gemma 프롬프트는 변경할 필요가 없습니다.
  2. 멀티 쿼리 모드 (Multi-query mode). pg_stat_statements 실행 계획 캡처 파일들이 담긴 디렉토리를 입력하면, 성능 저하 심각도(regression severity)에 따라 쿼리 순위를 매기고 쿼리당 하나의 런북(runbook)을 생성합니다. 이것이 실제 온콜(on-call) 워크플로입니다.

이 글을 읽는 분 중 규제 환경(regulated environment)에서 근무하며 로컬 전용 DBA 툴링에 대해 의견을 나누고 싶은 분이 있다면, 꼭 연락 부탁드립니다. "클라우드 LLM을 사용할 수 없다"라고 말하는 실무자 그룹은 보기보다 규모가 크며, 우리 모두는 병렬적으로 동일한 종류의 도구들을 구축하고 있습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0