I Tried Out the New S3 Vectors Feature "Metadata Pre-filtering"

I Tried Out the New S3 Vectors Feature "Metadata Pre-filtering"

Tested metadata prefiltering by publication year and string prefix match in Amazon S3 Vectors ENHANCED mode on 60,785 real records, covering QueryVectors filter syntax and how topK behaves differently from CLASSIC mode.
2026.10.02

This page has been translated by machine translation. View original

Introduction

On September 30, 2026, Amazon S3 Vectors added support for metadata pre-filtering.

https://aws.amazon.com/blogs/aws/amazon-s3-vectors-now-supports-metadata-pre-filtering-for-higher-recall-on-filtered-searches/

In this article, I loaded vectors combining the titles and summaries of Classmethod blog posts into S3 Vectors and tried QueryVectors with metadata filters.

I also checked the behavior in ENHANCED mode when the number of matches is below topK, and summarized the differences from the conventional CLASSIC mode.

Data Preparation and Environment Setup

I reused vectors I had generated previously: 60,785 items × 1024 dimensions.

https://dev.classmethod.jp/articles/vector-db-less-semantic-search/

https://dev.classmethod.jp/articles/dynamodb-realtime-vector-search/

I created the bucket and index in the Tokyo region (ap-northeast-1) without specifying an index mode.

aws s3vectors create-vector-bucket \
  --vector-bucket-name <バケット名>

aws s3vectors create-index \
  --vector-bucket-name <バケット名> \
  --index-name articles \
  --data-type float32 \
  --dimension 1024 \
  --distance-metric cosine

aws s3vectors get-index \
  --vector-bucket-name <バケット名> \
  --index-name articles

Here is the result of get-index (ARN and other details omitted). By default, indexMode is ENHANCED.

{
    "index": {
        "vectorBucketName": "<バケット名>",
        "indexName": "articles",
        "creationTime": "2026-10-02T01:27:42+09:00",
        "dataType": "float32",
        "dimension": 1024,
        "distanceMetric": "cosine",
        "indexMode": "ENHANCED"
    }
}

I loaded the data by calling the AWS CLI put-vectors from a Python script in batches of 500, and all 60,785 items succeeded. The metadata consists of four fields: language, published_year, slug, and title. published_year is stored as a number because I use $gte later.

# Add metadata only if meta exists
if meta:
    metadata = {
        "language": meta.get("language", ""),
        "published_year": extract_year(meta.get("published_at", "")),  # "2024-01-15" → 2024
        "slug": meta.get("slug", ""),
        "title": meta.get("title", ""),
    }
else:
    metadata = {}

item = {
    "key": article_id,
    "data": {"float32": vec},
}
if metadata:
    item["metadata"] = metadata

Metadata Filters in ENHANCED Mode

According to the AWS documentation, an ENHANCED index evaluates the filter first and then performs the similarity search only on the vectors that match the conditions.

https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-metadata-filtering.html

For the query vector, I used the vector of a Cognito-related article (2024) in the index. Because the article used for the query is itself included in the index, without a filter it is returned in first place with a distance of 0.0000. topK is 10 in every case.

The filter is passed as JSON to --filter. I ran each pattern by swapping only --filter in the following command.

aws s3vectors query-vectors \
  --vector-bucket-name <バケット名> \
  --index-name articles \
  --query-vector '{"float32": [<クエリベクトル>]}' \
  --top-k 10 \
  --filter '{"published_year": {"$eq": 2026}}' \
  --return-distance \
  --return-metadata
Filter Results returned Years of returned articles Distance range
None 10 2019, 2023, 2024, 2025, 2026 0.0000–0.1388
{"published_year": {"$eq": 2026}} 10 2026 only 0.1287–0.1843
{"published_year": {"$eq": 2024}} 10 2024 only 0.0000–0.1610
{"published_year": {"$gte": 2024}} 10 2024, 2025, 2026 0.0000–0.1523

Even the smallest match count, published_year == 2026, has 4,478 items (out of 60,785), which is well above topK=10 for every pattern. The first result for published_year == 2026 (distance 0.1287) is an article from 2026 that ranked 6th without the filter.

Checking the Behavior of the $startsWith Operator

$startsWith is a prefix-match operator for strings and can be used with ENHANCED indexes (its handling on CLASSIC indexes is covered in the next section). I tried three patterns: an English slug, a Japanese title, and a title combined with the year via $and.

{"slug": {"$startsWith": "enable-"}}
{"title": {"$startsWith": "[アップデート]"}}
{
  "$and": [
    {"published_year": {"$gte": 2025}},
    {"title": {"$startsWith": "[アップデート]"}}
  ]
}
Filter Results returned Distance range
slug starts with enable- 10 0.0000–0.2630
title starts with [アップデート] 10 0.0000–0.1735
2025 or later, and title starts with [アップデート] 10 0.1287–0.1952

In every pattern, all 10 returned results satisfied the condition. The title results included both titles with a space after the closing bracket, such as [アップデート] Amazon Cognito …, and titles without a space, such as [アップデート]AWS SSOで…. The comparison is made directly against the beginning of the string, not on word boundaries. With $and, articles from 2024 and earlier that ranked high under the title-only condition were excluded, and only articles from 2025 and 2026 were returned.

Differences Between ENHANCED and CLASSIC

When I specified CLASSIC with update-index-mode on this bucket (created on October 2, 2026), the following error was returned.

ValidationException: indexMode cannot be set to CLASSIC because the vector bucket does not support CLASSIC mode

Buckets created on or after September 30, 2026 are fixed to ENHANCED. Buckets created before that default to CLASSIC, can be changed to ENHANCED with update-index-mode, and can also be changed back to CLASSIC.

Since I can't try CLASSIC behavior myself, I summarize the differences based on the documentation and the announcement blog.

Item ENHANCED CLASSIC
Filter evaluation Evaluates the filter first and searches only the matching vectors Evaluates the filter condition at the same time as it explores candidate vectors
Number of results The smaller of the match count and topK If there are few matches, fewer than topK results may be returned
$startsWith Available Available only when queryMode=ENHANCED is added to the query

Behavior When the Match Count Is Below topK

I verified with real data how ENHANCED behaves when the number of matches is below topK. I used a slug prefix that exists in only 3 items across the whole index and queried with topK set greater than 3.

# Only 3 slugs in the whole index start with 'langfuse-'
aws s3vectors query-vectors \
  --vector-bucket-name <バケット名> \
  --index-name articles \
  --query-vector '{"float32": [<クエリベクトル>]}' \
  --top-k 10 \
  --filter '{"slug": {"$startsWith": "langfuse-"}}' \
  --return-distance \
  --return-metadata
Filter Total matches topK specified Results returned
slug $startsWith 'langfuse-' 3 10 3
slug $startsWith 'langfuse-' 3 50 3
slug $startsWith 'asr-v3' 3 10 3
published_year == 2024 6,628 10 10

Even if topK is set to 10 or 50, only 3 results are returned when there are 3 matches. Because ENHANCED evaluates the filter first, the number of results equals the smaller of "the number of matches across the whole index" and "topK".

In CLASSIC, the top topK vectors by vector proximity are retrieved first, and then the filter is checked. In this case, even if there are 3 articles in the whole index whose slug starts with langfuse-, they will not be returned if those 3 are not among the topK items retrieved by the initial vector search.

Summary

Because CLASSIC mode evaluates the filter at the same time as it explores candidate vectors, strongly restrictive conditions could return fewer than topK results, and it was necessary to work around this by taking a larger topK and narrowing the results again on the application side. In ENHANCED mode, the filter is evaluated first, so vectors that pass the conditions can be retrieved in order of similarity.

If there is a workload where you gave up on S3 Vectors because a CLASSIC index did not return the search results you intended, please try ENHANCED.

Note that buckets created on or after September 30, 2026 are ENHANCED only. This applies not only to new deployments but also when you recreate a bucket for reasons such as schema changes or region migration. Please keep in mind that implementations that assume CLASSIC behavior may need to be reviewed.

Share this article