The pool pattern is a shared infrastructure approach where all tenants use the same Amazon Bedrock Knowledge Base, S3 bucket, and vector database. Here's how it works based on the implementation:
Core Concept
Instead of creating separate resources for each tenant, everyone shares:
- One S3 bucket for all documents
- One Knowledge Base for processing
- One vector database (Pinecone, or AWS OpenSearch as an alternative) for storing embeddings
- Metadata filtering to keep data separated
Step-by-Step Process
1. Document Upload with Tenant Metadata
When a user uploads a document, the system creates two files in S3:
// Original document
s3Key = `users/${userId}/agents/${agentId}/documents/${documentId}/${filename}`
// Metadata file (CRITICAL for tenant isolation)
metadataKey = `${document.s3Key}.metadata.json`
The metadata file contains:
{
"metadataAttributes": {
"userId": "user123",
"username": "john_doe",
"agentId": "agent456",
"filename": "report.pdf",
"uploadDate": "2025-01-15T10:30:00Z"
}
}
This metadata is what enables tenant isolation in the shared system.
2. Shared Knowledge Base Processing
All documents go through the same Knowledge Base:
// Single shared Knowledge Base for all tenants
await this.bedrockClient.send(new StartIngestionJobCommand({
knowledgeBaseId: this.configService.get('SHARED_KNOWLEDGE_BASE_ID'),
dataSourceId: this.configService.get('SHARED_DATA_SOURCE_ID'),
}));
The Knowledge Base:
- Chunks all documents the same way
- Uses the same embedding model
- Stores vectors with the metadata in Pinecone
3. Querying with Tenant Isolation
When a user asks a question, the system filters results by their tenant ID:
filter: {
andAll: [
{
equals: {
key: 'userId',
value: userId, // Only this user's documents
},
},
{
equals: {
key: 'agentId',
value: agentId, // Only this specific agent's documents
},
},
],
}
This ensures User A never sees User B's documents, even though they're in the same database.
Visual Flow
- User uploads document → Creates S3 object + metadata.json
- Knowledge Base ingests → Processes both files together
- Vectors stored in Pinecone → With userId and agentId metadata
- User queries → Filter applied → Only their documents returned
Key Implementation Details
Agent Creation
Each user can have multiple agents (like different assistants):
async createAgent(userId: string, name: string, description?: string) {
return await this.agentModel.create({
userId: userId,
name,
description,
knowledgeBaseId: this.configService.get('SHARED_KNOWLEDGE_BASE_ID'),
dataSourceId: this.configService.get('SHARED_DATA_SOURCE_ID'),
});
}
Multi-Model Support
Users can choose different AI models while sharing the same knowledge base:
// User can select Claude, Mistral, Titan, etc.
const selectedModelKey = modelKey || session.modelKey || 'claude3Sonnet';
User Registration
New users automatically get access to the shared infrastructure:
await this.userModel.create({
cognitoId: response.UserSub,
username: username,
email: email,
// Pool pattern benefits - instant access to shared resources
tier: 'starter',
maxAgents: 5,
maxDocuments: 100,
maxMessagesPerMonth: 1000,
});
Why This Works
- Cost Efficient: One Knowledge Base serves hundreds of tenants
- Simple Onboarding: New user? Just start uploading - no setup needed
- Flexible: Each tenant can use different AI models
- Secure: Metadata filtering ensures complete data isolation
Critical Success Factor
The .metadata.json
file is crucial. Without it:
- Documents won't be filterable by tenant
- Data isolation breaks
- The entire multi-tenant system fails
Always ensure every document has its corresponding metadata file with the correct userId
and agentId
.
Top comments (0)