向量检索实战:商品语义相似搜索

更新时间:
复制为 MD 格式

本文以商品语义相似搜索为例,演示如何在PolarDB PostgreSQL DynamoDB兼容模式中,结合 PGVector 向量插件和 HNSW 近似索引,在数据库引擎层面完成 Top-K 最近邻检索,实现商品的语义相似推荐。

示例场景

假设业务中已有一张名为 itemtable 的 DynamoDB 表,用于存储商品信息。每条记录的业务文档(polardb_document)结构如下:

{
  "item_id": {"S": "item_1001"},
  "title": {"S": "入耳式降噪无线耳机"},
  "description": {"S": "支持主动降噪的入耳式蓝牙耳机,适合通勤和日常使用"},
  "price": {"N": "79.9"}
}
  • item_id:商品 ID(DynamoDB 分区键)。

  • title / description / price:商品基础信息和中文描述。

业务需求

  • 当用户浏览某个商品详情页时,实时返回语义上最相似的若干个商品("看了又看"或"相似推荐")。

  • 当用户输入一句中文商品描述或搜索词(例如"适合通勤的降噪无线耳机"),返回向量距离最近的前 K 个商品,而非简单的关键字匹配。

传统 DynamoDB 的局限

DynamoDB 不支持向量数据类型和相似度检索算子,无法在服务端对 Embedding 做相似度排序。同时缺少 HNSW、IVFFlat 等近似索引能力,数据量大时扫表加客户端逐一计算向量距离难以满足实时推荐场景的延迟要求。

在 AWS 原生方案中,通常需要将商品元数据存储在 DynamoDB,将向量写入 OpenSearch 或 Aurora PostgreSQL 等独立的向量存储,查询时先在向量存储中执行 Top-K 搜索获取商品 ID,再通过 DynamoDB BatchGetItem 回表补齐完整商品信息,架构复杂度较高。

PolarDB DynamoDB兼容模式的优势

在保留 DynamoDB API 写入和简单读能力的前提下,可以:

  1. 在底层 PostgreSQL 表(例如 dynamodb.itemtable)通过生成列提取向量字段为 VECTOR 类型。

  2. 基于该向量列创建 HNSW 近似索引

  3. 使用 SQL 的 <-> 等运算符在数据库内直接完成 Top-K 最近邻检索。

步骤一:创建表并预置数据

以下 Python 代码通过 DynamoDB API 创建 itemtable 表并插入 5 条测试数据。连接配置中的 ENDPOINT_URL 需替换为 DynamoDB 兼容访问地址,具体获取方式参见。

#!/usr/bin/env python3
"""
创建 itemtable 并插入测试数据
"""

import boto3
from botocore.exceptions import ClientError

# DynamoDB 连接配置
ENDPOINT_URL = "<your-endpoint_url>"
REGION = "<your-region>"
ACCESS_KEY = "<your-access-key>"
SECRET_KEY = "<your-secret-key>"
TABLE_NAME = "itemtable"

def create_dynamodb_client():
    return boto3.client(
        'dynamodb',
        endpoint_url=ENDPOINT_URL,
        region_name=REGION,
        aws_access_key_id=ACCESS_KEY,
        aws_secret_access_key=SECRET_KEY
    )

def create_table(client):
    try:
        client.create_table(
            TableName=TABLE_NAME,
            KeySchema=[{'AttributeName': 'item_id', 'KeyType': 'HASH'}],
            AttributeDefinitions=[{'AttributeName': 'item_id', 'AttributeType': 'S'}],
            BillingMode='PAY_PER_REQUEST'
        )
        waiter = client.get_waiter('table_exists')
        waiter.wait(TableName=TABLE_NAME, WaiterConfig={'Delay': 2, 'MaxAttempts': 60})
        print(f"表 {TABLE_NAME} 创建成功")
    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceInUseException':
            print(f"表 {TABLE_NAME} 已存在,跳过创建")
        else:
            raise

def insert_test_data(client):
    items = [
        {'item_id': 'item_1001', 'title': '入耳式降噪无线耳机',
         'description': '支持主动降噪的入耳式蓝牙耳机,适合通勤和日常使用', 'price': 79.9},
        {'item_id': 'item_1002', 'title': '头戴式无线耳机',
         'description': '包耳式头戴设计的蓝牙无线耳机,适合居家与办公场景,强调佩戴舒适度', 'price': 129.0},
        {'item_id': 'item_1003', 'title': '头戴式有线耳机',
         'description': '通过 3.5mm 音频线连接的头戴式耳机,更适合桌面或录音等有线场景', 'price': 109.0},
        {'item_id': 'item_1004', 'title': 'USB 电容麦克风',
         'description': '通过 USB 直连电脑的电容麦克风,适合直播和语音会议', 'price': 89.0},
        {'item_id': 'item_1005', 'title': '便携式数码相机',
         'description': '轻便小型数码相机,适合日常出游拍照,支持光学变焦和防抖功能', 'price': 139.0},
    ]
    for idx, d in enumerate(items, 1):
        client.put_item(TableName=TABLE_NAME, Item={
            'item_id': {'S': d['item_id']},
            'title': {'S': d['title']},
            'description': {'S': d['description']},
            'price': {'N': str(d['price'])},
        })
        print(f"插入数据 {idx}: {d['item_id']}, {d['title']}")

def main():
    client = create_dynamodb_client()
    create_table(client)
    insert_test_data(client)
    resp = client.scan(TableName=TABLE_NAME)
    print(f"验证完成,共 {len(resp.get('Items', []))} 条数据")

if __name__ == "__main__":
    main()

数据插入完成后,通过 psql 或阿里云 DMS 控制台连接 PolarDB,继续后续步骤。

步骤二:在 PolarDB 中创建 HNSW 近似索引

创建向量与模型扩展

使用高权限账号连接 polardb_internal_dynamodb 数据库,创建所需的扩展:

CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS polar_ai;
说明

polar_ai 扩展用于通过内置或自定义模型将文本转化为向量。如果已经在业务侧或离线任务中生成好了向量(例如通过外部 Embedding 服务),也可以直接将向量结果作为文档的一个属性写入 DynamoDB 表,再通过生成列解析该字段并创建向量索引。两种方式可根据现有架构自由选择。

查看底层表结构

所有 DynamoDB 表存储在 polardb_internal_dynamodb 数据库中,Schema 名称与 DynamoDB 账号名一致。以账号名 dynamodb 为例,连接数据库后执行以下 SQL 查看表数据:

SELECT * FROM dynamodb.itemtable LIMIT 5;

执行结果

image.png

为商品描述生成向量列

在创建向量索引前,需要基于商品的中文描述字段,通过 polar_ai.ai_text_embedding 函数生成向量,并以持久化生成列的方式存储在表中。以下示例使用内置的 text_embedding_v2 模型,向量维度为 1536:

-- 绑定模型 Token(仅需执行一次)
SELECT polar_ai.AI_SetModelToken('_dashscope/text_embedding/text_embedding_v2', '<YOUR_API_KEY>');

-- 使用生成列方式定义向量列
ALTER TABLE dynamodb.itemtable
    ADD COLUMN embedding_vec VECTOR(1536) GENERATED ALWAYS AS
        (polar_ai.ai_text_embedding(polardb_document -> 'description' ->> 'S')) STORED;
说明

示例中使用 VECTOR(1536) 匹配模型默认的输出维度。实际业务中可根据所选 Embedding 模型的输出维度进行调整,例如 384、768 或 3072 等。

创建 HNSW 近似索引

为了在大规模商品数据上实现低延迟的相似搜索,在 embedding_vec 上创建 HNSW 索引:

CREATE INDEX idx_item_embedding_hnsw
ON dynamodb.itemtable
USING hnsw (embedding_vec vector_l2_ops)
WITH (m = 16, ef_construction = 64);
  • vector_l2_ops:使用欧几里得距离(L2)作为相似度度量。

  • m:每个节点的最大连接数,默认 16;增大可提升召回率但会增加索引构建时间和内存占用。

  • ef_construction:索引构建时的候选列表大小,越大召回率越高,构建时间越长。

更多参数说明参见中对 HNSW 的详细说明。

可通过 pg_indexes 视图确认索引已创建:

SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'dynamodb'
  AND tablename  = 'itemtable';

执行结果:

image.png

步骤三:执行 Top-K 语义相似搜索

假设用户正在浏览 item_1001(入耳式降噪无线耳机),需要基于其中文描述找到语义相似的其他 2 个商品。使用如下 SQL 进行查询:

SET hnsw.ef_search = 100;

WITH target_item AS (
    SELECT embedding_vec
    FROM dynamodb.itemtable
    WHERE polardb_document -> 'item_id' ->> 'S' = 'item_1001'
)
SELECT
    polardb_document -> 'item_id'  ->> 'S' AS item_id,
    polardb_document -> 'title'    ->> 'S' AS title,
    embedding_vec <-> (SELECT embedding_vec FROM target_item) AS distance
FROM dynamodb.itemtable
WHERE polardb_document -> 'item_id' ->> 'S' <> 'item_1001'
ORDER BY distance
LIMIT 2;

查询逻辑说明:

  • CTE target_item 获取目标商品中文描述对应的向量(由 polar_ai.ai_text_embedding 基于 description 字段自动生成)。

  • 主查询排除目标商品自身,使用 <-> 运算符计算 L2 距离,按距离升序返回最近的 2 个商品。

  • hnsw.ef_search 控制查询时的候选列表大小,增大可提升召回率。

以本文的测试数据为例,距离"入耳式降噪无线耳机"语义最近的两个商品应为"头戴式无线耳机"和"头戴式有线耳机",均属于耳机品类。

执行结果:

image.png

总结

PolarDB PostgreSQL DynamoDB兼容模式结合 PGVector 向量能力与 polar_ai.ai_text_embedding 内置模型,可以在不改变 DynamoDB API 写入方式的前提下,在底层 PostgreSQL 表上基于商品中文描述动态生成向量列,基于该列创建 HNSW/IVFFlat 等近似索引,直接使用 SQL 完成商品相似推荐和语义搜索场景。关于 PGVector 的更多能力,参见。