Dublettenprüfung
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
-- Migration p2-002: pgvector und Ablage der Item-Embeddings
|
||||
--
|
||||
-- psql "$ADMIN_DATABASE_URL" -v ON_ERROR_STOP=1 -f migrations/p2-002-embeddings.sql
|
||||
--
|
||||
-- Setzt p2-001 voraus und ein Image, das pgvector mitbringt (siehe README):
|
||||
-- docker.io/pgvector/pgvector:pg18-trixie. Ohne die Erweiterung bricht die
|
||||
-- Migration in der ersten Zeile ab und hinterlaesst nichts.
|
||||
--
|
||||
-- Die Embeddings sind Vorfilter fuer die Kandidatengenerierung in Phase 3b,
|
||||
-- kein Selbstzweck. Phase 2 clustert nicht.
|
||||
|
||||
begin;
|
||||
|
||||
create extension if not exists vector;
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- Der Vektor liegt bewusst nicht in codings.nutzlast: JSONB-Vektoren sind
|
||||
-- weder indizierbar noch platzsparend. Die Coding-Zeile der Schicht
|
||||
-- 'embedding' haelt nur die Kennung, der Vektor steht hier.
|
||||
--
|
||||
-- 1024 Dimensionen deckt beide in der Spezifikation genannten Modelle ab
|
||||
-- (multilingual-e5-large, jina-embeddings-v3). Ein Modell mit anderer
|
||||
-- Dimension braucht eine eigene Migration - das ist gewollt, denn es waere
|
||||
-- ohnehin eine neue coder_version.
|
||||
-- --------------------------------------------------------------------------
|
||||
create table if not exists item_embeddings (
|
||||
raw_item_id bigint not null references raw_items(id) on delete cascade,
|
||||
coder_version text not null,
|
||||
erzeugt_am timestamptz not null default now(),
|
||||
vektor vector(1024) not null,
|
||||
|
||||
primary key (raw_item_id, coder_version)
|
||||
);
|
||||
|
||||
comment on table item_embeddings is
|
||||
'Satz-Embedding je Item und Kodierer-Version. Vorfilter fuer Phase 3b';
|
||||
comment on column item_embeddings.coder_version is
|
||||
'Ohne Fremdschluessel auf coder_versionen, wie codings auch - die Herkunft '
|
||||
'steht dort, erzwungen wird sie nicht';
|
||||
|
||||
-- Der Fremdschluessel auf raw_items ist nicht Kosmetik: ohne ihn bleiben
|
||||
-- Vektoren als Waisen liegen, wenn ein Item aus raw_items verschwindet.
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- Kein globaler HNSW-Index.
|
||||
--
|
||||
-- Ein Index ueber alle coder_version hinweg legt Vektoren verschiedener
|
||||
-- Modelle in dieselbe Nachbarschaftsstruktur. Eine Suche muss dann nach
|
||||
-- Version filtern, und pgvector filtert bei HNSW erst *nach* dem Durchlauf
|
||||
-- der Kandidatenliste - das kostet Treffer, nicht nur Zeit.
|
||||
--
|
||||
-- Stattdessen ein partieller Index je Version, angelegt wenn die Version in
|
||||
-- Betrieb geht:
|
||||
--
|
||||
-- select p2_embedding_index('p2-2026-09-07-a3f1b2c4');
|
||||
--
|
||||
-- Der Aufbau ist speicherhungrig; bei grossen Bestaenden vorher
|
||||
-- set maintenance_work_mem = '2GB';
|
||||
-- Und die Version wieder loswerden (Invariante 4, verwerfbar):
|
||||
-- drop index if exists item_embeddings_hnsw_<version>;
|
||||
-- delete from item_embeddings where coder_version = '...';
|
||||
-- --------------------------------------------------------------------------
|
||||
create or replace function p2_embedding_index(version text)
|
||||
returns text language plpgsql as $$
|
||||
declare
|
||||
idx text;
|
||||
begin
|
||||
-- Nicht-alphanumerisches im Versionsnamen wird zu _, damit der
|
||||
-- Indexname ohne Anfuehrungszeichen auskommt.
|
||||
idx := 'item_embeddings_hnsw_' || regexp_replace(version, '[^a-zA-Z0-9]+', '_', 'g');
|
||||
if to_regclass(idx) is not null then
|
||||
return idx || ' (bestand bereits)';
|
||||
end if;
|
||||
execute format(
|
||||
'create index %I on item_embeddings using hnsw (vektor vector_cosine_ops) where coder_version = %L',
|
||||
idx, version);
|
||||
return idx;
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function p2_embedding_index is
|
||||
'Legt den HNSW-Index fuer genau eine coder_version an. Idempotent';
|
||||
|
||||
insert into schema_migrationen (name) values ('p2-002-embeddings')
|
||||
on conflict (name) do nothing;
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,91 @@
|
||||
-- Migration p2-003: Schemaobjekte der Anwendungsrolle uebereignen
|
||||
--
|
||||
-- psql "$ADMIN_DATABASE_URL" -v ON_ERROR_STOP=1 -f migrations/p2-003-eigentuemer.sql
|
||||
--
|
||||
-- Die Datenbank wurzelwerk gehoert der Rolle wurzelwerk, ihre Tabellen aber
|
||||
-- postgres: schema.sql wurde beim Ingest-Deploy als Superuser eingespielt.
|
||||
-- Folge ist, dass die Anwendungsrolle an ihrem eigenen Schema nichts aendern
|
||||
-- darf - jedes `alter table` scheitert mit "must be owner of table".
|
||||
--
|
||||
-- Die uebrigen Dienste auf derselben Instanz halten es anders herum: gitea
|
||||
-- und hedgedoc besitzen ihre Tabellen vollstaendig. Diese Migration stellt
|
||||
-- denselben Zustand her. Der Radius bleibt auf die Datenbank wurzelwerk
|
||||
-- beschraenkt; an gitea und hedgedoc wird nicht geruehrt.
|
||||
--
|
||||
-- Danach laufen Migrationen ohne Superuser, also ueber DATABASE_URL statt
|
||||
-- ueber den Socket im Container. Ausnahme bleibt `create extension` in
|
||||
-- p2-002, sofern die Erweiterung nicht als trusted gefuehrt wird.
|
||||
--
|
||||
-- Diese Migration ist die einzige, die zwingend als Superuser laeuft - sie
|
||||
-- verschenkt Rechte, die sie selbst nicht mehr braeuchte.
|
||||
|
||||
begin;
|
||||
|
||||
do $$
|
||||
declare
|
||||
ziel constant name := 'wurzelwerk';
|
||||
o record;
|
||||
n integer := 0;
|
||||
begin
|
||||
if not exists (select 1 from pg_roles where rolname = ziel) then
|
||||
raise exception 'Rolle % existiert nicht', ziel;
|
||||
end if;
|
||||
|
||||
-- Tabellen, Sichten und freistehende Sequenzen.
|
||||
--
|
||||
-- Ausgenommen sind zwei Gruppen. Erstens Objekte einer Erweiterung
|
||||
-- (pg_trgm, vector): die gehoeren dem Superuser und werden mit der
|
||||
-- Erweiterung verwaltet, nicht von Hand. Zweitens Sequenzen, die an
|
||||
-- einer Spalte haengen - alles aus bigserial. Die folgen dem Eigentuemer
|
||||
-- ihrer Tabelle von selbst und lassen sich nicht einzeln uebereignen
|
||||
-- ("is linked to table"). Deshalb erst die Tabellen, dann der Rest.
|
||||
for o in
|
||||
select c.oid::regclass::text as name,
|
||||
case c.relkind when 'S' then 'sequence'
|
||||
when 'v' then 'view'
|
||||
when 'm' then 'materialized view'
|
||||
else 'table' end as art
|
||||
from pg_class c join pg_namespace n on n.oid = c.relnamespace
|
||||
where n.nspname = 'public'
|
||||
and c.relkind in ('r', 'p', 'S', 'v', 'm')
|
||||
and pg_get_userbyid(c.relowner) <> ziel
|
||||
and not exists (select 1 from pg_depend d
|
||||
where d.classid = 'pg_class'::regclass
|
||||
and d.objid = c.oid and d.deptype = 'e')
|
||||
and not exists (select 1 from pg_depend d
|
||||
where d.classid = 'pg_class'::regclass
|
||||
and d.objid = c.oid and d.deptype = 'a'
|
||||
and d.refclassid = 'pg_class'::regclass)
|
||||
order by case c.relkind when 'r' then 0 when 'p' then 0
|
||||
when 'v' then 1 when 'm' then 1 else 2 end
|
||||
loop
|
||||
execute format('alter %s %s owner to %I', o.art, o.name, ziel);
|
||||
n := n + 1;
|
||||
end loop;
|
||||
|
||||
-- Funktionen ebenso. Ohne sie duerfte die Rolle ihre eigenen Helfer
|
||||
-- spaeter nicht mehr ersetzen.
|
||||
for o in
|
||||
select p.oid::regprocedure::text as name
|
||||
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
||||
where n.nspname = 'public'
|
||||
and pg_get_userbyid(p.proowner) <> ziel
|
||||
and not exists (select 1 from pg_depend d
|
||||
where d.classid = 'pg_proc'::regclass
|
||||
and d.objid = p.oid and d.deptype = 'e')
|
||||
loop
|
||||
execute format('alter function %s owner to %I', o.name, ziel);
|
||||
n := n + 1;
|
||||
end loop;
|
||||
|
||||
raise notice 'uebereignet: % Objekte an %', n, ziel;
|
||||
end $$;
|
||||
|
||||
-- Neue Objekte gehoeren ohnehin dem, der sie anlegt; das Recht dazu im
|
||||
-- Schema public ist seit PostgreSQL 15 nicht mehr selbstverstaendlich.
|
||||
grant create, usage on schema public to wurzelwerk;
|
||||
|
||||
insert into schema_migrationen (name) values ('p2-003-eigentuemer')
|
||||
on conflict (name) do nothing;
|
||||
|
||||
commit;
|
||||
Reference in New Issue
Block a user