Skip to main content

OpenSearch Serverless

Comprehensive guide to deploying and using OpenSearch Serverless for full-text and vector search on documents.

Overview

OpenSearch Serverless provides a fully managed, serverless search and analytics engine that automatically scales based on your workload. Our implementation uses the SEARCH collection type, which supports both traditional full-text search and modern vector search (k-NN) for semantic search and RAG (Retrieval Augmented Generation) applications.

Key Features

  • Full-text search - Traditional keyword-based search with analyzers and filters
  • Vector search (k-NN) - Semantic similarity search using embeddings
  • Hybrid search - Combine full-text and vector search for best results
  • Serverless - No infrastructure to manage, automatic scaling
  • HIPAA compliant - Encryption at rest/transit, VPC-only access
  • Cost-effective - Pay only for what you use (minimum 4 OCUs)

Use Cases

  • Document search - Search across product documentation, knowledge bases
  • Semantic search - Find similar documents based on meaning, not just keywords
  • RAG applications - Retrieve relevant context for LLM prompts
  • Recommendation systems - Find similar items based on embeddings
  • Anomaly detection - Identify outliers in vector space

Architecture

┌──────────────────────────────────────────────────────────────────┐
│ OpenSearch Serverless Architecture │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Developer Laptop │
│ │ │
│ │ aws ssm start-session (port forward) │
│ v │
│ ┌─────────────────┐ │
│ │ Bastion Host │ │
│ │ (Private) │ │
│ └────────┬────────┘ │
│ │ │
│ │ HTTPS (443) │
│ v │
│ ┌─────────────────────────┐ ┌──────────────────┐ │
│ │ VPC Endpoint │───────►│ Security │ │
│ │ (Private Subnets) │ │ Policies │ │
│ │ │ │ - Encryption │ │
│ │ - No public IP │ │ - Network │ │
│ │ - Security group (443) │ │ - Data Access │ │
│ └────────┬────────────────┘ └──────────────────┘ │
│ │ │
│ │ HTTPS + AWS SigV4 │
│ v │
│ ┌─────────────────────────┐ ┌──────────────────┐ │
│ │ OpenSearch Serverless │───────►│ KMS Encryption │ │
│ │ Collection │ │ (AWS-owned) │ │
│ │ │ └──────────────────┘ │
│ │ - Type: SEARCH │ │
│ │ - Full-text + Vector │ ┌──────────────────┐ │
│ │ - Auto-scaling (4+ OCUs)│──────►│ CloudWatch │ │
│ │ - Standby: DISABLED │ │ Metrics & Logs │ │
│ └─────────────────────────┘ └──────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘

Prerequisites

Before deploying OpenSearch Serverless:

  1. VPC Module v1.1.0+ - With OpenSearch Serverless VPC endpoint enabled
  2. Bastion Host - For local development access (deployed via Slack bot)
  3. IAM Roles - For application access to the collection
  4. AWS CLI - With Session Manager plugin for bastion access

Deployment

Step 1: Enable VPC Endpoint

Update your VPC configuration in infrastructure-live/_envcommon/vpc.hcl:

inputs = {
# ... existing VPC config ...

# Enable OpenSearch Serverless VPC endpoint
enable_opensearch_serverless_endpoint = true
opensearch_serverless_endpoint_name = "docustack-${local.environment}-opensearch"
}

Deploy the VPC update:

cd infrastructure-live/dev/us-east-1/vpc
terragrunt plan
terragrunt apply

Step 2: Create OpenSearch Serverless Collection

Create infrastructure-live/_envcommon/opensearch-serverless.hcl:

terraform {
source = "git::git@github.com:docustackapp/docustack-infrastructure-modules.git//modules/opensearch-serverless?ref=v1.0.0"
}

include "root" {
path = find_in_parent_folders()
}

include "env" {
path = "${get_terragrunt_dir()}/../../_env/env.hcl"
}

dependency "vpc" {
config_path = "../vpc"

mock_outputs = {
opensearch_serverless_vpc_endpoint_id = "vpce-mock"
}
}

inputs = {
name_prefix = "docustack"
environment = local.environment

# Network configuration
vpc_endpoint_id = dependency.vpc.outputs.opensearch_serverless_vpc_endpoint_id

# Collection configuration
collection_type = "SEARCH" # Supports full-text + vector search
description = "Document search and vector embeddings for ${local.environment}"
standby_replicas = local.environment == "prod" ? "ENABLED" : "DISABLED"

# Data access - grant permissions to application roles
data_access_principals = [
"arn:aws:iam::${local.account_id}:role/docustack-${local.environment}-app-role",
"arn:aws:iam::${local.account_id}:role/docustack-${local.environment}-lambda-role"
]

common_tags = local.common_tags
}

Create environment-specific override in infrastructure-live/dev/us-east-1/opensearch-serverless/terragrunt.hcl:

include "root" {
path = find_in_parent_folders()
}

include "envcommon" {
path = "${dirname(find_in_parent_folders())}/_envcommon/opensearch-serverless.hcl"
}

# Dev-specific overrides (if any)
inputs = {}

Deploy the collection:

cd infrastructure-live/dev/us-east-1/opensearch-serverless
terragrunt plan
terragrunt apply

Note: Collection creation takes 5-10 minutes. Wait for status to become ACTIVE.

Step 3: Verify Deployment

Check collection status:

# Get collection endpoint
terragrunt output collection_endpoint

# Check collection status
aws opensearchserverless batch-get-collection \
--names docustack-dev-search

Accessing OpenSearch Serverless

Local Development Access via Bastion

Since OpenSearch Serverless is only accessible via VPC endpoint (no public access), you need to use the bastion host for local development.

Step 1: Request Bastion Host

Via Slack:

/bastion create

The bastion will auto-terminate after 3 hours.

Step 2: Get Connection Details

# Get bastion instance ID
cd infrastructure-live/dev/us-east-1/bastion-orchestrator
INSTANCE_ID=$(terragrunt output -raw bastion_instance_id)

# Get OpenSearch endpoint
cd ../opensearch-serverless
OPENSEARCH_ENDPOINT=$(terragrunt output -raw collection_endpoint)

echo "Instance ID: $INSTANCE_ID"
echo "OpenSearch Endpoint: $OPENSEARCH_ENDPOINT"

Step 3: Start Port Forwarding

Forward local port 9200 to OpenSearch Serverless:

aws ssm start-session \
--target $INSTANCE_ID \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters "{\"host\":[\"$OPENSEARCH_ENDPOINT\"],\"portNumber\":[\"443\"],\"localPortNumber\":[\"9200\"]}"

Keep this terminal open - the port forward is active while the session runs.

Step 4: Access OpenSearch

In a new terminal, test the connection:

# Get cluster health
curl -X GET "https://localhost:9200/_cluster/health" \
--aws-sigv4 "aws:amz:us-east-1:aoss" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
--insecure

# List indices
curl -X GET "https://localhost:9200/_cat/indices" \
--aws-sigv4 "aws:amz:us-east-1:aoss" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
--insecure

Python Access

from opensearchpy import OpenSearch, RequestsHttpConnection, AWSV4SignerAuth
import boto3

# Get AWS credentials
credentials = boto3.Session().get_credentials()
auth = AWSV4SignerAuth(credentials, 'us-east-1', 'aoss')

# Connect via port forward
client = OpenSearch(
hosts=[{'host': 'localhost', 'port': 9200}],
http_auth=auth,
use_ssl=True,
verify_certs=False, # Self-signed cert via port forward
connection_class=RequestsHttpConnection,
pool_maxsize=20
)

# Test connection
print(client.info())

# Create index
client.indices.create(
index='documents',
body={
'settings': {
'index': {
'number_of_shards': 2,
'number_of_replicas': 0
}
}
}
)

# Index a document
client.index(
index='documents',
id='1',
body={
'title': 'Getting Started with OpenSearch',
'content': 'OpenSearch is a powerful search engine...',
'timestamp': '2025-01-01T00:00:00Z'
}
)

# Search
results = client.search(
index='documents',
body={
'query': {
'match': {
'content': 'search engine'
}
}
}
)

print(results['hits']['hits'])

OpenSearch Dashboards Access

OpenSearch Serverless collections include OpenSearch Dashboards for visualization:

# Dashboard endpoint is at /_dashboards
# Access via port forward at: https://localhost:9200/_dashboards

Open in browser: https://localhost:9200/_dashboards

Note: You'll see a certificate warning (expected with port forwarding). Click "Advanced" → "Proceed to localhost".

Security Policies

OpenSearch Serverless requires three types of security policies:

1. Encryption Policy

Controls encryption at rest using KMS:

{
"Rules": [{
"ResourceType": "collection",
"Resource": ["collection/docustack-dev-search"]
}],
"AWSOwnedKey": true
}
  • AWS-owned key (default): Free, managed by AWS, HIPAA compliant
  • Customer-managed key: More control, additional cost, requires KMS permissions

2. Network Policy

Controls network access to the collection:

[{
"Description": "VPC-only access for docustack-dev-search",
"Rules": [
{
"ResourceType": "collection",
"Resource": ["collection/docustack-dev-search"]
},
{
"ResourceType": "dashboard",
"Resource": ["collection/docustack-dev-search"]
}
],
"AllowFromPublic": false,
"SourceVPCEs": ["vpce-xxxxx"]
}]
  • VPC-only access: Most secure, HIPAA compliant
  • No public access: Prevents unauthorized external access
  • Dashboard access: Enables OpenSearch Dashboards via VPC

3. Data Access Policy

Controls who can access data in the collection (IAM-based):

[{
"Principal": [
"arn:aws:iam::123456789012:role/docustack-dev-app-role"
],
"Rules": [
{
"ResourceType": "index",
"Resource": ["index/docustack-dev-search/*"],
"Permission": [
"aoss:CreateIndex",
"aoss:UpdateIndex",
"aoss:DescribeIndex",
"aoss:ReadDocument",
"aoss:WriteDocument"
]
},
{
"ResourceType": "collection",
"Resource": ["collection/docustack-dev-search"],
"Permission": ["aoss:DescribeCollectionItems"]
}
]
}]

Cost Optimization

Understanding OCUs

OpenSearch Serverless uses OCUs (OpenSearch Compute Units) for billing:

  • 1 OCU = 6 GB memory + 2 vCPUs
  • Cost: $0.24/hour per OCU ($175/month)
  • Minimum: 4 OCUs (2 indexing + 2 search)

Cost Breakdown

Development Environment (standby replicas DISABLED):

  • Minimum: 4 OCUs
  • Cost: ~$691/month (4 × $175)
  • Storage: $0.024/GB-month

Production Environment (standby replicas ENABLED):

  • Minimum: 8 OCUs (4 indexing + 4 search)
  • Cost: ~$1,382/month (8 × $175)
  • Storage: $0.024/GB-month

Cost Optimization Tips

  1. Disable standby replicas in dev/staging - Saves 50% on OCU costs
  2. Use AWS-owned KMS key - Free (customer-managed keys have additional cost)
  3. Optimize index mappings - Reduce storage costs
  4. Delete unused indices - Free up storage
  5. Monitor OCU usage - Scale down if over-provisioned

Monitoring

CloudWatch Metrics

Key metrics to monitor:

MetricDescriptionThreshold
SearchOCUSearch OCUs in use> 80% of allocated
IndexingOCUIndexing OCUs in use> 80% of allocated
SearchLatencySearch request latency> 1000ms
IndexingLatencyIndexing request latency> 5000ms
SearchRateSearches per minuteBaseline for anomaly detection
IndexingRateDocuments indexed per minuteBaseline for anomaly detection

CloudWatch Alarms

Create alarms for critical metrics:

# High search latency
aws cloudwatch put-metric-alarm \
--alarm-name "opensearch-high-search-latency" \
--alarm-description "Search latency > 1000ms" \
--metric-name SearchLatency \
--namespace AWS/AOSS \
--statistic Average \
--period 300 \
--evaluation-periods 2 \
--threshold 1000 \
--comparison-operator GreaterThanThreshold

Logging

OpenSearch Serverless automatically logs to CloudWatch Logs:

  • Audit logs: All API calls (via CloudTrail)
  • Error logs: Collection errors and warnings
  • Slow logs: Slow queries (configurable threshold)

Troubleshooting

Connection Issues

Problem: Cannot connect to OpenSearch Serverless

Solutions:

  1. Verify bastion is running: aws ec2 describe-instances --instance-ids $INSTANCE_ID
  2. Check VPC endpoint status: aws opensearchserverless batch-get-vpc-endpoint --vpc-endpoint-ids vpce-xxxxx
  3. Verify network policy allows VPC endpoint access
  4. Ensure port forward is active (check terminal)

Authentication Issues

Problem: 403 Forbidden or authentication errors

Solutions:

  1. Verify IAM principal is in data access policy
  2. Check AWS credentials: aws sts get-caller-identity
  3. Ensure credentials have aoss:* permissions
  4. Verify SigV4 signing is enabled in client

Performance Issues

Problem: Slow search or indexing

Solutions:

  1. Check OCU usage in CloudWatch (may need more capacity)
  2. Optimize index mappings (reduce fields, disable unnecessary features)
  3. Use bulk indexing for large datasets
  4. Enable caching for frequently accessed data
  5. Consider using filters instead of queries where possible

Port Forward Issues

Problem: Port 9200 already in use

Solution: Use a different local port:

aws ssm start-session \
--target $INSTANCE_ID \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters "{\"host\":[\"$OPENSEARCH_ENDPOINT\"],\"portNumber\":[\"443\"],\"localPortNumber\":[\"9201\"]}"

Then connect to https://localhost:9201 instead.

Next Steps

References