Best Practices
This guide covers recommended practices for building efficient and maintainable applications with Garak.
Data Modeling
Avoid Nested Fields
Garak, for backwards compatibility, supports nested fields via dot notation anywhere a field name is required. However, we strongly recommend avoiding nested fields. Parsing a dot notation field and traversing the document adds performance overhead which can add up at production scale.
Bad:
type User = {
profile: {
settings: {
theme: string;
language: string;
};
};
};
// Slow due to nested traversal
await tx.set(store, nodeId, "profile.settings.theme", "dark");
Good:
type User = {
theme: string;
language: string;
};
// Direct field access
await tx.set(store, nodeId, "theme", "dark");
Keep Documents Small
Garak enforces a 25KB limit per node. Aim to keep nodes well under this limit:
- Target size: 1-10KB per node for optimal performance
- Split large objects: Store large fields in separate nodes
- Avoid large arrays: Use separate nodes with categorization indexes
- No binary data: Store files in S3/blob storage, keep references in Garak
Design for Indexes
Plan your indexes before inserting data. Every index adds write overhead.
Questions to ask:
- How will I query this data?
- Which fields need unique constraints?
- Which fields will be used for filtering?
- What ranges or time-based queries do I need?
Use Scalar Types for Slice Indexes
Slice indexes work best with numerical timestamps:
Bad:
type Message = {
createdAt: Date; // Will be stringified
};
Good:
type Message = {
createdAt: number; // Unix timestamp in milliseconds
};
Performance Optimization
Batch Read Operations
Use mget instead of multiple get calls:
Bad:
const users = [];
for (const userId of userIds) {
const user = await tx.get(usersStore, userId);
users.push(user);
}
Good:
const { nodes } = await tx.mget(usersStore, userIds);
const users = nodes
.filter(n => n.value !== null)
.map(n => n.value);
Minimize Index Updates
Updates to indexed fields trigger index maintenance. Avoid frequent updates to indexed fields.
Use Appropriate Index Types
| Access Pattern | Index Type | Example |
|---|---|---|
| Unique lookup | SecondaryKeyIndex | User by email |
| Categorical grouping | CategorizationIndex | Tasks by owner |
| Range queries | SliceIndex | Messages by time |
Security
Use Scoped Tokens
Create tokens with minimal required permissions:
// Good: Scoped to specific silo
const token = await masterController.createToken("write", "tenant-123");
// Better: Read-only when possible
const readToken = await masterController.createToken("read", "tenant-123");
Sanitize User Input
function sanitizeShardId(input: string): string {
return input
.replace(/[^a-zA-Z0-9_-]/g, "")
.substring(0, 64);
}
Summary
Key Takeaways:
- Keep documents flat and small (< 10KB ideal)
- Plan indexes before writing data
- Use batch operations (
mget) when possible - Always handle errors explicitly
- Use scoped tokens for security
- Monitor performance in production
- Design for horizontal scaling with shards