-- 0.4.0: a reader is sealed with the compute it reads on, and a reader registered before that is -- replaced, never relabelled. \set VERBOSITY terse CREATE EXTENSION pg_phasevec VERSION '0.3.0'; \set m '{"reader":"vec","version":"0.1.0","base_sha256":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","adapter_sha256":"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","heads_sha256":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","marker":"","pooling":"none","read_at":"last_marker_token","mrl_dims":[1024,256,64],"query_instruction":"Query: "}' -- a reader from before 0.4.0, with readings, on a live watch SELECT phasevec.reader_register(:'m'::jsonb) > 0 AS legacy; legacy -------- t (1 row) CREATE TABLE notes (id int PRIMARY KEY, body text); INSERT INTO notes SELECT g, 'note ' || g FROM generate_series(1, 6) g; SELECT phasevec.watch('notes', 'notes'::regclass, 'id', 'r.body', 'vec-0.1.0') > 0 AS watch; watch ------- t (1 row) SELECT count(phasevec.record('notes', n.key, 'vec-0.1.0', n.text_hash, n.key::bigint, array_fill(1::smallint, ARRAY[1024]))) FROM phasevec.next('notes', 6) n; count ------- 6 (1 row) ALTER EXTENSION pg_phasevec UPDATE TO '0.4.0'; SELECT extversion FROM pg_extension WHERE extname = 'pg_phasevec'; extversion ------------ 0.4.0 (1 row) -- the legacy reader keeps its readings and has no compute: a reader process refuses it SELECT name, compute IS NULL AS unpinned FROM phasevec.readers; name | unpinned -----------+---------- vec-0.1.0 | t (1 row) SELECT phasevec.reader_compute('vec-0.1.0') IS NULL AS no_compute; no_compute ------------ t (1 row) SELECT phasevec.reader_compute_matches('vec-0.1.0', '{"backend":"cpu","device":"x","libs":"0000000000000000000000000000000000000000000000000000000000000000"}') AS legacy_matches; legacy_matches ---------------- f (1 row) \set cpu '{"backend":"cpu","device":"Some CPU","system_info":"CPU : AVX2 = 1","libs":"1111111111111111111111111111111111111111111111111111111111111111"}' \set cuda '{"backend":"cuda","device":"Some GPU","system_info":"CUDA : ARCHS = 1200","libs":"2222222222222222222222222222222222222222222222222222222222222222"}' -- a compute signature must name a backend we pin, a device and the libraries loaded SELECT phasevec.reader_register(:'m'::jsonb, '{"backend":"hybrid","device":"x","libs":"1111111111111111111111111111111111111111111111111111111111111111"}'); ERROR: phasevec: a compute signature names its backend, cpu or cuda SELECT phasevec.reader_register(:'m'::jsonb, '{"backend":"cpu","libs":"1111111111111111111111111111111111111111111111111111111111111111"}'); ERROR: phasevec: a compute signature names its device and the sha256 of the libraries it loaded SELECT phasevec.reader_register(:'m'::jsonb, '{"backend":"cpu","device":"x","libs":"not a sha"}'); ERROR: phasevec: a compute signature names its device and the sha256 of the libraries it loaded SELECT phasevec.reader_register(:'m'::jsonb, '"cpu"'); ERROR: phasevec: a compute signature names its backend, cpu or cuda -- the manifest is still checked when a compute comes with it SELECT phasevec.reader_register(jsonb_set(:'m'::jsonb, '{base_sha256}', 'null'), :'cpu'::jsonb); ERROR: phasevec: base_sha256 is not sealed -- control: the same weights on two backends are two readers, next to the legacy one SELECT phasevec.reader_register(:'m'::jsonb, :'cpu'::jsonb) > 0 AS on_cpu; on_cpu -------- t (1 row) SELECT phasevec.reader_register(:'m'::jsonb, :'cuda'::jsonb) > 0 AS on_cuda; on_cuda --------- t (1 row) SELECT name, compute->>'backend' AS backend FROM phasevec.readers ORDER BY id; name | backend ----------------+--------- vec-0.1.0 | vec-0.1.0@cpu | cpu vec-0.1.0@cuda | cuda (3 rows) -- the same weights on the same compute cannot be registered twice SELECT phasevec.reader_register(jsonb_set(:'m'::jsonb, '{version}', '"0.1.1"'), :'cpu'::jsonb); ERROR: duplicate key value violates unique constraint "readers_seal_key" -- and the constraint holds without the function: a bad signature cannot be written in directly UPDATE phasevec.readers SET compute = '{"backend":"hybrid"}' WHERE name = 'vec-0.1.0@cpu'; ERROR: new row for relation "readers" violates check constraint "readers_compute_check" -- a process proves its compute: the same signature in another key order matches, another does not SELECT phasevec.reader_compute_matches('vec-0.1.0@cpu', '{"libs":"1111111111111111111111111111111111111111111111111111111111111111","system_info":"CPU : AVX2 = 1","device":"Some CPU","backend":"cpu"}') AS same; same ------ t (1 row) SELECT phasevec.reader_compute_matches('vec-0.1.0@cpu', :'cuda'::jsonb) AS cuda_on_cpu_seal; cuda_on_cpu_seal ------------------ f (1 row) SELECT phasevec.reader_compute_matches('vec-0.1.0@cuda', :'cpu'::jsonb) AS cpu_on_cuda_seal; cpu_on_cuda_seal ------------------ f (1 row) SELECT phasevec.reader_compute_matches('vec-0.1.0@cpu', jsonb_set(:'cpu'::jsonb, '{system_info}', '"CPU : AVX512 = 1"')) AS other_kernels; other_kernels --------------- f (1 row) SELECT phasevec.reader_compute_matches('nobody', :'cpu'::jsonb) AS no_reader; no_reader ----------- (1 row) -- the migration: switching the watch to the pinned reader makes every legacy reading foreign SELECT phasevec.reader_switch('notes', 'vec-0.1.0@cpu'); reader_switch --------------- (1 row) SELECT state, n FROM phasevec.coverage('notes') WHERE n > 0; state | n ---------+--- pending | 6 foreign | 6 (2 rows) SELECT count(*) AS to_read FROM phasevec.next('notes', 100); to_read --------- 6 (1 row) -- a legacy reading is refused now that the watch belongs to the pinned reader SELECT phasevec.record('notes', '1', 'vec-0.1.0', (SELECT text_hash FROM phasevec.sources WHERE key = '1'), 1, array_fill(1::smallint, ARRAY[1024])); ERROR: phasevec: watch notes is read by reader #2, not by vec-0.1.0 -- the pinned reader reads them again; they come back fresh, and the counts agree with a recount SELECT count(phasevec.record('notes', n.key, 'vec-0.1.0@cpu', n.text_hash, n.key::bigint, array_fill(2::smallint, ARRAY[1024]))) FROM phasevec.next('notes', 100) n; count ------- 6 (1 row) SELECT state, n FROM phasevec.coverage('notes') WHERE n > 0; state | n ---------+--- fresh | 6 foreign | 6 (2 rows) SELECT count(*) AS mismatches FROM phasevec.coverage_check('notes'); mismatches ------------ 0 (1 row) DROP TABLE notes; DROP EXTENSION pg_phasevec;