From 080795c4dc70158da8db97f4e86eecf371d4eb6e Mon Sep 17 00:00:00 2001 From: Dierk Date: Tue, 19 May 2026 11:52:56 +0200 Subject: [PATCH] Update README to reflect current implementation - PostgreSQL now runs in Docker (pgvector/pgvector:pg18) on port 5433 - pgvector version updated to 0.8.2, PostgreSQL to version 18 - Project structure updated with start.sh, stop.sh, and indb/ subfolder - Running section now documents start.sh / stop.sh - Frontend URLs updated to /ui/ endpoint - Photo count corrected to 116 Co-Authored-By: Claude Sonnet 4.6 --- README.md | 171 +++++++++++++++++++++++++++--------------------------- 1 file changed, 86 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index f22f3cc..a44918c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ ML library is loaded or called at search time. ## Architecture overview ``` - 115 JPEG photos + 116 JPEG photos │ ▼ ┌───────────────────────────────┐ @@ -37,8 +37,8 @@ ML library is loaded or called at search time. │ │ ▼ ▼ ┌──────────────────────┐ ┌──────────────────────┐ ┌───────────────────────┐ - │ PostgreSQL 16 │ │ Oracle 26ai │ │ Oracle 26ai │ - │ + pgvector 0.6.0 │ │ (version 23.26.1) │ │ (version 23.26.1) │ + │ PostgreSQL 18 │ │ Oracle 26ai │ │ Oracle 26ai │ + │ + pgvector 0.8.2 │ │ (version 23.26.1) │ │ (version 23.26.1) │ │ database: │ │ PDB: FREEPDB1 │ │ PDB: FREEPDB1 │ │ vectors_demo │ │ user: vectors_user │ │ schema: VECTOR │ │ HNSW index │ │ HNSW index │ │ HNSW not needed │ @@ -57,8 +57,7 @@ ML library is loaded or called at search time. └──────┬───────┘ └──────┬───────┘ │ port 8002 │ │ │ └────────┬─────────┘ ▼ ▼ ▼ - frontend/index.html frontend/index.html frontend/index_indb.html - (badge: pgvector) (badge: Oracle 26ai) (badge: Oracle In-DB) + /ui/ (pgvector) /ui/ (Oracle 26ai) /ui/ (Oracle In-DB) ``` --- @@ -66,50 +65,59 @@ ML library is loaded or called at search time. ## Project structure ``` -pgvector-demo/ -├── backend/ -│ ├── .env # PostgreSQL credentials, photo path -│ ├── db.py # PostgreSQL connection factory -│ ├── embedder.py # CLIP model wrapper -│ ├── index_images.py # One-time indexing script -│ └── main.py # FastAPI app (port 8000) -└── frontend/ - └── index.html # Search UI - -oravector-demo/ -├── backend/ -│ ├── .env # Oracle credentials, photo path -│ ├── db_oracle.py # Oracle connection factory (vectors_user) -│ ├── embedder.py # CLIP model wrapper (identical to pgvector) -│ ├── index_images_oracle.py # One-time indexing script (Python embedding) -│ ├── main_oracle.py # FastAPI app — Python embedding (port 8001) -│ └── main_oracle_indb.py # FastAPI app — in-database embedding (port 8002) -└── frontend/ - ├── index.html # Search UI (Oracle 26ai, Python embedding) - └── index_indb.html # Search UI (Oracle 26ai, in-database embedding) +vector-search-demo/ +├── start.sh # Start all three backends +├── stop.sh # Stop all three backends +├── photos/ # 116 JPEG photos (gitignored) +├── pgvector-demo/ +│ ├── backend/ +│ │ ├── .env # PostgreSQL credentials, photo path +│ │ ├── db.py # PostgreSQL connection factory +│ │ ├── embedder.py # CLIP model wrapper +│ │ ├── index_images.py # One-time indexing script +│ │ └── main.py # FastAPI app (port 8000) +│ └── frontend/ +│ └── index.html # Search UI (served at /ui/) +└── oravector-demo/ + ├── backend/ + │ ├── .env # Oracle credentials, photo path + │ ├── db_oracle.py # Oracle connection factory + │ ├── embedder.py # CLIP model wrapper (identical to pgvector) + │ ├── index_images_oracle.py # One-time indexing script (Python embedding) + │ ├── main_oracle.py # FastAPI app — Python embedding (port 8001) + │ └── main_oracle_indb.py # FastAPI app — in-database embedding (port 8002) + └── frontend/ + ├── index.html # Search UI (Oracle 26ai, served at port 8001 /ui/) + └── indb/ + └── index.html # Search UI (Oracle In-DB, served at port 8002 /ui/) ``` --- -## System components installed +## System components -### Operating system packages +### PostgreSQL (Docker) -| Package | Version | Purpose | -|---|---|---| -| PostgreSQL | 16.13 (Ubuntu) | Relational database | -| postgresql-16-pgvector | 0.6.0 | Vector data type and indexes for PostgreSQL | -| Python | 3.12.3 | Runtime for all backend code | -| Podman | — | Container runtime for Oracle 26ai | +| Property | Value | +|---|---| +| Image | `pgvector/pgvector:pg18` | +| Version | PostgreSQL 18 | +| pgvector version | 0.8.2 | +| Host port | 5433 (mapped to 5432 inside container) | +| Database | `vectors_demo` | +| User | `dl` | +| Compose file | `~/docker/postgresql/docker-compose.yml` | -**PostgreSQL pgvector installation:** +**Start PostgreSQL:** ```bash -sudo apt install postgresql-16-pgvector +cd ~/docker/postgresql && docker compose up -d ``` -**pgvector extension activation** (requires superuser, run once per database): +The `pgvector/pgvector:pg18` image includes pgvector pre-installed. The extension +must be activated once per database: + ```bash -sudo -u postgres psql -d vectors_demo -c "CREATE EXTENSION vector;" +docker exec postgresql-database-1 psql -U dl -d vectors_demo -c "CREATE EXTENSION vector;" ``` ### Oracle 26ai (Podman container) @@ -121,29 +129,15 @@ sudo -u postgres psql -d vectors_demo -c "CREATE EXTENSION vector;" | Container name | `oracle.free` | | Host port | 37611 (mapped to 1521 inside container) | | Pluggable Database | FREEPDB1 | -| Schema user | `vectors_user` | +| Schema users | `vectors_user`, `VECTOR` | **Oracle vector memory** — the HNSW index is held entirely in the SGA's Vector -Memory Area. This must be configured before the database starts: +Memory Area. This is already configured: ```sql --- Connect as SYSDBA to service FREE (CDB root) ALTER SYSTEM SET vector_memory_size = 512M SCOPE=SPFILE; ``` -Then restart Oracle inside the container: -```bash -podman exec oracle.free bash -c "sqlplus -s / as sysdba <<'EOF' -SHUTDOWN ABORT; -EXIT; -EOF" - -podman exec oracle.free bash -c "sqlplus -s / as sysdba <<'EOF' -STARTUP; -EXIT; -EOF" -``` - After restart, the SGA confirms: `Vector Memory Area: 536870912 bytes (512 MB)`. ### Python packages @@ -206,8 +200,8 @@ The database then finds the photos whose vectors point in the most similar direc ### PostgreSQL + pgvector ```sql --- database: vectors_demo (PostgreSQL 16) -CREATE EXTENSION vector; -- pgvector 0.6.0 +-- database: vectors_demo (PostgreSQL 18) +CREATE EXTENSION vector; -- pgvector 0.8.2 CREATE TABLE images ( id SERIAL PRIMARY KEY, @@ -264,8 +258,9 @@ Reads `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` from `.env` and returns a `psycopg2` connection. **`db_oracle.py` (Oracle):** -Reads `ORA_HOST`, `ORA_PORT`, `ORA_SERVICE`, `ORA_USER`, `ORA_PASSWORD` from `.env` -and returns an `oracledb` connection. The DSN is assembled as `host:port/service`. +Reads `ORA_HOST`, `ORA_PORT`, `ORA_SERVICE`, `ORA_USER`, `ORA_PASSWORD` (and +`ORA_USER_INDB`, `ORA_PASSWORD_INDB` for the in-DB backend) from `.env` and +returns an `oracledb` connection. The DSN is assembled as `host:port/service`. Runs in **thin mode** — no Oracle Instant Client installation is required on the host. --- @@ -280,7 +275,7 @@ photos. Each photo is committed individually so a crash does not lose prior work | Run command | `python3 index_images.py` | `python3 index_images_oracle.py` | | Vector bind | Python `list` passed directly | `array.array("f", embedding)` required | | Bind style | `%s` placeholders (psycopg2) | `:1`, `:2`, `:3` positional (oracledb) | -| Runtime (115 photos, CPU) | **26 seconds** | **16 seconds** | +| Runtime (116 photos, CPU) | ~26 seconds | ~16 seconds | **Why `array.array` for Oracle?** The `python-oracledb` driver does not accept a plain Python list for a `VECTOR` @@ -291,13 +286,14 @@ matching the `FLOAT32` declaration in the Oracle column type. ### FastAPI applications -Both apps expose identical endpoints at different ports: +All three apps expose identical endpoints: | Endpoint | Description | |---|---| | `GET /search?q=&limit=` | Embed query, run nearest-neighbour search, return ranked results | | `GET /stats` | Return count of indexed photos | | `GET /photos/` | Serve original JPEG from the photos directory | +| `GET /ui/` | Serve the search frontend (HTML) | **Search query comparison:** @@ -326,7 +322,7 @@ FETCH FIRST :lim ROWS ONLY | Cast required | `$1::vector` — explicit cast | No cast, column type is enforced | | Top-N clause | `LIMIT n` | `FETCH FIRST n ROWS ONLY` | | Bind style | `$1`, `$2` positional (psycopg2) | `:name` named binds (dict) | -| Repeated param | `$1` can appear multiple times | Same `:name` can appear multiple times; positional `:1` cannot be reused | +| Repeated param | `$1` can appear multiple times | Same `:name` can appear multiple times | | Score formula | `1 - (embedding <=> val)` | `1 - VECTOR_DISTANCE(...)` | In both cases `1 − distance` converts cosine distance (0 = identical) into a @@ -336,14 +332,13 @@ similarity score (1.0 = identical), displayed as a percentage in the frontend. ## Frontend -Both frontends are identical single HTML files with no build step. Open directly -in a browser. +Three single-file HTML frontends, each served by its own backend at `/ui/`: -| | pgvector frontend | Oracle 26ai frontend | -|---|---|---| -| File | `pgvector-demo/frontend/index.html` | `oravector-demo/frontend/index.html` | -| Badge label | pgvector | Oracle 26ai | -| API base URL | `http://localhost:8000` | `http://localhost:8001` | +| | pgvector | Oracle 26ai | Oracle In-DB | +|---|---|---|---| +| URL | `http://localhost:8000/ui/` | `http://localhost:8001/ui/` | `http://localhost:8002/ui/` | +| Badge colour | Blue | Red | Purple | +| File | `pgvector-demo/frontend/index.html` | `oravector-demo/frontend/index.html` | `oravector-demo/frontend/indb/index.html` | Features: search box, Enter-key support, suggestion chips (trees, water, people, buildings, sky, street, night, cars), result grid with thumbnails and similarity @@ -353,29 +348,35 @@ scores in percent. ## Running the applications -**Start PostgreSQL backend** (Python embedding): +### Start all backends + ```bash -cd pgvector-demo/backend -uvicorn main:app --host 0.0.0.0 --port 8000 +./start.sh ``` -**Start Oracle backend — Python embedding:** +This starts all three backends concurrently. Press Ctrl+C to stop all. + +### Stop all backends + ```bash -cd oravector-demo/backend -uvicorn main_oracle:app --host 0.0.0.0 --port 8001 +./stop.sh ``` -**Start Oracle backend — in-database embedding:** +### Start backends individually + ```bash -cd oravector-demo/backend -uvicorn main_oracle_indb:app --host 0.0.0.0 --port 8002 +# PostgreSQL backend +cd pgvector-demo/backend && uvicorn main:app --host 0.0.0.0 --port 8000 + +# Oracle backend — Python embedding +cd oravector-demo/backend && uvicorn main_oracle:app --host 0.0.0.0 --port 8001 + +# Oracle backend — in-database embedding +cd oravector-demo/backend && uvicorn main_oracle_indb:app --host 0.0.0.0 --port 8002 ``` -Open the matching `frontend/index.html` (ports 8000/8001) or -`frontend/index_indb.html` (port 8002) in a browser. All three can run -simultaneously. +### Re-index after adding photos -**Re-index after adding photos:** ```bash # PostgreSQL cd pgvector-demo/backend && python3 index_images.py @@ -441,8 +442,8 @@ Oracle's `DBMS_VECTOR.LOAD_ONNX_MODEL` requires the model's ONNX graph to use export uses `input_ids` additionally in `ArgMax` for EOS-token pooling, which Oracle's validator rejects. The manually loaded CLIP_TXT model in the `VECTOR` schema uses CLS-token pooling (position 0) instead, which produces a simpler -graph that Oracle accepts. The -cosine similarity between EOS-pooling and CLS-pooling variants is ~0.70. +graph that Oracle accepts. The cosine similarity between EOS-pooling and +CLS-pooling variants is ~0.70. --- @@ -452,8 +453,8 @@ Measured on this installation (CPU only, no GPU): | Metric | PostgreSQL + pgvector | Oracle 26ai (Python embed) | Oracle 26ai (in-DB embed) | |---|---|---|---| -| Photos indexed | 115 | 115 | 116 (manually indexed) | -| Indexing time | 26 seconds | 16 seconds | 0 (indexed separately by admin) | +| Photos indexed | 116 | 116 | 116 (manually indexed) | +| Indexing time | ~26 seconds | ~16 seconds | 0 (indexed separately by admin) | | Index type | HNSW (on disk) | HNSW (in-memory) | Full table scan (116 rows) | | Memory required | None | 512 MB SGA | 512 MB SGA | | Python CLIP at query time | Yes | Yes | **No** |