운영자 기록(Operator notes)은 시계열 데이터입니다. 각 기록에는 타임스탬프, 현장(site), 온도, 그리고 무슨 일이 있었는지에 대한 문장이 포함되어 있습니다. 이들을 의미로 검색하려면 보통 임베딩을 위한 두 번째 데이터베이스와 두 저장소를 동기화하는 작업이 필요합니다.
저는 임베딩을 기록과 동일한 TimescaleDB hypertable에 유지했습니다. 질문, 현장, 그리고 시간 범위만으로 하나의 SQL 문장을 작성할 수 있습니다.
코드는 github.com/tharun634/ghw-tiger-vector-search에서 확인할 수 있습니다.
테이블 구조 (The row)
ghw_vectors.operator_notes는 observed_at을 기준으로 파티셔닝된 hypertable입니다. 2026년 9월 22일부터 10월 13일까지의 기록 18건이 포함되어 있습니다. 각 행은 기록 텍스트, 현장, 라인, 심각도(severity), 온도, 습도, 그리고 fastembed를 사용하여 로컬로 임베딩된 sentence-transformers/all-MiniLM-L6-v2의 vector(384)를 가지고 있습니다.
기본 키는 (site, observed_at, note_id)입니다. hypertable에서는 모든 고유 제약 조건이 파티션 열을 포함해야 하므로, 단독 id 기본 키는 유효하지 않습니다.
컬럼스토어(Columnstore)는 비활성화되어 있습니다. 벡터 인덱스는 압축된 청크(chunk)에 위치할 수 없습니다. 일일 온도 집계는 time_bucket에 대한 일반 뷰이므로, 별도의 새로고침 단계 없이 정확하게 유지됩니다.
인덱스 (The index)
pgvector 0.8.7은 vector 타입과 코사인 연산자 <=>를 제공합니다. pgvectorscale 0.9.0은 StreamingDiskANN을 제공합니다. 이 Tiger Cloud 서비스에서는 DiskANN이 vector에 대한 코사인 연산자 클래스를 가지고 있고, halfvec에는 없습니다. 따라서 컬럼은 vector(384)입니다. 일반적으로 pgvector만 사용할 경우 halfvec와 HNSW가 기본값입니다. DiskANN은 Tiger가 이러한 종류의 벡터 검색을 위해 문서화하는 인덱스입니다. 384차원에서는 어쨌든 halfvec로 인한 메모리 절약 효과가 작습니다.
인덱스는 로드 후에 구축됩니다:
CREATE INDEX operator_notes_embedding_diskann
ON ghw_vectors.operator_notes
USING diskann (embedding vector_cosine_ops);
단일 쿼리 (One query)
질문은
SELECT observed_at, site, temperature_c,
embedding <=> $1::vector(384) AS cosine_distance,
body
...
$1은 행을 임베딩한 것과 동일한 MiniLM 모델입니다.
가장 가까운 기록은 31.4°C의 패킹 홀 냉각기 트립입니다 (코사인 거리 0.5113). 다음으로는 공기를 순환시키던 에어 핸들러가 30.2°C에서 작동을 멈췄고, 그 뒤에는 29.8°C에서의 냉각 장치 오류 기록이 있습니다. 이 문장들 중 어느 것도 “에어컨”이나 “과열”이라는 단어를 포함하고 있지는 않습니다.
사이트 필터를 적용하지 않은 동일한 질문은 12.4°C의 온실 히터 고장을 순위로 보여줍니다. 사이트(site) 조건절이 이를 제거합니다. 같은 테이블의 일별 보기에서는 가장 가까운 기록과 같은 순간인 10월 4일 창고 최고 온도 31.4°C를 보여줍니다.
플래너는 18개 행에 대해서는 DiskANN 인덱스를 사용하지 않습니다. 시간 범위를 벗어난 청크(chunk)를 버리고, btree로 site를 필터링하며, 남은 데이터를 정확하게 정렬합니다. 이 크기에서는 이것이 올바른 계획입니다. 해당 인덱스는 근사 스캔을 수행하는 것이 행을 읽는 것보다 저렴할 때 유효합니다.
임베딩이 기록(note)에 위치해야 하는 이유
별도의 벡터 테이블은 필터링된 검색마다 조인(join)을 필요로 하며, 새로운 기록이 도착할 때 동기화 경로가 필요합니다. Google의 인라인 대 별도 벡터 테이블 작성 글은 이러한 조인 오버헤드를 측정합니다. 벡터를 하이퍼테이블(hypertable)에 두면 시간 파티션이 거리 계산을 수행하기 전에 청크를 버릴 수 있게 합니다.
DevOps Daily가 하나의 코퍼스에서 전체 텍스트, pgvector, 하이브리드 검색을 측정했습니다. pgvector와 BM25는 상반된 질문에서 승리했기 때문에 전반적으로 동률입니다: 벡터가 자연어에서 더 좋았고, 키워드가 전문 용어(jargon)에서 더 좋았습니다. 벡터 지연 시간의 대부분은 질문을 벡터로 변환하는 호출에 있었으며, 인덱스 자체가 아니었습니다. 로컬에서 임베딩하고 쓰기 및 읽기에 동일한 모델을 사용하면 그 비용이 쿼리를 실행하는 장치에 유지됩니다.
⚠️ jamilxt’s note on dedicated vector databases는 주장의 나머지 절반을 담당합니다: 두 번째 저장소는 동기화 작업과 두 번째 실패 모드를 추가합니다. 이 노트 세트에서 Postgres는 전체 검색 경로였습니다.
재현하기 (Reproduce)
py -3.14 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe scripts\build_seed.py
...
sql/001_schema.sql, sql/002_seed.sql, sql/003_index.sql, 그리고 sql/004_search.sql을 적용하세요. 시드 파일과 검색 파일에는 이미 로드된 데이터에 사용되는 벡터가 포함되어 있습니다.