404 Not Found

404 Not Found


nginx

PostgreSQL Extension Ecosystem, Foreign Data Wrappers, and Vector Search

1. What You'll Learn


2. The Story

Alice's e-commerce company needs to do three things: (1) integrate AI product recommendations, requiring vector search; (2) migrate legacy MySQL data into PG, requiring cross-database queries; (3) speed up slow fuzzy search, requiring pg_trgm. She found PostgreSQL's extension ecosystem solves it all in one place—pgvector stores product embeddings, postgres_fdw connects to MySQL for the migration, and pg_trgm boosts fuzzy search performance. One database replaces Elasticsearch + middleware + a self-built recommendation service.


3. Concept: Extension Mechanism Overview

(1) What Is a PostgreSQL Extension

An extension is PostgreSQL's plugin system—it packages related SQL objects (functions, types, operators, index methods) as a single unit that can be installed and uninstalled with one command.

SQL
-- List available extensions
SELECT name, default_version, installed_version, comment
FROM pg_available_extensions
ORDER BY name;

(2) Extension Management Commands

Command Purpose
CREATE EXTENSION ext_name Install an extension
CREATE EXTENSION IF NOT EXISTS ext_name Idempotent install
CREATE EXTENSION ext_name VERSION '1.2' Install a specific version
DROP EXTENSION ext_name Uninstall an extension (CASCADE to keep config)
ALTER EXTENSION ext_name UPDATE TO '2.0' Upgrade an extension
\dx (psql) List installed extensions

(3) Prerequisites for Installing Extensions

Condition Description
Shared library file .so / .dll must be in shared_preload_libraries or dynamic_library_path
Control file extension_name.control in SHAREDIR/extension/
SQL script extension_name--version.sql defines the objects
Privileges Need CREATE on current database + superuser (for some extensions)

4. Operation: Common Extensions

▶ Example: uuid-ossp Generates UUIDs

SQL
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Generate UUID v4 (random)
SELECT uuid_generate_v4();

-- Generate UUID v1 (time-based)
SELECT uuid_generate_v1();

-- Use as default column value
CREATE TABLE api_keys (
    id          UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_id     BIGINT NOT NULL,
    key_name    TEXT,
    created_at  TIMESTAMPTZ DEFAULT now()
);

Output:

TEXT
CREATE TABLE

▶ Example: pgcrypto Data Encryption

SQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- Hash password with salt
SELECT crypt('MySecret123', gen_salt('bf'));

-- Verify password
SELECT crypt('MySecret123', stored_hash) = stored_hash AS is_match;

-- AES encryption
SELECT encode(encrypt('credit card data'::bytea,
    'secret_key_16bytes'::bytea, 'aes'), 'hex');

-- Generate random token
SELECT encode(gen_random_bytes(32), 'hex');

Output:

TEXT
CREATE TABLE
SQL
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- Show similarity score
SELECT similarity('PostgreSQL', 'Postgres');

-- GIN trigram index for fast fuzzy search
CREATE INDEX idx_products_name_trgm ON products
    USING GIN (name gin_trgm_ops);

-- Fuzzy search with threshold
SELECT name, similarity(name, 'iphon') AS score
FROM products
WHERE name % 'iphon'
ORDER BY score DESC;
TEXT
     name      | score
---------------+-------
 iPhone 15 Pro |  0.42
 iPhone 14     |  0.38
(2 rows)

▶ Example: pg_stat_statements Slow-Query Statistics

SQL
-- Must be in shared_preload_libraries first
-- postgresql.conf: shared_preload_libraries = 'pg_stat_statements'

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Top 10 queries by total execution time
SELECT query,
       calls,
       round(total_exec_time::numeric, 2) AS total_ms,
       round(mean_exec_time::numeric, 2)  AS avg_ms,
       rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

-- Reset statistics
SELECT pg_stat_statements_reset();

Output:

TEXT
  result  
----------
   42.50
(1 row)

▶ Example: PostGIS Spatial Data Introduction

SQL
CREATE EXTENSION IF NOT EXISTS postgis;

-- Store point geometry
CREATE TABLE stores (
    id       SERIAL PRIMARY KEY,
    name     TEXT,
    location GEOMETRY(POINT, 4326)
);

-- Insert coordinates (longitude, latitude)
INSERT INTO stores (name, location)
VALUES ('Dubai Mall', ST_SetSRID(ST_MakePoint(55.2796, 25.1972), 4326));

-- Find stores within 5 km
SELECT name,
    ST_Distance(location::geography,
        ST_SetSRID(ST_MakePoint(55.2700, 25.2000), 4326)::geography
    ) AS distance_m
FROM stores
WHERE ST_DWithin(location::geography,
    ST_SetSRID(ST_MakePoint(55.2700, 25.2000), 4326)::geography, 5000);

Output:

TEXT
INSERT 0 1

5. Concept: Foreign Data Wrappers

(1) FDW Architecture

100%
flowchart LR
    A["Local PG<br/>postgres_fdw"] -->|"CREATE SERVER"| B["Remote PG<br/>(or MySQL/Oracle)"]
    A -->|"CREATE SERVER"| C["file_fdw<br/>(CSV/Log files)"]
    A -->|"CREATE SERVER"| D["Other FDW<br/>(Redis/MongoDB...)"]
    B --> E["IMPORT FOREIGN SCHEMA"]
    C --> F["CREATE FOREIGN TABLE"]
    D --> G["Cross-system JOIN"]
FDW name Target data source Use case
postgres_fdw Remote PostgreSQL Cross-database queries, data migration
mysql_fdw Remote MySQL MySQL→PG migration
file_fdw Local CSV files Log analysis, data import
redis_fdw Redis Cache queries
mongo_fdw MongoDB Document queries

(2) FDW Configuration Steps

Step Command
1. Install extension CREATE EXTENSION postgres_fdw
2. Create SERVER CREATE SERVER remote FOREIGN DATA WRAPPER postgres_fdw OPTIONS (...)
3. Create USER MAPPING CREATE USER MAPPING FOR local_user SERVER remote OPTIONS (...)
4. Create FOREIGN TABLE CREATE FOREIGN TABLE ft_xxx SERVER remote OPTIONS (...)
5. Or import whole SCHEMA IMPORT FOREIGN SCHEMA public LIMIT TO (orders) FROM SERVER remote INTO remote_schema

6. Operation: postgres_fdw Cross-Database Query

▶ Example: Configure a Remote PostgreSQL Connection

SQL
-- Step 1: Install extension
CREATE EXTENSION IF NOT EXISTS postgres_fdw;

-- Step 2: Create server connection
CREATE SERVER legacy_db
    FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '192.168.1.100', port '5432', dbname 'legacy');

-- Step 3: Map local user to remote credentials
CREATE USER MAPPING FOR current_user
    SERVER legacy_db
    OPTIONS (user 'admin', password 'secret123');

-- Step 4: Import foreign schema
IMPORT FOREIGN SCHEMA public
    LIMIT TO (users, products, orders)
    FROM SERVER legacy_db
    INTO legacy_schema;

-- Now query remote tables as if local
SELECT u.name, COUNT(o.id) AS order_count
FROM legacy_schema.users u
JOIN legacy_schema.orders o ON u.id = o.user_id
GROUP BY u.name;

Output:

TEXT
 count 
-------
     5
(1 row)

▶ Example: Cross-Database JOIN Between Local and Remote

SQL
-- Local: new PG product catalog
-- Remote: legacy MySQL order data via postgres_fdw

SELECT p.name,
       SUM(loi.quantity) AS total_sold,
       SUM(loi.quantity * loi.unit_price) AS revenue
FROM products p
JOIN legacy_schema.order_items loi ON p.id = loi.product_id
WHERE p.category = 'Electronics'
GROUP BY p.name
ORDER BY revenue DESC;

Output:

TEXT
  result  
----------
   42.50
(1 row)

▶ Example: file_fdw Reads External CSV

SQL
CREATE EXTENSION IF NOT EXISTS file_fdw;

CREATE SERVER csv_server
    FOREIGN DATA WRAPPER file_fdw;

CREATE FOREIGN TABLE access_logs_csv (
    ip_address    TEXT,
    request_time  TIMESTAMP,
    method        TEXT,
    path          TEXT,
    status_code   INT,
    response_time NUMERIC
) SERVER csv_server
OPTIONS (filename '/var/log/nginx/access.csv', format 'csv', header 'true');

-- Analyze nginx logs with SQL
SELECT path,
       COUNT(*) AS hits,
       AVG(response_time) AS avg_ms
FROM access_logs_csv
WHERE status_code = 200
GROUP BY path
ORDER BY hits DESC
LIMIT 20;

Output:

TEXT
 count 
-------
     5
(1 row)

▶ Example: FDW Data Migration in Practice

SQL
-- Migrate users from legacy to local
INSERT INTO users (name, email, created_at)
SELECT name, email, created_at
FROM legacy_schema.users
WHERE id NOT IN (SELECT legacy_id FROM users);

-- Use dblink for incremental sync
CREATE EXTENSION IF NOT EXISTS dblink;

SELECT dblink_connect('legacy', 'host=192.168.1.100 dbname=legacy user=admin password=secret123');

SELECT * FROM dblink('legacy',
    'SELECT id, name, email FROM users WHERE created_at > now() - interval ''1 day'''
) AS t(id BIGINT, name TEXT, email TEXT);

Output:

TEXT
INSERT 0 1

(1) How Vector Search Works

AI models encode text/images into high-dimensional vectors (embeddings), and the distance between vectors represents semantic similarity. pgvector lets PostgreSQL store and retrieve vectors natively.

Distance metric Formula Use case
L2 distance (<=>) Euclidean distance Spatial distance
Inner product (<#>) Dot product Normalized vectors
Cosine distance (<=>) 1 - cos(θ) Semantic similarity

(2) pgvector Index Selection

100%
flowchart TD
    A["Vector column created"] --> B{"Rows < 10K?"}
    B -->|Yes| C["Exact search<br/>(no index needed)"]
    B -->|No| D{"Recall requirement?"}
    D -->|"High recall (> 99%)"| E["IVFFlat<br/>(probes=lists)"]
    D -->|"Fast + good recall"| F["HNSW<br/>(ef_search tuning)"]
    E --> G["CREATE INDEX ... USING ivfflat<br/>(vector_cosine_ops)"]
    F --> H["CREATE INDEX ... USING hnsw<br/>(vector_cosine_ops)"]
Index Build speed Query speed Recall Best for
No index (brute force) N/A Slow 100% < 10K
IVFFlat Medium Fast 95-99% 10K-1M
HNSW Slow Fastest 97-99.5% 100K-10M+

8. Operation: pgvector in Practice

▶ Example: Install pgvector and Create a Vector Column

SQL
-- Install pgvector
CREATE EXTENSION IF NOT EXISTS vector;

-- Product table with embedding vector (1536 dims for OpenAI)
CREATE TABLE products_vec (
    id          SERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    category    TEXT,
    price       NUMERIC(10,2),
    embedding   vector(1536)
);

Output:

TEXT
CREATE TABLE

▶ Example: Insert and Search by Cosine Similarity

SQL
-- Insert product with embedding
INSERT INTO products_vec (name, category, price, embedding)
VALUES ('Wireless Headphones', 'Electronics', 79.99,
    '[0.012, -0.034, 0.056, ...]'::vector);

-- Find top 5 similar products by cosine distance
SELECT p.id, p.name, p.category, p.price,
       p.embedding <=> '[0.015, -0.030, 0.050, ...]'::vector AS distance
FROM products_vec p
ORDER BY p.embedding <=> '[0.015, -0.030, 0.050, ...]'::vector
LIMIT 5;

Output:

TEXT
INSERT 0 1

▶ Example: Create an HNSW Index

SQL
-- HNSW index for fast approximate search
CREATE INDEX idx_products_vec_hnsw ON products_vec
    USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 64);

-- Tune search accuracy vs speed
SET hnsw.ef_search = 100;

-- Query with index (much faster on large datasets)
SELECT name,
       embedding <=> '[0.015, -0.030, 0.050, ...]'::vector AS distance
FROM products_vec
ORDER BY embedding <=> '[0.015, -0.030, 0.050, ...]'::vector
LIMIT 10;

Output:

TEXT
CREATE TABLE

▶ Example: IVFFlat Index and Tuning

SQL
-- IVFFlat index (build after data loaded for best centroids)
CREATE INDEX idx_products_vec_ivf ON products_vec
    USING ivfflat (embedding vector_cosine_ops)
    WITH (lists = 100);

-- Increase probes for higher recall
SET ivfflat.probes = 10;

SELECT name, embedding <=> '[0.015, -0.030, 0.050, ...]'::vector AS dist
FROM products_vec
ORDER BY embedding <=> '[0.015, -0.030, 0.050, ...]'::vector
LIMIT 5;

Output:

TEXT
CREATE TABLE

▶ Example: Full AI Product Recommendation Flow

SQL
-- Store product embeddings from AI model
INSERT INTO products_vec (name, category, price, embedding)
VALUES
    ('Running Shoes',     'Sports',   129.99, array_to_vec(ARRAY[0.1,0.2,0.3])::vector),
    ('Yoga Mat',          'Sports',    39.99, array_to_vec(ARRAY[0.11,0.19,0.31])::vector),
    ('Bluetooth Speaker', 'Electronics', 49.99, array_to_vec(ARRAY[0.5,0.1,0.2])::vector);

-- User viewed "Running Shoes", recommend similar items
WITH target AS (
    SELECT embedding FROM products_vec WHERE name = 'Running Shoes'
)
SELECT p.name, p.category, p.price,
       p.embedding <=> (SELECT embedding FROM target) AS similarity
FROM products_vec p
WHERE p.name != 'Running Shoes'
ORDER BY similarity
LIMIT 3;

Output:

TEXT
INSERT 0 1

9. Operation: Extension Management Best Practices

(1) Version Management

SQL
-- Check current extension version
SELECT extname, extversion FROM pg_extension ORDER BY extname;

-- Upgrade extension
ALTER EXTENSION pgvector UPDATE TO '0.7.0';

-- Upgrade all extensions
SELECT extname,
       installed_version,
       default_version
FROM pg_available_extensions
WHERE installed_version IS NOT NULL
  AND installed_version != default_version;

(2) Privilege Management

Scenario Recommended practice
Install extension in production superuser runs CREATE EXTENSION
Ordinary user usage GRANT USAGE ON SCHEMA / function execute privilege
FDW connection USER MAPPING stores credentials, don't hardcode passwords
Extension upgrade Test in staging first, then run in production

(3) Production Audit Checklist

Check item Description
shared_preload_libraries pg_stat_statements/pgvector need preloading
Extension source Use only official or trusted third-party
Version lock Record production extension versions, avoid auto-upgrade
Security audit pgcrypto key management, FDW credential protection
Uninstall test Confirm DROP EXTENSION impact scope

10. Comprehensive Example

SQL
-- Full setup: extensions + FDW + pgvector for e-commerce AI

-- Step 1: Install core extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pgvector;

-- Step 2: Product catalog with vector search
CREATE TABLE products_ai (
    id          UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    name        TEXT NOT NULL,
    category    TEXT,
    price       NUMERIC(10,2),
    attrs       JSONB DEFAULT '{}',
    embedding   vector(1536)
);

-- Step 3: GIN index for fuzzy name search
CREATE INDEX idx_products_name_trgm ON products_ai
    USING GIN (name gin_trgm_ops);

-- Step 4: HNSW index for vector similarity
CREATE INDEX idx_products_embedding ON products_ai
    USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 64);

-- Step 5: Connect legacy database via FDW
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE SERVER legacy_db FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '192.168.1.50', port '5432', dbname 'legacy_shop');
CREATE USER MAPPING FOR current_user SERVER legacy_db
    OPTIONS (user 'migrate_user', password 'secure_pass');
IMPORT FOREIGN SCHEMA public LIMIT TO (old_products, old_users)
    FROM SERVER legacy_db INTO legacy;

-- Step 6: Migrate and transform data
INSERT INTO products_ai (name, category, price, attrs)
SELECT name, category, price,
       jsonb_build_object('weight_kg', weight, 'color', color)
FROM legacy.old_products
WHERE active = true;

-- Step 7: Combined fuzzy + vector search
SELECT p.name, p.price,
       similarity(p.name, 'wireless earbuds') AS text_score,
       p.embedding <=> '[0.015,-0.030,0.050]'::vector AS vec_dist
FROM products_ai p
WHERE p.name % 'wireless earbuds'
ORDER BY vec_dist ASC
LIMIT 5;

❓ FAQ

Q Does CREATE EXTENSION need superuser?
A Most extensions need superuser because they load a C dynamic library. PG 14+ allows some extensions via GRANT CREATE ON DATABASE + the trusted-extension mechanism.
Q What's the max dimension pgvector's vector column supports?
A pgvector supports up to 16,000 dimensions (0.7.0+), but choose based on model output (OpenAI 1536, Cohere 1024, etc.). Higher dimensions make indexes slower.
Q How does postgres_fdw query perform?
A FDW has network overhead—simple queries add about 2-5ms latency. Setting use_remote_estimate=on lets the remote generate cost estimates and improves plan quality. For bulk migration, prefer COPY over FDW.
Q Which should I choose, HNSW or IVFFlat?
A For < 100K rows, no index needed; for 100K-1M, choose IVFFlat (fast to build); for > 100K with low-latency needs, choose HNSW (fastest query). HNSW builds slowly but queries far faster than IVFFlat.
Q Does the pg_trgm GIN index slow down INSERT?
A Yes. A trigram index splits many tokens; write overhead is about 3-5x that of a normal B-tree. Best for read-heavy, write-light search scenarios.
Q Can FDW write to remote tables?
A postgres_fdw supports INSERT/UPDATE/DELETE on remote tables. file_fdw is read-only. Writing to remote tables carries distributed-transaction risk—keep it to read-only queries or bulk migration.
Q Does an extension upgrade lock tables?
A Generally ALTER EXTENSION UPDATE needs an ACCESS EXCLUSIVE lock; duration depends on extension contents. Run it in off-peak hours and validate in staging first.

📖 Summary


📝 Exercises

  1. ⭐ Install the uuid-ossp and pgcrypto extensions, create an api_tokens table using a UUID as the primary key and crypt() to store the password hash, and write a query that verifies a password.

  2. ⭐⭐ Configure postgres_fdw to connect to a remote PG database (use Docker to simulate one), import the remote products table, and write a cross-database JOIN query: local orders table + remote products table.

  3. ⭐⭐⭐ Design a dual-engine product search: pg_trgm handles fuzzy text search, pgvector handles semantic vector search. Write a combined search function search_products(keyword TEXT, query_vec vector, limit_count INT) that fuses the text score and vector distance for ranking, and test how results differ across weightings.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏