fly postgres に pgvector を入れてセマンティックサーチ: まだできず
flyio で postgres を中心に apps の Scale-To-Zero ができたので、今度はこの postgres に pgvector を入れて、text-embedding-ada-002 でベクトル化した文書を格納して、SQL でセマンティックサーチをできるようにしたい。
基本的にこの投稿のとおりにすればいいはず。
Adding pgvector to Fly Postgres
flyio 公式の Docker イメージ flyio/postgres-flex をベースに、pgvector 拡張をビルドして入れるだけ。
flyio の dockerhubをみると flyio/postgres-flex/15.3 が最新みたい。 flyio/postgres-flex-timescaledb も気になるけど、まずはシンプルに普通のをいれたい。
Dockerfile。元記事からコピペしてバージョン番号最新にしただけ。
FROM flyio/postgres-flex:15.3
# Install build dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
curl \
postgresql-server-dev-all
# Set the pgvector version
ARG PGVECTOR_VERSION=0.4.4
# Download and extract the pgvector release, build the extension, and install it
RUN curl -L -o pgvector.tar.gz "https://github.com/ankane/pgvector/archive/refs/tags/v${PGVECTOR_VERSION}.tar.gz" && \
tar -xzf pgvector.tar.gz && \
cd "pgvector-${PGVECTOR_VERSION}" && \
make && \
make install
# Clean up build dependencies and temporary files
RUN apt-get remove -y build-essential curl postgresql-server-dev-all && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /pgvector.tar.gz /pgvector-${PGVECTOR_VERSION}
イメージビルド
$ docker build . -t coji/fly-pg-pgvector --platform "linux/amd64"
ビルド成功。
dockerhub に push
$ docker push coji/fly-pg-pgvector
push 長いので待ってる間に作成済みの postgres インスタンスのイメージ更新方法をしらべる
一応標準ではこの記事があって
fly image update -a <postgres-app-name>
で最新バージョンにアップデートしてくれるっぽいことが書いてある。けど今回は独自のイメージに差し替えたいのじゃよ
fly image のリファレンス によると --image string で独自のイメージ名指定できるみたい。
flyctl image update [flags]
-a, --app string Application name
-c, --config string Path to application configuration file
--detach Return immediately instead of monitoring update progress. (Nomad only)
-h, --help help for update
--image string Target a specific image. (Machines only)
--skip-health-checks Skip waiting for health checks inbetween VM updates. (Machines only)
--strategy string Deployment strategy. (Nomad only)
-y, --yes Accept all confirmations
push 完了
こうかな?
$ fly image update -a coji-db --image coji/fly-pg-postgres
お、いけそう感。
$ fly image update -a coji-db --image coji/fly-pg-postgres
The following changes will be applied to all Postgres machines.
Machines not running the official Postgres image will be skipped.
... // 85 identical lines
}
},
- "image": "flyio/postgres-flex:15.3@sha256:c380a6108f9f49609d64e5e83a3117397ca3b5c3202d0bf0996883ec3dbb80c8",
+ "image": "coji/fly-pg-postgres",
"restart": {
"policy": "on-failure",
... // 8 identical lines
? Apply changes? (y/N)
おっと
? Apply changes? Yes
Identifying cluster role(s)
Machine 17811616a52548: primary
Updating machine 17811616a52548
Error: could not update machine 17811616a52548: failed to update VM 17811616a52548: Authentication required to access image "docker.io/coji/fly-pg-postgres:latest"
普通にイメージ名まちがえてただけでした。
✗ $ fly image update -a coji-db --image coji/fly-pg-postgres
◯ $ fly image update -a coji-db --image coji/fly-pg-pgvector
Identifying cluster role(s)
Machine 17811616a52548: primary
Updating machine 17811616a52548
Waiting for 17811616a52548 to become healthy (started, 3/3)
Machine 17811616a52548 updated successfully!
Postgres cluster has been successfully updated!
TablePlus で接続して試してみる。
まず proxy でポートフォワード開けておいて
$ fly proxy 5432:5432 -a <postgres-app-name>
TablePlus で接続します。
fly pg attach でアタッチしたときにできた DATABASE_URL のユーザ名、パスワード、データベースを使って localhost:5432 に接続。
pgvector の Getting Startedをそのままやる
拡張を有効に。
CREATE EXTENSION vector;
Query 1 OK: CREATE EXTENSION
ベクトル付きのテーブル作成
CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));
Query 1 OK: CREATE TABLE
ベクトルのレコードを2件INSERT
INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');
Query 1 OK: INSERT 0 2, 2 rows affected
近い順に検索
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
id embedding
1 [1,2,3]
2 [4,5,6]
うごいたね。
さて、これを prisma 経由で使いたい。queryRaw でいけるかな?
これでまず select しようとすると
import { Box } from '@chakra-ui/react'
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { prisma } from '~/services/database.server'
export const loader = async () => {
const list = await prisma.$queryRaw`SELECT * FROM items`
return json({ list })
}
export default function VectorTestPage() {
const { list } = useLoaderData<typeof loader>()
return <Box>{JSON.stringify(list)}</Box>
}
エラー
Error:
Invalid `prisma.$queryRaw()` invocation:
Raw query failed. Code: `N/A`. Message: `Failed to deserialize column of type 'vector'. If you're using $queryRaw and this column is explicitly marked as `Unsupported` in your Prisma schema, try casting this column to any supported Prisma type such as `String`.`
at An.handleRequestError (/Users/coji/progs/nickname-gpt/node_modules/.pnpm/@prisma+client@4.15.0_prisma@4.15.0/node_modules/@prisma/client/runtime/library.js:174:6929)
at An.handleAndLogRequestError (/Users/coji/progs/nickname-gpt/node_modules/.pnpm/@prisma+client@4.15.0_prisma@4.15.0/node_modules/@prisma/client/runtime/library.js:174:6358)
at An.request (/Users/coji/progs/nickname-gpt/node_modules/.pnpm/@prisma+client@4.15.0_prisma@4.15.0/node_modules/@prisma/client/runtime/library.js:174:6237)
at loader5 (/Users/coji/progs/nickname-gpt/app/routes/vector-test.tsx:7:16)
at Object.callRouteLoaderRR (/Users/coji/progs/nickname-gpt/node_modules/.pnpm/@remix-run+server-runtime@1.17.0/node_modules/@remix-run/server-runtime/dist/data.js:52:16)
at callLoaderOrAction (/Users/coji/progs/nickname-gpt/node_modules/.pnpm/@remix-run+router@1.6.3/node_modules/@remix-run/router/router.ts:3568:16)
at async Promise.all (index 1)
at loadRouteData (/Users/coji/progs/nickname-gpt/node_modules/.pnpm/@remix-run+router@1.6.3/node_modules/@remix-run/router/router.ts:3001:19)
at queryImpl (/Users/coji/progs/nickname-gpt/node_modules/.pnpm/@remix-run+router@1.6.3/node_modules/@remix-run/router/router.ts:2780:20)
at Object.query (/Users/coji/progs/nickname-gpt/node_modules/.pnpm/@remix-run+router@1.6.3/node_modules/@remix-run/router/router.ts:2656:18)
prisma の github issues に pgvector サポートのスレがあったのだけど、良き方法が。
マイグレーション、これでいいみたい。
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
datasource db {
provider = "postgres"
url = env("DATABASE_URL")
extensions = [pgvector(map: "vector", schema: "public")]
}
model items {
id Int @id @default(autoincrement())
embedding Unsupported("vector")
}
これで prisma migrate dev で作られた SQL の先頭にこれ CREATE EXTENSION 文を入れると prisma migrate deploy したときに自動的に有効になって、テーブル作成できるよと。
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "vector" WITH SCHEMA "public";
-- CreateTable
CREATE TABLE "items" (
"id" SERIAL NOT NULL,
"embedding" vector NOT NULL,
CONSTRAINT "items_pkey" PRIMARY KEY ("id")
);
というわけで queryRaw で cast してやってみる。
import { Box } from '@chakra-ui/react'
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { prisma } from '~/services/database.server'
export const loader = async () => {
const list = await prisma.$queryRaw`
SELECT
id,
cast(embedding as text)
FROM
items
ORDER BY
embedding <-> '[3,1,2]'
`
return json({ list })
}
export default function VectorTestPage() {
const { list } = useLoaderData<typeof loader>()
return (
<Box>
<pre>{JSON.stringify(list, null, 2)}</pre>
</Box>
)
}
実行結果
[
{
"id": 1,
"embedding": "[1,2,3]"
},
{
"id": 2,
"embedding": "[4,5,6]"
}
]
yay!
pgvector の nodejs サンプルに prisma での使い方載ってた。
Import the library
import pgvector from 'pgvector/utils';
Add the extension to the schema
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
extensions = [vector]
}
Add a vector column to the schema
model Item {
id Int @id @default(autoincrement())
embedding Unsupported("vector(3)")?
}
Insert a vector
const embedding = pgvector.toSql([1, 1, 1])
await prisma.$executeRaw`INSERT INTO items (embedding) VALUES (${embedding}::vector)`
Get the nearest neighbors to a vector
const embedding = pgvector.toSql([1, 1, 1])
const items = await prisma.$queryRaw`SELECT id, embedding::text FROM items ORDER BY embedding <-> ${embedding}::vector LIMIT 5`
見慣れない ${embedding}::vector
とか、 embedding::text
とかの ::
は postgres 独自の型キャスト演算子のようで CAST(embedding as text)
とかにするのと同じみたい。つらい。
order by のこれ、なんなんだろう。距離っぽいけど、pgvector で定義されてるのかなあ。
ORDER BY embedding <-> ${embedding}::vector
pgvector の README に下記記載があるから、やっぱり <->
は pgvector 定義の演算子っぽい
Vector Operators
Operator | Description |
---|---|
+ | element-wise addition |
- | element-wise subtraction |
<-> | Euclidean distance |
<#> | negative inner product |
<=> | cosine distance |
Vector Functions
Function | Description |
---|---|
cosine_distance(vector, vector) → double precision | cosine distance |
inner_product(vector, vector) → double precision | inner product |
l2_distance(vector, vector) → double precision | Euclidean distance |
vector_dims(vector) → integer | number of dimensions |
vector_norm(vector) → double precision | Euclidean norm |
Aggregate Functions
Function | Description |
---|---|
avg(vector) → vector | arithmetic mean |
デプロイしたけど、text-embedding-ada-002 で 1536 次元にしたやつを SELECT しようとすると、flyio でのみうまく動かない〜
7次元までは大丈夫だけど、8次元以上だと select 時にプロセスが落ちちゃう。どういうことなの。。
postgres-flex の中身見てみるといろんな処理がうごいてて、レプリケーションとかもあるから、そのあたりでなにか踏んでいるのかなあ。残念
dmesg で以下のログが出てた。 invalid opcode ということで。ぜんぜんわかんない
[203708.729520] traps: postgres[21870] trap invalid opcode ip:7f7a9cc316f2 sp:7ffe482c95c0 error:0 in vector.so[7f7a9cc2b000+8000]