The __words__ keyword index stores 50-5000 entries per entity (one per word), which inflated avg entries/entity well above the corruption threshold of 100. This caused: 1. validateConsistency() to falsely detect corruption on every startup, triggering unnecessary clearAllIndexData() + rebuild() cycles 2. getStats() to log false "Metadata index may be corrupted" warnings and report inflated totalEntries/totalIds stats Both methods now skip __words__ when counting, so stats and health checks reflect metadata fields only (noun, type, createdAt, etc.). Keyword search is unaffected since the __words__ field index itself is not modified.
14 KiB
Azure Blob Storage Cost Optimization Guide for Brainy
Cost Impact: Reduce storage costs from $107k/year to $5k/year at 500TB scale (95% savings)
Overview
Brainy provides enterprise-grade cost optimization features for Azure Blob Storage, including manual tier management, lifecycle policies, and batch operations.
Cost Breakdown (Before Optimization)
Hot Tier Azure Storage Costs (500TB Dataset)
Storage: 500TB × $0.0184/GB/month × 12 months = $107,520/year
Operations: ~$5,000/year (write/read operations)
Total: $112,520/year
Azure Blob Storage Tiers
| Tier | Cost/GB/Month | Retrieval | Early Deletion | Use Case |
|---|---|---|---|---|
| Hot | $0.0184 | None | None | Frequent access |
| Cool | $0.0115 | $0.01/GB | 30 days | Infrequent access |
| Archive | $0.00099 | $0.02/GB + rehydration time | 180 days | Long-term archive |
Key Difference from AWS/GCS:
- Azure has only 3 tiers (vs AWS 6 tiers, GCS 4 classes)
- Archive tier requires rehydration (hours to 15 hours) before access
- No "Intelligent-Tiering" equivalent - must use lifecycle policies or manual management
Strategy 1: Manual Tier Management (Immediate Savings)
Setup: Change Blob Tiers Manually
Best for: Quick wins, specific files, immediate cost reduction
import { Brainy } from '@soulcraft/brainy'
import { AzureBlobStorage } from '@soulcraft/brainy/storage'
// Initialize Brainy with Azure storage
const storage = new AzureBlobStorage({
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
containerName: 'brainy-data'
})
const brain = new Brainy({ storage })
await brain.init()
// Change tier for a single blob
await storage.changeBlobTier(
'entities/nouns/vectors/00/00123456-uuid.json',
'Cool'
)
// Batch tier changes (efficient for thousands of blobs)
const blobsToMove = [
'entities/nouns/vectors/01/...',
'entities/nouns/vectors/02/...',
// ... up to thousands of blobs
]
await storage.batchChangeTier(blobsToMove, 'Cool') // Hot → Cool
await storage.batchChangeTier(oldBlobs, 'Archive') // Cool → Archive
Immediate Cost Impact
Moving 400TB from Hot to Cool:
Before (Hot): 400TB × $0.0184/GB × 12 = $86,016/year
After (Cool): 400TB × $0.0115/GB × 12 = $53,760/year
Savings: $32,256/year (37% savings on moved data)
Moving 100TB from Hot to Archive:
Before (Hot): 100TB × $0.0184/GB × 12 = $21,504/year
After (Archive): 100TB × $0.00099/GB × 12 = $1,158/year
Savings: $20,346/year (95% savings on moved data)
Strategy 2: Lifecycle Policies (Automated)
Setup: Automatic Tier Transitions
Best for: Predictable patterns, automatic management
// Set lifecycle policy for automatic tier management
await storage.setLifecyclePolicy({
rules: [{
name: 'optimizeVectors',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/nouns/vectors/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 30 },
tierToArchive: { daysAfterModificationGreaterThan: 90 }
}
}
}
}, {
name: 'optimizeMetadata',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/nouns/metadata/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 30 },
tierToArchive: { daysAfterModificationGreaterThan: 180 }
}
}
}
}, {
name: 'cleanupOldSystemFiles',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['_system/']
},
actions: {
baseBlob: {
delete: { daysAfterModificationGreaterThan: 365 }
}
}
}
}]
})
// Verify lifecycle policy
const policy = await storage.getLifecyclePolicy()
console.log('Lifecycle policy active:', policy.rules.length, 'rules')
Cost Calculation (500TB with Lifecycle Policy)
Assumptions:
- 30% of data in Hot tier (< 30 days old)
- 40% of data in Cool tier (30-90 days old)
- 30% of data in Archive tier (90+ days old)
Hot (150TB): 150TB × $0.0184/GB × 12 = $32,256/year
Cool (200TB): 200TB × $0.0115/GB × 12 = $26,880/year
Archive (150TB): 150TB × $0.00099/GB × 12 = $1,732/year
Total Storage Cost: $60,868/year
Total with Operations: ~$65,500/year
Savings: $47,000/year (42%)
Strategy 3: Aggressive Archival (Maximum Savings)
Setup: Fast Archival for Cold Data
Best for: Archival workloads, compliance, historical data
await storage.setLifecyclePolicy({
rules: [{
name: 'aggressiveArchival',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 14 },
tierToArchive: { daysAfterModificationGreaterThan: 30 }
}
}
}
}]
})
Cost Calculation (500TB Aggressive Archival)
After 6 months:
Hot (50TB): 50TB × $0.0184/GB × 12 = $10,752/year
Cool (100TB): 100TB × $0.0115/GB × 12 = $13,440/year
Archive (350TB): 350TB × $0.00099/GB × 12 = $4,039/year
Total Storage Cost: $28,231/year
Total with Operations: ~$33,000/year
Savings: $79,500/year (71%)
Warning: Archive rehydration takes 1-15 hours
Strategy 4: Hybrid Approach (Balanced)
Setup: Different Policies for Different Data Types
await storage.setLifecyclePolicy({
rules: [{
// Vectors: Keep in Hot/Cool for search performance
name: 'vectors-moderate',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/nouns/vectors/', 'entities/verbs/vectors/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 60 }
// Don't archive vectors - keep searchable
}
}
}
}, {
// Metadata: Aggressive archival
name: 'metadata-aggressive',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/nouns/metadata/', 'entities/verbs/metadata/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 30 },
tierToArchive: { daysAfterModificationGreaterThan: 90 }
}
}
}
}]
})
Cost Calculation (500TB Hybrid Approach)
Vectors (300TB):
Hot (90TB): 90TB × $0.0184/GB × 12 = $19,354/year
Cool (210TB): 210TB × $0.0115/GB × 12 = $28,980/year
Subtotal: $48,334/year
Metadata (200TB):
Hot (30TB): 30TB × $0.0184/GB × 12 = $6,451/year
Cool (70TB): 70TB × $0.0115/GB × 12 = $9,660/year
Archive (100TB): 100TB × $0.00099/GB × 12 = $1,158/year
Subtotal: $17,269/year
Total Cost: $65,603/year + operations (~$70,500/year total) Savings vs Hot: $42,000/year (37%)
Comparison Table: All Strategies
| Strategy | Annual Cost | Savings | Archive % | Best For |
|---|---|---|---|---|
| No Optimization | $112,500 | 0% | 0% | N/A |
| Manual Tier Mgmt | $75,000 | 33% | 20% | Immediate savings |
| Lifecycle Policy | $65,500 | 42% | 30% | Automated management |
| Hybrid Approach | $70,500 | 37% | 20% | Balance performance/cost |
| Aggressive Archival | $33,000 | 71% | 70% | Cold data, compliance |
Archive Rehydration (Important!)
Rehydrate from Archive Tier
Required before accessing archived blobs:
// Rehydrate blob from Archive to Hot (high priority)
await storage.rehydrateBlob(
'entities/nouns/vectors/00/00123456-uuid.json',
'High' // 'Standard' or 'High' priority
)
// Rehydration time:
// - High priority: 1 hour
// - Standard priority: up to 15 hours
// Check rehydration status
const metadata = await storage.getBlobMetadata(blobPath)
console.log('Archive status:', metadata.archiveStatus)
// Output: 'rehydrate-pending-to-hot', 'rehydrate-pending-to-cool', or undefined (done)
Batch Rehydration
// Rehydrate multiple blobs (useful for planned access)
const blobsToRehydrate = [/* array of blob paths */]
for (const blobPath of blobsToRehydrate) {
await storage.rehydrateBlob(blobPath, 'High')
}
// Wait for rehydration to complete (1-15 hours)
// Then access blobs normally
Cost Impact of Rehydration
Retrieval fee: $0.02/GB
High priority: Additional $0.10/GB
Examples:
- 1GB blob (standard): $0.02 + wait 15 hours
- 1GB blob (high priority): $0.12 + wait 1 hour
- 1TB batch (high priority): $122.88 + wait 1 hour
Batch Operations
Efficient Bulk Deletions
// Batch delete (256 blobs per request)
const idsToDelete = [/* array of entity IDs */]
const paths = idsToDelete.flatMap(id => {
const shard = id.substring(0, 2)
return [
`entities/nouns/vectors/${shard}/${id}.json`,
`entities/nouns/metadata/${shard}/${id}.json`
]
})
// Batch delete via BlobBatchClient
await storage.batchDelete(paths)
// Cost impact:
// - Individual deletes: 1M operations × $0.0005 per 10k = $50
// - Batch deletes: 4k batches × $0.0005 = $2 (25x cheaper!)
Batch Tier Changes
// Change tier for thousands of blobs efficiently
const vectorPaths = [/* 10,000 blob paths */]
// Batch operation (256 blobs per batch)
await storage.batchChangeTier(vectorPaths, 'Cool')
// Much faster than individual changeBlobTier() calls
// Azure automatically batches internally for efficiency
Monitoring and Management
Get Current Lifecycle Policy
const policy = await storage.getLifecyclePolicy()
console.log('Active rules:', policy.rules)
// Example output:
// {
// rules: [
// {
// name: 'optimizeVectors',
// enabled: true,
// type: 'Lifecycle',
// definition: {...}
// }
// ]
// }
Remove Lifecycle Policy
await storage.removeLifecyclePolicy()
Get Storage Status
const status = await storage.getStorageStatus()
console.log('Storage type:', status.type)
console.log('Container:', status.details.container)
console.log('Account:', status.details.account)
Azure Portal Monitoring
Track Your Savings
- Storage Account → Insights → View tier distribution
- Cost Management → Cost Analysis → Filter by storage account
- Monitoring → Metrics → Track blob count by tier
- Lifecycle Management → View policy execution logs
Expected Metrics After 6 Months
Hot tier: 20-30% of total data
Cool tier: 40-50%
Archive tier: 20-40%
Monthly cost trend: Decreasing 5-8% per month as data transitions
Best Practices
- ✅ Use lifecycle policies for automatic management
- ✅ Archive cold data aggressively (95% cost savings!)
- ✅ Use batch operations for tier changes and deletions
- ✅ Plan rehydration ahead of time (1-15 hour delay)
- ✅ Monitor early deletion charges (Cool: 30 days, Archive: 180 days)
- ✅ Use Cool tier for infrequent access (no rehydration needed)
- ✅ Consider ZRS or GRS for critical data redundancy
Storage Redundancy Options
| Option | Cost Multiplier | Copies | Availability | Use Case |
|---|---|---|---|---|
| LRS | 1x | 3 (same datacenter) | 11 nines | Cost-optimized |
| ZRS | 1.25x | 3 (different zones) | 12 nines | High availability |
| GRS | 2x | 6 (secondary region) | 16 nines | Disaster recovery |
| RA-GRS | 2.5x | 6 (read access) | 16 nines | Global read access |
Recommendation for Brainy:
- Production: GRS (geo-redundancy for disaster recovery)
- Cost-optimized: LRS (lowest cost, still very reliable)
Troubleshooting
Issue: Data not transitioning tiers
Solution:
// Check lifecycle policy status
const policy = await storage.getLifecyclePolicy()
console.log('Policy rules:', policy.rules.map(r => ({
name: r.name,
enabled: r.enabled
})))
// Azure lifecycle policies run once per day
// Transitions may take 24-48 hours to execute
Issue: Access denied on archived blob
Solution:
// Archived blobs cannot be accessed directly
// Must rehydrate first:
await storage.rehydrateBlob(blobPath, 'High')
// Wait for rehydration (check status)
let status
do {
await new Promise(resolve => setTimeout(resolve, 60000)) // Wait 1 minute
const metadata = await storage.getBlobMetadata(blobPath)
status = metadata.archiveStatus
} while (status && status.includes('pending'))
// Now access the blob
const data = await storage.get(blobPath)
Issue: High early deletion charges
Solution:
- Cool tier: 30-day minimum storage
- Archive tier: 180-day minimum storage
- Early deletion incurs pro-rated charges
- Use lifecycle policies instead of manual changes to avoid early deletion fees
Authentication
Connection String (Simple)
const storage = new AzureBlobStorage({
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
containerName: 'brainy-data'
})
Account Key (Explicit)
const storage = new AzureBlobStorage({
accountName: process.env.AZURE_STORAGE_ACCOUNT,
accountKey: process.env.AZURE_STORAGE_KEY,
containerName: 'brainy-data'
})
SAS Token (Granular Access)
const storage = new AzureBlobStorage({
sasToken: process.env.AZURE_STORAGE_SAS_TOKEN,
accountName: process.env.AZURE_STORAGE_ACCOUNT,
containerName: 'brainy-data'
})
Summary
Recommended Strategy for Most Use Cases:
- Lifecycle policies for automatic tier management
- Batch operations for efficient tier changes and deletions
- Cool tier for infrequently accessed data (no rehydration delay)
- Archive tier for long-term storage (1-15 hour rehydration)
Expected Savings:
- Year 1: 40-50% reduction in storage costs
- Year 2+: 60-70% reduction as more data ages into Archive
- Long-term: 75-85% reduction for mature datasets
500TB Example (Lifecycle Policy):
- Before: $112,500/year
- After: $65,500/year
- Savings: $47,000/year (42%)
500TB Example (Aggressive Archival):
- Before: $112,500/year
- After: $33,000/year
- Savings: $79,500/year (71%)
1PB Example (Lifecycle Policy):
- Before: $225,000/year
- After: $131,000/year
- Savings: $94,000/year (42%)
Last Updated: 2025-10-17 Cloud Provider: Azure Blob Storage