┌──────────────────┐ ┌───────────────────────┐ ┌────────────────────┐│ product_catalog │ │ product_image_search │ │ image_search_log ││──────────────────│ │───────────────────────│ │────────────────────││ product_id (PK) │◄────│ product_id (FK) │ │ id (PK) ││ product_name │ │ image_id (UK) │ │ session_id ││ category │ │ embedding vector(768) │ │ query_vector ││ brand │ │ image_type │ │ result_count ││ price │ │ angle │ │ top_similarity ││ tags │ │ metadata (JSONB) │ │ search_time_ms │└──────────────────┘ └───────────────────────┘ └────────────────────┘
SELECTversion() AS pg_version,current_database() AS database,current_user AS user_name,pg_size_pretty(pg_database_size(current_database())) AS db_size,inet_server_addr() AS server_ip,inet_server_port() AS server_port;
pg_version | database | user_name | db_size | server_ip | server_port |
PostgreSQL 15.14 on x86_64-pc-linux-gnu, compiled by gcc ... | functional_verification | postgres_admin | 9175 kB | 30.121.110.245 | 50742 |
SELECT name, default_version, commentFROM pg_available_extensionsWHERE name IN ('vector', 'age', 'pg_trgm', 'pg_prewarm')ORDER BY name;
name | default_version | comment |
age | 1.5.0 | AGE database extension |
pg_prewarm | 1.2 | prewarm relation data |
pg_trgm | 1.6 | text similarity measurement and index searching based on trigrams |
vector | 0.8.2 | vector data type and ivfflat and hnsw access methods |
CREATE EXTENSION IF NOT EXISTS vector;
SELECT extname, extversionFROM pg_extensionWHERE extname = 'vector';
extname | extversion |
vector | 0.8.2 |
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pg_prewarm;
DROP TABLE IF EXISTS product_image_search CASCADE;DROP TABLE IF EXISTS product_catalog CASCADE;DROP TABLE IF EXISTS image_search_log CASCADE;
CREATE TABLE product_catalog (id BIGSERIAL PRIMARY KEY,product_id VARCHAR(32) NOT NULL UNIQUE,product_name TEXT NOT NULL,category VARCHAR(100) NOT NULL,sub_category VARCHAR(100),brand VARCHAR(100),price NUMERIC(10, 2),description TEXT,tags TEXT[],status SMALLINT DEFAULT 1,created_at TIMESTAMPTZ DEFAULT NOW(),updated_at TIMESTAMPTZ DEFAULT NOW());COMMENT ON TABLE product_catalog IS 'Product catalog table - storing basic product information';
CREATE TABLE product_image_search (id BIGSERIAL PRIMARY KEY,image_id VARCHAR(64) NOT NULL UNIQUE,product_id VARCHAR(32) NOT NULL REFERENCES product_catalog(product_id),image_url TEXT NOT NULL,thumbnail_url TEXT,embedding vector(768) NOT NULL,image_type VARCHAR(20) DEFAULT 'main',angle VARCHAR(20) DEFAULT 'front',background VARCHAR(20) DEFAULT 'white',metadata JSONB DEFAULT '{}',status SMALLINT DEFAULT 1,created_at TIMESTAMPTZ DEFAULT NOW());COMMENT ON TABLE product_image_search IS 'Product image vector table - core table for image search by image';COMMENT ON COLUMN product_image_search.embedding IS '768-dimensional image feature vector generated by the CLIP ViT-L/14 model';COMMENT ON COLUMN product_image_search.image_type IS 'Image type: main=main image, detail=detail image, scene=scene image';COMMENT ON COLUMN product_image_search.angle IS 'Shooting angle: front=front view, side=side view, back=back view, top=top view';
CREATE TABLE image_search_log (id BIGSERIAL PRIMARY KEY,session_id VARCHAR(64),query_vector vector(768),query_image_url TEXT,result_count INT,top_similarity REAL,search_time_ms REAL,filter_category VARCHAR(100),created_at TIMESTAMPTZ DEFAULT NOW());COMMENT ON TABLE image_search_log IS 'Image-based search log - record search behavior and performance';
SELECTc.relname AS table_name,obj_description(c.oid) AS comment,pg_size_pretty(pg_total_relation_size(c.oid)) AS total_sizeFROM pg_class cJOIN pg_namespace n ON n.oid = c.relnamespaceWHERE n.nspname = 'public'AND c.relkind = 'r'AND c.relname IN ('product_catalog', 'product_image_search', 'image_search_log')ORDER BY c.relname;
table_name | comment | total_size |
image_search_log | Image-based search log - record search behavior and performance | 16KB |
product_catalog | Product catalog table - storing basic product information | 24KB |
product_image_search | Product image vector table - core table for image-based search | 24KB |
SELECTcolumn_name,data_type,character_maximum_length,column_default,is_nullableFROM information_schema.columnsWHERE table_name = 'product_image_search'AND table_schema = 'public'ORDER BY ordinal_position;
column_name | data_type | character_maximum_length | column_default | is_nullable |
id | bigint | NULL | nextval('product_image_search_id_seq'::regclass) | NO |
image_id | character varying | 64 | NULL | NO |
product_id | character varying | 32 | NULL | NO |
image_url | text | NULL | NULL | NO |
thumbnail_url | text | NULL | NULL | YES |
embedding | USER-DEFINED | NULL | NULL | NO |
image_type | character varying | 20 | 'main'::character varying | YES |
angle | character varying | 20 | 'front'::character varying | YES |
background | character varying | 20 | 'white'::character varying | YES |
metadata | jsonb | NULL | '{}'::jsonb | YES |
status | smallint | NULL | 1 | YES |
created_at | timestamp with time zone | NULL | now() | YES |
INSERT INTO product_catalog (product_id, product_name, category, sub_category, brand, price, description, tags, status)SELECT'P' || lpad(g::text, 6, '0'),CASE (g % 8)WHEN 0 THEN 'Smartphone ' || (ARRAY['Pro','Max','Ultra','Lite','SE','Plus','Mini','Air'])[(g % 8) + 1] || ' ' || gWHEN 1 THEN 'Laptop ' || (ARRAY['ThinkPad','MacBook','Surface','MateBook','XPS','Yoga','ZenBook','Swift'])[(g % 8) + 1] || ' ' || gWHEN 2 THEN 'Sneakers ' || (ARRAY['Air Max','Ultra Boost','Gel','Fresh Foam','React','Zoom','NB','Asics'])[(g % 8) + 1] || ' ' || gWHEN 3 THEN 'Mechanical Keyboard ' || (ARRAY['Cherry','Filco','Leopold','HHKB','Ducky','Razer','Logitech','Keychron'])[(g % 8) + 1] || ' ' || gWHEN 4 THEN 'Smartwatch ' || (ARRAY['Apple Watch','Galaxy Watch','Huawei Watch','Amazfit','Garmin','Fitbit','TicWatch','Suunto'])[(g % 8) + 1] || ' ' || gWHEN 5 THEN 'Wireless Earbuds ' || (ARRAY['AirPods','Galaxy Buds','FreeBuds','WF-1000','WH-1000','Bose QC','Jabra','Sony'])[(g % 8) + 1] || ' ' || gWHEN 6 THEN 'Backpack ' || (ARRAY['Osprey','North Face','Arc','Samsonite','Tumi','Incase','Herschel','Peak'])[(g % 8) + 1] || ' ' || gELSE 'Coffee Machine ' || (ARRAY['Nespresso','De Longhi','Breville','Jura','Saeco','Krups','Moccamaster','Fellow'])[(g % 8) + 1] || ' ' || gEND,(ARRAY['Mobile & Digital','Computers & Office','Sports & Outdoors','Peripherals & Accessories','Wearable Devices','Audio Equipment','Luggage & Leather Goods','Home Appliances'])[(g % 8) + 1],-- ... (Brand/Price/Description/Tag/Status auto-generated)FROM generate_series(1, 200) g;
SELECTcategory,count(*) AS product_count,round(avg(price), 2) AS avg_price,min(price) AS min_price,max(price) AS max_priceFROM product_catalogWHERE status = 1GROUP BY categoryORDER BY product_count DESC;
category | product_count | avg_price | min_price | max_price |
Peripherals & Accessories | 25 | 2600.00 | 200.00 | 5000.00 |
Home Appliances | 25 | 2600.00 | 200.00 | 5000.00 |
Sports & Outdoors | 25 | 2500.00 | 100.00 | 4900.00 |
Audio Equipment | 25 | 2600.00 | 200.00 | 5000.00 |
Computers & Office | 25 | 2600.00 | 200.00 | 5000.00 |
Luggage & Leather Goods | 25 | 2500.00 | 100.00 | 4900.00 |
Mobile Phones & Digital | 20 | 2600.00 | 300.00 | 4900.00 |
Wearable Devices | 20 | 2600.00 | 300.00 | 4900.00 |
INSERT INTO product_image_search(image_id, product_id, image_url, thumbnail_url, embedding, image_type, angle, background, metadata, status)SELECT'IMG-' || lpad(sub.img_idx::text, 8, '0'),sub.pid,'https://cdn.example.com/products/' || sub.pid || '/' || sub.itype || '.jpg','https://cdn.example.com/products/' || sub.pid || '/' || sub.itype || '_thumb.jpg',-- Generate similar vectors for images of the same product (adding noise to simulate different angles)(SELECT array_agg(sin(sub.base_seed + i * 0.01 + sub.noise_offset) * 0.5+ cos(sub.base_seed * 2 + i * 0.02) * 0.3+ (random() - 0.5) * sub.noise_factor)::vector(768)FROM generate_series(1, 768) i),sub.itype,sub.angle_val,(ARRAY['white','transparent','scene','studio'])[(sub.img_idx % 4) + 1],jsonb_build_object('width', 800 + (sub.img_idx % 400),'height', 800 + (sub.img_idx % 400),'format', 'jpg','size_kb', 50 + (sub.img_idx % 200),'model', 'CLIP-ViT-L/14','dim', 768),1FROM (SELECTp.product_id AS pid,(p.id - 1) * 3 + t.n AS img_idx,CASE t.n WHEN 1 THEN 'main' WHEN 2 THEN 'detail' ELSE 'scene' END AS itype,CASE t.n WHEN 1 THEN 'front' WHEN 2 THEN 'side' ELSE 'top' END AS angle_val,(p.id * 7.13 + (p.id % 8) * 100) AS base_seed,t.n * 0.05 AS noise_offset,CASE t.n WHEN 1 THEN 0.02 WHEN 2 THEN 0.05 ELSE 0.08 END AS noise_factorFROM product_catalog pCROSS JOIN (SELECT generate_series(1, 3) AS n) t) sub;
SELECTpis.image_type,count(*) AS image_count,count(DISTINCT pis.product_id) AS product_count,pg_size_pretty(sum(pg_column_size(pis.embedding))) AS vector_storageFROM product_image_search pisWHERE pis.status = 1GROUP BY pis.image_typeORDER BY image_count DESC;
image_type | image_count | product_count | vector_storage |
detail | 200 | 200 | 601KB |
main | 200 | 200 | 601KB |
scene | 200 | 200 | 601KB |
ANALYZE product_catalog;ANALYZE product_image_search;ANALYZE image_search_log;
CREATE INDEX idx_product_image_embedding_cosineON product_image_searchUSING hnsw (embedding vector_cosine_ops)WITH (m = 16, ef_construction = 128);
Parameter | Value | Description |
m | 16 | The maximum number of connections per node. A higher value improves precision but consumes more memory. |
ef_construction | 128 | The search scope during construction. The larger the value, the slower the build process but the higher the index quality. |
Operator class | vector_cosine_ops | Cosine distance, suitable for normalized vectors. |
-- product ID association indexCREATE INDEX idx_product_image_product_id ON product_image_search (product_id);-- Image type filtering index (partial index)CREATE INDEX idx_product_image_type ON product_image_search (image_type) WHERE status = 1;-- Product classification index (partial index)CREATE INDEX idx_product_catalog_category ON product_catalog (category) WHERE status = 1;-- Brand filtering indexCREATE INDEX idx_product_catalog_brand ON product_catalog (brand) WHERE status = 1;-- Price range indexCREATE INDEX idx_product_catalog_price ON product_catalog (price) WHERE status = 1;-- Product Tag GIN indexCREATE INDEX idx_product_catalog_tags ON product_catalog USING GIN (tags) WHERE status = 1;-- Image metadata GIN indexCREATE INDEX idx_product_image_metadata ON product_image_search USING GIN (metadata);-- Search log time indexCREATE INDEX idx_search_log_created ON image_search_log (created_at DESC);
SELECTindexrelname AS index_name,relname AS table_name,pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,idx_scan AS scansFROM pg_stat_user_indexesWHERE schemaname = 'public'AND relname IN ('product_image_search', 'product_catalog', 'image_search_log')ORDER BY pg_relation_size(indexrelid) DESC;
index_name | table_name | index_size | scans |
idx_product_image_embedding_cosine | product_image_search | 2408KB | 0 |
idx_product_image_metadata | product_image_search | 64KB | 0 |
product_image_search_image_id_key | product_image_search | 40KB | 0 |
idx_product_image_product_id | product_image_search | 32KB | 0 |
product_image_search_pkey | product_image_search | 32KB | 0 |
idx_product_catalog_price | product_catalog | 16KB | 0 |
idx_product_catalog_tags | product_catalog | 16KB | 0 |
product_catalog_pkey | product_catalog | 16KB | 0 |
product_catalog_product_id_key | product_catalog | 16KB | 600 |
idx_product_catalog_category | product_catalog | 16KB | 0 |
idx_product_catalog_brand | product_catalog | 16KB | 0 |
idx_product_image_type | product_image_search | 16KB | 0 |
idx_search_log_created | image_search_log | 8192bytes | 0 |
image_search_log_pkey | image_search_log | 8192bytes | 0 |
SET hnsw.ef_search = 100;SET hnsw.iterative_scan = relaxed_order;
Parameter | Value | Description |
hnsw.ef_search | 100 | The larger the candidate set size during search, the more precise but slower the results. |
hnsw.iterative_scan | relaxed_order | Iterative scan optimization supporting HNSW search with filter criteria. |
WITH query_image AS (SELECT embeddingFROM product_image_searchWHERE product_id = 'P000001' AND image_type = 'main'LIMIT 1)SELECTpis.image_id,pis.product_id,pc.product_name,pc.category,pc.brand,pc.price,pis.image_type,round((1 - (pis.embedding <=> qi.embedding))::numeric, 4) AS similarityFROM product_image_search pisCROSS JOIN query_image qiJOIN product_catalog pc ON pc.product_id = pis.product_idWHERE pis.status = 1AND pis.product_id != 'P000001'ORDER BY pis.embedding <=> qi.embeddingLIMIT 10;
image_id | product_id | product_name | category | brand | price | image_type | similarity |
IMG-00000565 | P000189 | wireless headphones Bose QC 189 | Audio Equipment | JBL | 4000.00 | main | 0.9998 |
IMG-00000235 | P000079 | coffee machine Fellow 79 | Home Appliances | DeLonghi | 3000.00 | main | 0.9989 |
IMG-00000566 | P000189 | wireless headphones Bose QC 189 | Audio Equipment | JBL | 4000.00 | detail | 0.9984 |
IMG-00000293 | P000098 | running shoes Gel 98 | Sports & Outdoors | Nike | 4900.00 | detail | 0.9964 |
IMG-00000236 | P000079 | coffee machine Fellow 79 | Home Appliances | DeLonghi | 3000.00 | detail | 0.9964 |
IMG-00000274 | P000092 | smart watch Garmin 92 | Wearable Devices | Amazfit | 4300.00 | main | 0.9960 |
IMG-00000292 | P000098 | running shoes Gel 98 | Sports & Outdoors | Nike | 4900.00 | main | 0.9956 |
IMG-00000294 | P000098 | running shoes Gel 98 | Sports & Outdoors | Nike | 4900.00 | scene | 0.9947 |
IMG-00000567 | P000189 | wireless headphones Bose QC 189 | Audio Equipment | JBL | 4000.00 | scene | 0.9946 |
IMG-00000254 | P000085 | wireless headphones Bose QC 85 | Audio Equipment | Jabra | 3600.00 | detail | 0.9924 |
WITH query AS (SELECT embedding, product_idFROM product_image_searchWHERE product_id = 'P000010' AND image_type = 'main'LIMIT 1)SELECTpis.image_id,pis.product_id,pis.image_type,pis.angle,CASE WHEN pis.product_id = q.product_id THEN '✅ same product' ELSE 'other product' END AS match_type,round((1 - (pis.embedding <=> q.embedding))::numeric, 4) AS similarityFROM product_image_search pisCROSS JOIN query qWHERE pis.status = 1ORDER BY pis.embedding <=> q.embeddingLIMIT 10;
image_id | product_id | image_type | angle | match_type | similarity |
IMG-00000028 | P000010 | main | front | same product | 1.0000 |
IMG-00000592 | P000198 | main | front | other product | 0.9998 |
IMG-00000358 | P000120 | main | front | other product | 0.9987 |
IMG-00000359 | P000120 | detail | side | other product | 0.9984 |
IMG-00000593 | P000198 | detail | side | other product | 0.9984 |
IMG-00000029 | P000010 | detail | side | same product | 0.9983 |
IMG-00000320 | P000107 | detail | side | other product | 0.9964 |
IMG-00000301 | P000101 | main | front | other product | 0.9958 |
IMG-00000360 | P000120 | scene | top | other product | 0.9956 |
IMG-00000319 | P000107 | main | front | other product | 0.9955 |
WITH query_image AS (SELECT embeddingFROM product_image_searchWHERE product_id = 'P000001' AND image_type = 'main'LIMIT 1)SELECTpis.image_id,pis.product_id,pc.product_name,pc.category,pc.brand,pc.price,round((1 - (pis.embedding <=> qi.embedding))::numeric, 4) AS similarityFROM product_image_search pisCROSS JOIN query_image qiJOIN product_catalog pc ON pc.product_id = pis.product_idWHERE pis.status = 1AND pc.category = 'Mobile & Digital Products'AND pis.image_type = 'main'AND pis.product_id != 'P000001'ORDER BY pis.embedding <=> qi.embeddingLIMIT 10;
image_id | product_id | product_name | category | brand | price | similarity |
IMG-00000022 | P000008 | Smartphone Pro 8 | Mobile Phones & Digital | Samsung | 900.00 | 0.9750 |
IMG-00000310 | P000104 | Smartphone Pro 104 | Mobile Phones & Digital | Samsung | 500.00 | 0.9638 |
IMG-00000334 | P000112 | Smartphone Pro 112 | Mobile Phones & Digital | Huawei | 1300.00 | 0.9367 |
IMG-00000598 | P000200 | Smartphone Pro 200 | Mobile Phones & Digital | Samsung | 100.00 | 0.7316 |
IMG-00000046 | P000016 | Smartphone Pro 16 | Mobile Phones & Digital | Huawei | 1700.00 | 0.6672 |
IMG-00000286 | P000096 | Smartphone Pro 96 | Mobile Phones & Digital | Apple | 4700.00 | 0.6388 |
IMG-00000358 | P000120 | Smartphone Pro 120 | Mobile Phones & Digital | Xiaomi | 2100.00 | 0.5713 |
IMG-00000574 | P000192 | Smartphone Pro 192 | Mobile Phones & Digital | Apple | 4300.00 | 0.2146 |
IMG-00000070 | P000024 | Smartphone Pro 24 | Mobile Phones & Digital | Xiaomi | 2500.00 | 0.1986 |
IMG-00000382 | P000128 | Smartphone Pro 128 | Mobile Phones & Digital | Apple | 2900.00 | 0.1082 |
WITH query_image AS (SELECT embeddingFROM product_image_searchWHERE product_id = 'P000003' AND image_type = 'main'LIMIT 1)SELECTpis.image_id,pis.product_id,pc.product_name,pc.category,pc.price,round((1 - (pis.embedding <=> qi.embedding))::numeric, 4) AS similarityFROM product_image_search pisCROSS JOIN query_image qiJOIN product_catalog pc ON pc.product_id = pis.product_idWHERE pis.status = 1AND pc.price BETWEEN 100 AND 2000AND pis.image_type = 'main'AND pis.product_id != 'P000003'ORDER BY pis.embedding <=> qi.embeddingLIMIT 10;
image_id | product_id | product_name | category | price | similarity |
IMG-00000337 | P000113 | Laptop MacBook 113 | Computers & Office | 1400.00 | 0.9987 |
IMG-00000046 | P000016 | Smartphone Pro 16 | Mobile Phones & Digital | 1700.00 | 0.9987 |
IMG-00000298 | P000100 | smart watch Garmin 100 | Wearable Devices | 100.00 | 0.9956 |
IMG-00000319 | P000107 | Mechanical Keyboard HHKB 107 | Peripherals & Accessories | 800.00 | 0.9905 |
IMG-00000025 | P000009 | Laptop MacBook 9 | Computers & Office | 1000.00 | 0.9832 |
IMG-00000028 | P000010 | running shoes Gel 10 | Sports & Outdoors | 1100.00 | 0.9736 |
IMG-00000316 | P000106 | Sports Shoes Gel 106 | Sports & Outdoors | 700.00 | 0.9624 |
IMG-00000301 | P000101 | Wireless Headphones Bose QC 101 | Audio Equipment | 200.00 | 0.9496 |
IMG-00000340 | P000114 | Sports Shoes Gel 114 | Sports & Outdoors | 1500.00 | 0.9346 |
IMG-00000004 | P000002 | Sports Shoes Gel 2 | Sports & Outdoors | 300.00 | 0.9176 |
WITH query_image AS (SELECT embeddingFROM product_image_searchWHERE product_id = 'P000010' AND image_type = 'main'LIMIT 1)SELECTpis.image_id,pis.product_id,pc.product_name,pc.category,round((1 - (pis.embedding <=> qi.embedding))::numeric, 4) AS similarityFROM product_image_search pisCROSS JOIN query_image qiJOIN product_catalog pc ON pc.product_id = pis.product_idWHERE pis.status = 1AND 1 - (pis.embedding <=> qi.embedding) > 0.85ORDER BY pis.embedding <=> qi.embeddingLIMIT 20;
image_id | product_id | product_name | category | similarity |
IMG-00000028 | P000010 | running shoes Gel 10 | Sports & Outdoors | 1.0000 |
IMG-00000592 | P000198 | Herschel Backpack 198 | Luggage & Leather Goods | 0.9998 |
IMG-00000358 | P000120 | Smartphone Pro 120 | Mobile Phones & Digital | 0.9987 |
IMG-00000359 | P000120 | Smartphone Pro 120 | Mobile Phones & Digital | 0.9984 |
IMG-00000593 | P000198 | Herschel Backpack 198 | Luggage & Leather Goods | 0.9984 |
IMG-00000029 | P000010 | running shoes Gel 10 | Sports & Outdoors | 0.9983 |
IMG-00000320 | P000107 | Mechanical Keyboard HHKB 107 | Peripherals & Accessories | 0.9964 |
IMG-00000301 | P000101 | Wireless Headphones Bose QC 101 | Audio Equipment | 0.9958 |
IMG-00000360 | P000120 | Smartphone Pro 120 | Mobile Phones & Digital | 0.9956 |
IMG-00000319 | P000107 | Mechanical Keyboard HHKB 107 | Peripherals & Accessories | 0.9955 |
... | ... | ... | ... | ... |
IMG-00000303 | P000101 | Wireless Headphones Bose QC 101 | Audio Equipment | 0.9867 |
WITH query_image AS (SELECT embeddingFROM product_image_searchWHERE product_id = 'P000001' AND image_type = 'main'LIMIT 1),similar_products AS (SELECT DISTINCT ON (pc.brand)pis.product_id,pc.product_name,pc.brand,pc.category,pc.price,round((1 - (pis.embedding <=> qi.embedding))::numeric, 4) AS similarityFROM product_image_search pisCROSS JOIN query_image qiJOIN product_catalog pc ON pc.product_id = pis.product_idWHERE pis.status = 1AND pis.image_type = 'main'AND pis.product_id != 'P000001'ORDER BY pc.brand, pis.embedding <=> qi.embedding)SELECT * FROM similar_productsORDER BY similarity DESCLIMIT 10;
product_id | product_name | brand | category | price | similarity |
P000189 | wireless headphones Bose QC 189 | JBL | Audio Equipment | 4000.00 | 0.9998 |
P000079 | coffee machine Fellow 79 | DeLonghi | Home Appliances | 3000.00 | 0.9989 |
P000092 | smart watch Garmin 92 | Amazfit | Wearable Devices | 4300.00 | 0.9960 |
P000098 | running shoes Gel 98 | Nike | Sports & Outdoors | 4900.00 | 0.9956 |
P000105 | Laptop MacBook 105 | Dell | Computers & Office | 600.00 | 0.9910 |
P000085 | wireless headphones Bose QC 85 | Jabra | Audio Equipment | 3600.00 | 0.9906 |
P000183 | coffee machine Fellow 183 | Breville | Home Appliances | 3400.00 | 0.9844 |
P000195 | Mechanical Keyboard HHKB 195 | Cherry | Peripherals & Accessories | 4600.00 | 0.9833 |
P000196 | smart watch Garmin 196 | Apple | Wearable Devices | 4700.00 | 0.9757 |
P000008 | Smartphone Pro 8 | Samsung | Mobile Phones & Digital | 900.00 | 0.9750 |
WITH query_image AS (SELECT embeddingFROM product_image_searchWHERE product_id = 'P000005' AND image_type = 'main'LIMIT 1)SELECTpis.product_id,pc.product_name,pc.category,pc.price,round((1 - (pis.embedding <=> qi.embedding))::numeric, 4) AS image_sim,round(similarity(pc.product_name, 'smartwatch')::numeric, 4) AS text_sim,round((0.7 * (1 - (pis.embedding <=> qi.embedding))+ 0.3 * similarity(pc.product_name, 'smartwatch'))::numeric, 4) AS hybrid_scoreFROM product_image_search pisCROSS JOIN query_image qiJOIN product_catalog pc ON pc.product_id = pis.product_idWHERE pis.status = 1AND pis.image_type = 'main'ORDER BY hybrid_score DESCLIMIT 10;
product_id | product_name | category | price | image_sim | text_sim | hybrid_score |
P000005 | wireless headphones Bose QC 5 | Audio Equipment | 600.00 | 1.0000 | 0.0000 | 0.7000 |
P000128 | Smartphone Pro 128 | Mobile Phones & Digital | 2900.00 | 0.9998 | 0.0000 | 0.6998 |
P000018 | running shoes Gel 18 | Sports & Outdoors | 1900.00 | 0.9988 | 0.0000 | 0.6992 |
P000115 | Mechanical Keyboard HHKB 115 | Peripherals & Accessories | 1600.00 | 0.9987 | 0.0000 | 0.6991 |
P000102 | Herschel Backpack 102 | Luggage & Leather Goods | 300.00 | 0.9955 | 0.0000 | 0.6968 |
P000109 | Wireless Headphones Bose QC 109 | Audio Equipment | 1000.00 | 0.9910 | 0.0000 | 0.6937 |
P000024 | Smartphone Pro 24 | Mobile Phones & Digital | 2500.00 | 0.9908 | 0.0000 | 0.6936 |
P000122 | Sports Shoes Gel 122 | Sports & Outdoors | 2300.00 | 0.9842 | 0.0000 | 0.6889 |
P000011 | Mechanical Keyboard HHKB 11 | Peripherals & Accessories | 1200.00 | 0.9837 | 0.0000 | 0.6886 |
P000199 | coffee machine Fellow 199 | Home Appliances | 5000.00 | 0.9830 | 0.0000 | 0.6881 |
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)SELECTpis.image_id,pis.product_id,1 - (pis.embedding <=> (SELECT embedding FROM product_image_search WHERE id = 1)) AS similarityFROM product_image_search pisWHERE pis.status = 1ORDER BY pis.embedding <=> (SELECT embedding FROM product_image_search WHERE id = 1)LIMIT 10;
Limit (cost=69.55..69.58 rows=10 width=37) (actual time=...)Buffers: shared hit=7237InitPlan 1 (returns $0)-> Index Scan using product_image_search_pkey on pro...Index Cond: (id = 1)Buffers: shared hit=6InitPlan 2 (returns $1)-> Index Scan using product_image_search_pkey on pro...Index Cond: (id = 1)Buffers: shared hit=3-> Sort (cost=52.97..54.47 rows=600 width=37) (actual...)Sort Key: ((pis.embedding <=> $1))Sort Method: top-N heapsort Memory: 25kBBuffers: shared hit=7237-> Seq Scan on product_image_search pis (cost=0...)Filter: (status = 1)Buffers: shared hit=7237Planning:Buffers: shared hit=12Planning Time: 0.141 msExecution Time: 8.436 ms
INSERT INTO image_search_log (session_id, query_vector, query_image_url, result_count, top_similarity, search_time_ms, filter_category)SELECT'sess-test-001',embedding,'https://user-upload.example.com/query.jpg',10,0.95,15.3,'Mobile Devices & Electronics'FROM product_image_searchWHERE id = 1;
SELECT session_id, result_count, top_similarity, search_time_ms, filter_category, created_atFROM image_search_logORDER BY created_at DESCLIMIT 5;
session_id | result_count | top_similarity | search_time_ms | filter_category | created_at |
sess-test-001 | 10 | 0.95 | 15.3 | Mobile Phones & Digital | 2026-03-24 17:26:15.941036+08:00 |
SELECT pg_prewarm('idx_product_image_embedding_cosine') AS pages_loaded;
pages_loaded |
301 |
SELECTc.relname AS table_name,pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,pg_size_pretty(pg_relation_size(c.oid)) AS data_size,pg_size_pretty(pg_indexes_size(c.oid)) AS index_size,s.n_live_tup AS estimated_rowsFROM pg_class cJOIN pg_stat_user_tables s ON s.relid = c.oidWHERE c.relname IN ('product_catalog', 'product_image_search', 'image_search_log')ORDER BY pg_total_relation_size(c.oid) DESC;
table_name | total_size | data_size | index_size | estimated_rows |
product_image_search | 5312KB | 224KB | 2592KB | 600 |
product_catalog | 176KB | 48KB | 96KB | 200 |
image_search_log | 64KB | 8192bytes | 32KB | 0 |
SELECTindexrelname AS index_name,relname AS table_name,pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,idx_scan AS scan_count,idx_tup_read AS rows_read,idx_tup_fetch AS rows_fetchedFROM pg_stat_user_indexesWHERE schemaname = 'public'AND relname IN ('product_image_search', 'product_catalog', 'image_search_log')ORDER BY pg_relation_size(indexrelid) DESC;
index_name | table_name | index_size | scan_count | rows_read | rows_fetched |
idx_product_image_embedding_cosine | product_image_search | 2408KB | 0 | 0 | 0 |
idx_product_image_metadata | product_image_search | 64KB | 0 | 0 | 0 |
product_image_search_image_id_key | product_image_search | 40KB | 0 | 0 | 0 |
idx_product_image_product_id | product_image_search | 32KB | 0 | 0 | 0 |
product_catalog_product_id_key | product_catalog | 16KB | 600 | 600 | 600 |
SELECTschemaname,relname AS table_name,heap_blks_hit AS buffer_hits,heap_blks_read AS disk_reads,CASEWHEN (heap_blks_hit + heap_blks_read) > 0THEN round(100.0 * heap_blks_hit / (heap_blks_hit + heap_blks_read), 1)ELSE 0END AS hit_ratio_pctFROM pg_statio_user_tablesWHERE relname IN ('product_image_search', 'product_catalog')ORDER BY relname;
schemaname | table_name | buffer_hits | disk_reads | hit_ratio_pct |
public | product_catalog | 1419 | 8 | 99.4% |
public | product_image_search | 1251 | 30 | 97.7% |
SELECTpg_size_pretty(pg_database_size(current_database())) AS total_db_size,(SELECT count(*) FROM product_catalog) AS total_products,(SELECT count(*) FROM product_image_search) AS total_images,(SELECT count(*) FROM image_search_log) AS total_searches;
total_db_size | total_products | total_images | total_searches |
15MB | 200 | 600 | 1Phase Two |
フィードバック