Best Practices
Best practices for building performant, reliable applications with TerraScale.
Key Design
Section titled “Key Design”Use Meaningful Partition Keys
Section titled “Use Meaningful Partition Keys”Design partition keys that distribute data evenly and support your access patterns:
// Good: Distributes data by user{ "pk": "user#123", "sk": "profile" }{ "pk": "user#123", "sk": "order#001" }
// Avoid: All data in one partition{ "pk": "all_users", "sk": "user#123" }Leverage Sort Keys for Related Items
Section titled “Leverage Sort Keys for Related Items”Group related items under the same partition key:
// User and their orders in same partition{ "pk": "user#123", "sk": "profile" }{ "pk": "user#123", "sk": "order#2024-001" }{ "pk": "user#123", "sk": "order#2024-002" }
// Query all orders efficientlyvar filter = new QueryFilter{ PartitionKey = "user#123", SortKeyCondition = SortKeyCondition.BeginsWith("order#")};Avoid Hot Partitions
Section titled “Avoid Hot Partitions”Distribute writes across partition keys:
// Bad: All writes to one partition{ "pk": "orders", "sk": "order#12345" }
// Good: Partition by date or user{ "pk": "orders#2024-01-15", "sk": "order#12345" }{ "pk": "user#456#orders", "sk": "order#12345" }Query Optimization
Section titled “Query Optimization”Use Query Instead of Scan
Section titled “Use Query Instead of Scan”Queries are efficient; scans read everything:
// Good: Query by partition keyvar result = await client.QueryAsync(new QueryFilter{ PartitionKey = "user#123"});
// Avoid: Scan the entire databasevar result = await client.ScanAsync(new PaginationOptions());Limit Result Sizes
Section titled “Limit Result Sizes”Always specify reasonable limits:
var result = await client.QueryAsync(filter, new QueryOptions{ Limit = 50 // Don't fetch more than needed});Use Projection Expressions
Section titled “Use Projection Expressions”Return only needed attributes:
var options = new QueryOptions{ ProjectionAttributes = new[] { "name", "email" } // Skip large attributes};Design for Access Patterns
Section titled “Design for Access Patterns”Structure keys to support your queries:
// Access pattern: Get user's orders by date{ "pk": "user#123", "sk": "order#2024-01-15#001" }
// Now you can query by date rangeSortKeyCondition.Between("order#2024-01-01", "order#2024-01-31")Error Handling
Section titled “Error Handling”Always Check Results
Section titled “Always Check Results”Never assume operations succeed:
var result = await client.GetItemAsync("user#123");
if (result.IsFailed){ logger.LogError("Get failed: {Error}", result.Errors.First().Message); return null;}
return result.Value;Implement Retry Logic
Section titled “Implement Retry Logic”Handle transient failures gracefully:
var client = new TerraScaleDatabase(new TerraScaleDatabaseOptions{ Retry = new RetryPolicyOptions { MaxRetries = 3, BaseDelay = TimeSpan.FromMilliseconds(500), UseJitter = true }});Log Errors with Context
Section titled “Log Errors with Context”Include operation details for debugging:
if (result.IsFailed){ logger.LogError( "Failed to get item PK={Pk} SK={Sk}: {Error}", pk, sk, result.Errors.First().Message );}Transactions
Section titled “Transactions”Use Transactions Only When Needed
Section titled “Use Transactions Only When Needed”Transactions have higher latency than batch operations:
// Use batch for independent writesawait client.BatchWriteAsync(items);
// Use transactions only for atomic operationsawait client.TransactWriteAsync(items);Keep Transactions Small
Section titled “Keep Transactions Small”Fewer items = faster execution:
// Good: Small, focused transactionvar items = new List<TransactWriteItem>{ new() { Action = TransactAction.Put, ... }, new() { Action = TransactAction.Update, ... }};
// Avoid: Large transactions with many itemsUse Idempotency Tokens
Section titled “Use Idempotency Tokens”Prevent duplicate operations on retry:
var result = await client.TransactWriteAsync( items, clientRequestToken: "order-12345-payment");Performance
Section titled “Performance”Use Batch Operations
Section titled “Use Batch Operations”Reduce network round trips:
// Bad: Many individual requestsforeach (var key in keys){ await client.GetItemAsync(key.Pk, key.Sk);}
// Good: Single batch requestawait client.BatchGetAsync(keys);Parallel Processing
Section titled “Parallel Processing”Process independent operations concurrently:
var tasks = partitions.Select(pk => client.QueryAsync(new QueryFilter { PartitionKey = pk }));
var results = await Task.WhenAll(tasks);Connection Pooling
Section titled “Connection Pooling”Reuse client instances:
// Good: Create once, reusepublic class MyService{ private readonly TerraScaleDatabase _client;
public MyService(TerraScaleDatabase client) { _client = client; }}
// Avoid: Creating new clients per requestSecurity
Section titled “Security”Use Specific Scopes
Section titled “Use Specific Scopes”Grant minimum necessary permissions:
// Good: Specific permissions{ "scopes": ["database:read", "repository:read"] }
// Avoid: Overly broad permissions{ "scopes": ["*"] }Rotate API Keys
Section titled “Rotate API Keys”Set expiration and rotate regularly:
await client.ApiKeys.CreateAsync(new CreateApiKeyRequest( Name: "Production Key", Scopes: new[] { "database:read", "database:write" }, ExpiresAt: DateTime.UtcNow.AddMonths(3)));Enable MFA
Section titled “Enable MFA”Protect accounts with two-factor authentication.
Store Secrets Securely
Section titled “Store Secrets Securely”Never commit API keys to source control:
// Good: Environment variablevar apiKey = Environment.GetEnvironmentVariable("TERRASCALE_API_KEY");
// Good: Secret managervar apiKey = await secretManager.GetSecretAsync("terrascale-api-key");
// Bad: Hardcodedvar apiKey = "ts_live_abc123...";Data Modeling
Section titled “Data Modeling”Use Repositories for Domain Entities
Section titled “Use Repositories for Domain Entities”Typed entities with schema validation:
public record Customer : EntityBase{ public required string Name { get; init; } public required string Email { get; init; }}
var customers = client.GetRepository<Customer>("customers");Use Raw Items for Flexible Data
Section titled “Use Raw Items for Flexible Data”Dynamic attributes without schema:
var item = new DatabaseItem{ PartitionKey = "config#app", Attributes = configData};Denormalize for Read Performance
Section titled “Denormalize for Read Performance”Store data in the shape you read it:
// Instead of joining user and address{ "pk": "user#123", "sk": "profile", "data": { "name": "John", "address": { // Embedded, not referenced "street": "123 Main St", "city": "NYC" }}}Monitoring
Section titled “Monitoring”Track Usage
Section titled “Track Usage”Monitor your usage against plan limits:
var usage = await client.Payment.GetUsageAsync();
if (usage.Value.TotalRequests > warningThreshold){ logger.LogWarning("Approaching request limit");}Use Health Endpoints
Section titled “Use Health Endpoints”Monitor API availability:
curl https://api.terrascale.io/healthNext Steps
Section titled “Next Steps”- Error Handling - Handle errors gracefully
- Rate Limits - Stay within limits
- API Reference - Complete API documentation