미해결된 Prisma 버그에 Pinaka를 실행해 보았습니다. 정답(Ground Truth)이 없는 상황에서 무엇을 찾아냈을까요?
요약
Prisma의 특정 필드에서 발생하는 null 필터링 버그를 Pinaka를 통해 진단한 사례를 다룹니다. 정답(Ground Truth)이 없는 상황에서 런타임 로그와 스택 트레이스를 활용해 근본 원인을 분석하는 과정을 설명합니다.
핵심 포인트
- Prisma의 String? 필드에서 null 필터링 시 발생하는 유효성 검사 에러 분석
- 정답이 없는 버그 상황에서 런타임 컨텍스트와 로그를 활용한 검증 방식
- Pinaka의 벡터 검색 및 리랭커를 활용한 빠른 근본 원인 분석(RCA) 과정
지난주에 발표한 BullMQ 벤치마크는 깔끔한 채점 메커니즘을 갖추고 있었습니다. 실제 병합된 PR(Pull Request), 버그를 확인해 주는 실제 유지 관리자의 코멘트, 그리고 비교할 수 있는 알려진 정확한 수정 사항(fix)이 있었죠. 하지만 이번 사례는 그렇지 않습니다.
Prisma 이슈 #29480은 현재 열려 있는 상태입니다. 이 이슈는 우리가 이 벤치마크를 실행한 시점까지 7.5.0, 7.6.0, 7.7.0, 7.8.0 등 네 번의 마이너 릴리스(minor release)를 거치며 해결되지 않은 채 지속되었습니다. 채점할 수 있는 병합된 수정 사항이 없습니다. 따라서 우리는 Pinaka의 진단을 기존과는 다른 방식으로 검증해야 했습니다. 즉, 알려진 정답이 아니라 코드베이스 자체의 동작 증거(behavioral evidence)를 바탕으로 검증해야 했습니다. 이는 다른 종류의 벤치마크이며, Pinaka가 무엇을 증명하고 무엇을 증명하지 못하는지에 대해 정확히 짚고 넘어갈 가치가 있습니다.
버그 (The bug)
PrismaPg 드라이버 어댑터(driver adapter)를 사용할 때, null 허용 가능한 String? 필드에 대해 null 값을 필터링하면 PrismaClientValidationError가 발생합니다:
// 다음은 모두 에러를 발생시킵니다:
prisma.user.findMany({ where: { name: null } })
prisma.user.findMany({ where: { name: { equals: null } } })
...
에러 내용:
PrismaClientValidationError:
Invalid `prisma.user.findMany()` invocation:
{
...
이 버그는 재현 가능하고 일관적이며, 특정 타입에 국한됩니다. 동일한 null 필터 구문을 사용하는 DateTime?나 정수(integer) 필드에서는 발생하지 않고, 오직 String? 필드에서만 발생합니다. 이 문제는 네 번의 Prisma 마이너 릴리스 동안 해결되지 않은 채 열려 있었습니다.
Pinaka가 활용한 데이터
이번 실행에는 라이브 SDK 런타임 컨텍스트(runtime context)가 포함되었습니다. 즉, @pinaka/sdk를 통해 충돌(crash) 시점에 캡처된 실제 로그, 실제 스택 트레이스(stack trace), 실제 요청 컨텍스트가 포함되었습니다. 로그에는 중요한 정보가 포함되어 있었습니다:
[DEBUG] Executing prisma.user.findMany with where: { name: null }
[ERROR] PrismaClientValidationError thrown
[DEBUG] Retried with { name: { equals: null } } - same error
...
마지막 두 줄, 즉 재시도 시도와 DateTime 대조는 이번 진단을 가능하게 만든 핵심 요소였습니다. 이 정보가 없었다면, 정적 코드(static-code)만으로 수행한 근본 원인 분석 (RCA)은 에러 메시지만 보고 "사용자가 잘못된 타입을 전달함" 단계에서 멈췄을 것입니다. 로그는 이미 시도된 사항들을 기록함으로써 이것이 사용자 오류가 아님을 증명했습니다.
이번 실행의 파이프라인 통계:
- 벡터 검색 (Vector search): 25개 후보
- 직접 스택 트레이스 파일 가져오기 (Direct stack trace file fetch): 3개 파일 강제 포함
- 총 리랭커 (Reranker) 후보: 45개 (벡터 25개 + 직접 가져오기 20개)
- LLM에 전달된 최종 컨텍스트: 8개 청크
- 리랭커 (Reranker): Voyage rerank-2, 폴백 (fallback) 트리거되지 않음
- 작업 시작부터 RCA까지 소요 시간: 약 3초
Pinaka가 진단한 내용
실패 유형 (Failure class): PrismaPg 어댑터의 SQL 생성 계층(layer) 내 로직 오류.
근본 원인 (Root cause): PrismaPg 어댑터가 Nullable한 String? 필드에 대한 findMany 연산을 수행할 때, WHERE "name" IS NULL 대신 WHERE "name" = NULL을 잘못 생성합니다. PostgreSQL에서 = NULL은 항상 거짓(false)으로 평가되며, IS NULL이 올바른 술어(predicate)입니다. 이는 쿼리가 데이터베이스에 도달하기도 전에 검증이 실패하는 이유를 설명해 줍니다.
Pinaka가 이를 격리한 방법: 검색된 테스트 증거에서 발견된 세 가지 사례를 대조함으로써 격리했습니다:
| 사례 | 필드 타입 | 연산 | 결과 |
|---|---|---|---|
findMany({ where: { team_id: null } }) | Integer | findMany | ✅ 작동함 |
| ... |
이 세 가지 데이터 포인트의 교집합을 통해, 이 버그가 일반적인 Null 처리 문제나 스키마 문제, 또는 DMMF 문제가 아니라, 구체적으로 findMany + String? 조합에서 발생하는 문제임을 격리해 냈습니다.
결함 소유권 (Fault ownership): 사용자 오류가 아닌 PrismaPg 어댑터의 프레임워크 버그입니다. 애플리케이션 측에서의 모든 임시 방편 (workaround)은 말 그대로 임시 방편일 뿐입니다. 올바른 수정은 상위 단계인 Prisma 측에서 이루어져야 합니다.
제거된 주요 가설:
제거된 주요 가설:
"The field isn't nullable"— 기각: null 레코드는 성공적으로 생성될 수 있으며, 오류는 DB 제약 조건이 아닌 클라이언트 측 유효성 검사 문제입니다."Prisma doesn't support null filtering at all"— 동일한 구문으로DateTime?가 작동하는 사례를 통해 기각됨."The fix lives in the client runtime"— 기각: 오류는@prisma/client/runtime/library.js에서 발생하지만, 행동적 증거는 더 근본적인 원인이 어댑터 레벨의 SQL 생성에 있음을 가리킵니다.
방향성 수정(Directional fix): findMany + String? 케이스에 대해 어댑터의 SQL 생성 경로에서 = NULL을 IS NULL로 변경해야 합니다. Pinaka는 API 구문이나 확인할 수 없는 특정 라인 레벨의 수정 사항을 발명하지 않았으며 — 과도하게 주장하지 않고 방향성만 제시했습니다.
Pinaka가 적절히 불확실했던 부분
어댑터의 실제 소스(packages/adapter-pg/)는 검색되지 않았습니다. SQL 생성 오류의 정확한 파일과 라인은 지목되지 않았습니다.
출력에서 신뢰도(Confidence)가 높다고 표시되었지만, 주의사항은 어댑터 소스가 누락되었다는 점을 올바르게 언급했습니다 — 이는 검색이 작동하는 방식에 대한 알려진 한계입니다: 티켓 프레이밍이 벡터 검색을 어댑터 내부보다는 클라이언트 런타임 파일 쪽으로 끌어당겼습니다. 어댑터 중심의 티켓이었다면 SQL 생성 코드가 직접적으로 나타났을 것입니다.
우리는 이 내용을 각주에 숨기지 않고 본문에서 이야기하고 있습니다.
SDK가 실제로 기여한 부분
SDK로 캡처된 로그 라인들이 전체 코드베이스를 읽은 것보다 더 많은 진단 작업을 수행했습니다. 특히:
엔지니어의 재시도 시도가 동일한 요청 컨텍스트에서 발생했기 때문에 자동으로 캡처된 `
정적 코드 읽기(Static code reading)는 코드가 무엇을 해야 한다고 명시하는지를 알려줍니다. 반면 런타임 컨텍스트(Runtime context)는 누군가가 세 가지 다른 방식으로 시도했을 때 세 가지 다른 결과가 발생하며 실제로 어떤 일이 일어났는지를 알려줍니다. 이 둘은 중복되는 것이 아니라 상호 보완적이며, 이번 사례에서는 런타임 증거가 결정적인 지렛대 역할을 했습니다.
이것이 증명하는 것 — 그리고 증명하지 못하는 것
BullMQ 벤치마크와 달리, 비교할 수 있는 병합된 PR(Merged PR)이 없습니다. 아직 올바른 수정 사항이 존재하지 않기 때문에 "Pinaka의 진단이 정확한 수정안과 일치한다"라고 말씀드릴 수는 없습니다.
저희가 말씀드릴 수 있는 것은 다음과 같습니다: 이 진단은 사용 가능한 모든 행동 증거(필드 유형에 따른 비대칭적 동작, 에러 메시지의 출처, 테스트 파일의 대조 사례 등)와 일치하며, 추론 과정(Reasoning chain)이 독립적으로 평가할 수 있을 만큼 충분히 투명하다는 점입니다.
여기서 더 어려운 입증 과제는 정확도를 측정하는 것이 아닙니다. Pinaka가 단순히 알려진 스택을 따라 알려진 정답으로 추적하는 것이 아니라, 테스트 파일 전반에 걸친 행동 대조 증거(Behavioral contrast evidence)로부터 추론할 수 있다는 점을 보여주는 것입니다. 이 둘은 서로 다른 능력이며, 아직 아무도 정답을 찾지 못한 상황에서는 두 번째 능력이 더 중요합니다.
만약 Prisma가 #29480에 대해 Pinaka가 진단한 내용과 다른 수정 사항을 출시한다면, 이 게시물을 업데이트하겠습니다.
솔직한 한계점
- 정답(Ground truth) 없음 — 진단은 병합된 수정안이 아닌 행동 증거를 통해서만 검증되었습니다.
- 어댑터 소스 미검색 — 정확한 결함 지점(Fault line)을 특정하지 못했습니다.
- 단일 버그, 단일 리포지토리 — 통계적 유의성을 갖추지 못했습니다.
- 티켓 프레이밍(Ticket framing)이 검색에 영향을 미침 — 어댑터에 집중된 티켓이었다면 SQL 생성 코드가 직접적으로 드러났을 가능성이 높습니다.
저희는 더 많은 리포지토리를 대상으로 이 작업을 계속 수행하고 있습니다. 이번 사례처럼 깔끔해 보이지 않는 결과들을 포함하여, 저희가 발견한 것들을 공개할 예정입니다.
체험하기
여러분의 코드베이스에서 Pinaka가 무엇을 찾아내는지 직접 확인하고 싶다면, getpinaka.com에서 대기 명단(Waitlist)에 참여하세요. 저희는 이를 공개적으로 구축(Building in public)하고 있습니다. 이러한 벤치마크는 단순히 출시 게시물에 무엇을 올릴지 결정하는 용도가 아니라, 다음에 무엇을 만들지 결정하는 기준이 됩니다.
이전 글: AI가 실제로 버그의 근본 원인(root cause)을 찾을 수 있을까요, 아니면 그저 자신감 있게 들릴 뿐일까요? (BullMQ 벤치마크)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기