Skip to main content

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 PatternIndex TypeExample
Unique lookupSecondaryKeyIndexUser by email
Categorical groupingCategorizationIndexTasks by owner
Range queriesSliceIndexMessages 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:

  1. Keep documents flat and small (< 10KB ideal)
  2. Plan indexes before writing data
  3. Use batch operations (mget) when possible
  4. Always handle errors explicitly
  5. Use scoped tokens for security
  6. Monitor performance in production
  7. Design for horizontal scaling with shards