This guide covers scaling Mantissa Log for high-volume environments and optimizing costs.
Mantissa Log scales automatically with serverless components, but optimization is needed for:
- High event volumes (> 10M events/month)
- Cost optimization
- Query performance
- Multi-account deployments
| Volume | Events/Month | Recommended Configuration |
|---|---|---|
| Small | < 1M | Default configuration |
| Medium | 1M - 10M | Optimized partitioning, Parquet conversion |
| Large | 10M - 100M | Reserved capacity, query optimization |
| Enterprise | > 100M | Multi-region, dedicated resources |
| Queries/Day | Configuration |
|---|---|
| < 100 | Default |
| 100 - 1000 | Query caching, result reuse |
| > 1000 | Reserved Athena capacity |
Problem: Athena costs $5 per TB scanned.
Solutions:
1. Partition Pruning
-- Bad: Scans entire table (expensive)
SELECT * FROM cloudtrail
WHERE eventtime > '2024-01-15T00:00:00Z'
-- Good: Partition filters (cheap)
SELECT * FROM cloudtrail
WHERE year = '2024'
AND month = '01'
AND day = '15'
AND eventtime > '2024-01-15T00:00:00Z'2. Columnar Format (Parquet)
Convert JSON logs to Parquet:
# Create Parquet table
aws athena start-query-execution \
--query-string "
CREATE TABLE cloudtrail_parquet
WITH (
format = 'PARQUET',
parquet_compression = 'SNAPPY',
partitioned_by = ARRAY['year', 'month', 'day']
) AS
SELECT * FROM cloudtrail
" \
--query-execution-context Database=$DATABASE \
--work-group $WORKGROUPSavings: 80-90% reduction in data scanned
3. Query Result Reuse
Enable in Athena workgroup:
aws athena update-work-group \
--work-group mantissa-log \
--configuration-updates "
ResultConfigurationUpdates={
OutputLocation=s3://$ATHENA_BUCKET/results/
},
ResultReuseConfigurationUpdates={
Enabled=true,
MaxAgeInMinutes=60
}
"4. Column Projection
-- Bad: Selects all columns
SELECT * FROM cloudtrail WHERE ...
-- Good: Only needed columns
SELECT eventtime, eventname, useridentity.principalid
FROM cloudtrail WHERE ...1. Lifecycle Policies
cat > lifecycle-policy.json << 'EOF'
{
"Rules": [
{
"Id": "TransitionOldLogs",
"Status": "Enabled",
"Transitions": [
{
"Days": 90,
"StorageClass": "STANDARD_IA"
},
{
"Days": 365,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 2555
}
}
]
}
EOF
aws s3api put-bucket-lifecycle-configuration \
--bucket $LOGS_BUCKET \
--lifecycle-configuration file://lifecycle-policy.jsonSavings: 40-70% on storage costs
2. Intelligent Tiering
aws s3api put-bucket-intelligent-tiering-configuration \
--bucket $LOGS_BUCKET \
--id ManualTiering \
--intelligent-tiering-configuration '{
"Id": "ManualTiering",
"Status": "Enabled",
"Tierings": [
{
"Days": 90,
"AccessTier": "ARCHIVE_ACCESS"
},
{
"Days": 180,
"AccessTier": "DEEP_ARCHIVE_ACCESS"
}
]
}'1. Right-Size Memory
Test different memory sizes:
# Test with different memory
for MEMORY in 512 1024 2048; do
aws lambda update-function-configuration \
--function-name mantissa-log-detection-engine \
--memory-size $MEMORY
# Run test
aws lambda invoke \
--function-name mantissa-log-detection-engine \
response.json
# Check duration
aws logs tail /aws/lambda/mantissa-log-detection-engine \
| grep "Duration"
doneFind sweet spot where duration × cost is minimized.
2. Reserved Concurrency
For predictable workloads:
aws lambda put-function-concurrency \
--function-name mantissa-log-detection-engine \
--reserved-concurrent-executions 101. On-Demand vs Provisioned
Small/variable load: On-Demand (default) Large/steady load: Provisioned with auto-scaling
# Switch to provisioned
aws dynamodb update-table \
--table-name $STATE_TABLE \
--billing-mode PROVISIONED \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
# Enable auto-scaling
aws application-autoscaling register-scalable-target \
--service-namespace dynamodb \
--resource-id "table/$STATE_TABLE" \
--scalable-dimension "dynamodb:table:ReadCapacityUnits" \
--min-capacity 5 \
--max-capacity 1002. TTL for State Data
Automatically delete old state:
aws dynamodb update-time-to-live \
--table-name $STATE_TABLE \
--time-to-live-specification "Enabled=true, AttributeName=ttl"Hourly partitions for high volume:
s3://bucket/cloudtrail/year=2024/month=01/day=15/hour=14/
CREATE EXTERNAL TABLE cloudtrail_hourly (
...
)
PARTITIONED BY (
year STRING,
month STRING,
day STRING,
hour STRING
)Benefits:
- Smaller partitions = faster queries
- More granular time filters
- Better parallelization
Use Snappy compression for Parquet:
CREATE TABLE cloudtrail_optimized
WITH (
format = 'PARQUET',
parquet_compression = 'SNAPPY' -- Fast decompression
) AS SELECT * FROM cloudtrailAthena default: 20 concurrent queries Increase for high volume:
# Contact AWS Support to increase limits
# Or use multiple workgroups
aws athena create-work-group \
--name mantissa-log-high-priority \
--configuration "
ResultConfiguration={
OutputLocation=s3://$ATHENA_BUCKET/results/
}
"Add Redis/ElastiCache for frequently accessed data:
import redis
cache = redis.Redis(host='cache-endpoint', port=6379)
def query_with_cache(sql, ttl=300):
cache_key = hashlib.md5(sql.encode()).hexdigest()
# Check cache
cached = cache.get(cache_key)
if cached:
return json.loads(cached)
# Execute query
result = athena.execute_query(sql)
# Cache result
cache.setex(cache_key, ttl, json.dumps(result))
return resultProblem: CloudTrail delivers logs with delay
Solutions:
1. Kinesis Firehose for Real-Time
aws firehose create-delivery-stream \
--delivery-stream-name cloudtrail-realtime \
--extended-s3-destination-configuration \
BucketARN=arn:aws:s3:::$LOGS_BUCKET,\
Prefix=cloudtrail/,\
BufferingHints={SizeInMBs=128,IntervalInSeconds=60},\
CompressionFormat=GZIP,\
DataFormatConversionConfiguration={
SchemaConfiguration={
DatabaseName=$DATABASE,
TableName=cloudtrail,
Region=us-east-1
},
InputFormatConfiguration={
Deserializer={OpenXJsonSerDe={}}
},
OutputFormatConfiguration={
Serializer={ParquetSerDe={}}
}
}2. Event-Driven Detection
Trigger on S3 object creation:
# Add S3 event notification
aws s3api put-bucket-notification-configuration \
--bucket $LOGS_BUCKET \
--notification-configuration '{
"LambdaFunctionConfigurations": [{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:mantissa-log-detection-engine",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [{
"Name": "prefix",
"Value": "cloudtrail/"
}]
}
}
}]
}'Parallel Rule Execution:
# In detection engine
import concurrent.futures
def execute_rules_parallel(rules, max_workers=10):
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(execute_rule, rule): rule for rule in rules}
for future in concurrent.futures.as_completed(futures):
rule = futures[future]
try:
result = future.result()
yield result
except Exception as e:
logger.error(f"Rule {rule.name} failed: {e}")Batch Query Execution:
# Execute multiple queries in single Athena call
queries = [rule.query for rule in rules]
query_ids = []
for query in queries:
query_id = athena.start_query_execution(
QueryString=query,
QueryExecutionContext={'Database': database},
WorkGroup=workgroup
)
query_ids.append(query_id)
# Poll for results
results = athena.batch_get_query_execution(QueryExecutionIds=query_ids)Spoke Accounts → Central Log Account → Mantissa Log
1. Set up cross-account S3 access:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::central-logs-bucket"
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::central-logs-bucket/cloudtrail/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}2. Configure spoke account CloudTrail:
# In each spoke account
aws cloudtrail create-trail \
--name organization-trail \
--s3-bucket-name central-logs-bucket \
--s3-key-prefix cloudtrail/account-123456789012/ \
--is-organization-trail \
--region us-east-13. Create Glue tables with account partitions:
CREATE EXTERNAL TABLE cloudtrail_multi_account (
...
)
PARTITIONED BY (
account_id STRING,
year STRING,
month STRING,
day STRING
)Use AWS CloudFormation StackSets:
aws cloudformation create-stack-set \
--stack-set-name mantissa-log-org \
--template-body file://mantissa-log-stackset.yaml \
--capabilities CAPABILITY_IAM \
--parameters \
ParameterKey=CentralLogsBucket,ParameterValue=central-logs-bucket
aws cloudformation create-stack-instances \
--stack-set-name mantissa-log-org \
--accounts 123456789012 234567890123 345678901234 \
--regions us-east-1 \
--operation-preferences FailureToleranceCount=0,MaxConcurrentCount=1Deploy Mantissa Log in multiple regions:
# Deploy to us-east-1
cd infrastructure/aws/terraform
terraform workspace new us-east-1
terraform apply -var="aws_region=us-east-1"
# Deploy to us-west-2
terraform workspace new us-west-2
terraform apply -var="aws_region=us-west-2"Replicate logs to secondary region:
aws s3api put-bucket-replication \
--bucket $LOGS_BUCKET \
--replication-configuration '{
"Role": "arn:aws:iam::123456789012:role/s3-replication-role",
"Rules": [{
"Status": "Enabled",
"Priority": 1,
"DeleteMarkerReplication": {"Status": "Disabled"},
"Filter": {},
"Destination": {
"Bucket": "arn:aws:s3:::logs-bucket-replica",
"ReplicationTime": {
"Status": "Enabled",
"Time": {"Minutes": 15}
}
}
}]
}'DynamoDB Global Tables:
aws dynamodb update-table \
--table-name $STATE_TABLE \
--replica-updates '[{
"Create": {
"RegionName": "us-west-2"
}
}]' \
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGESCustom Metrics:
import boto3
cloudwatch = boto3.client('cloudwatch')
# Track rules executed
cloudwatch.put_metric_data(
Namespace='MantissaLog',
MetricData=[{
'MetricName': 'RulesExecuted',
'Value': len(rules),
'Unit': 'Count',
'Timestamp': datetime.utcnow()
}]
)
# Track data scanned
cloudwatch.put_metric_data(
Namespace='MantissaLog',
MetricData=[{
'MetricName': 'DataScannedBytes',
'Value': query_result['Statistics']['DataScannedInBytes'],
'Unit': 'Bytes'
}]
)Add X-Ray to Lambda functions:
aws lambda update-function-configuration \
--function-name mantissa-log-detection-engine \
--tracing-config Mode=Active# In Lambda function
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all
patch_all()
@xray_recorder.capture('execute_rule')
def execute_rule(rule):
# Function code
pass| Metric | Small | Medium | Large |
|---|---|---|---|
| Detection Cycle | < 30s | < 2m | < 5m |
| Query Latency | < 5s | < 10s | < 30s |
| Alert Delivery | < 1s | < 2s | < 5s |
| Data Scanned/Query | < 100MB | < 1GB | < 10GB |
- Athena query execution: < 10s
- Lambda cold start: < 3s
- Alert routing latency: < 2s
- End-to-end (detection → alert): < 60s
Events per month: 50M Rules: 50 Detection frequency: 5 minutes
Athena queries/month:
- 50 rules × 12 per hour × 24 hours × 30 days = 432,000 queries
Data scanned (with partitioning):
- 50M events × 500 bytes = 25GB raw
- With Parquet: 25GB × 0.2 = 5GB
- Per query (5 min window): 5GB / (12 × 24 × 30) = 580KB
- Total scanned: 432,000 × 580KB = 250GB/month
Cost estimate:
- Athena: 250GB × $5/TB = $1.25
- S3: 25GB × $0.023 = $0.58
- Lambda: 432,000 × $0.0000002 = $0.09
- DynamoDB: Variable, ~$5-10
- Total: ~$7-12/month
- Enable partition pruning in all queries
- Convert high-volume tables to Parquet
- Set up S3 lifecycle policies
- Configure DynamoDB auto-scaling
- Enable Athena query result reuse
- Right-size Lambda memory
- Implement query caching
- Monitor and optimize slow queries
- Set up cost alerts
- Review and disable low-value rules