Documentation
¶
Overview ¶
Package multidb provides composite database support for multi-database functionality.
Composite databases are virtual databases that span multiple physical databases, allowing queries to transparently access data from multiple constituent databases.
Package multidb provides configuration helpers for multi-database support.
Package multidb provides limit enforcement for multi-database support.
This package implements resource limit checking and enforcement for NornicDB's multi-database feature. Limits are enforced at runtime to prevent any single database from consuming excessive resources.
Key Features:
- Storage limits: MaxNodes, MaxEdges, MaxBytes (enforced with exact size calculation)
- Query limits: MaxQueryTime, MaxResults, MaxConcurrentQueries
- Connection limits: MaxConnections
- Rate limits: MaxQueriesPerSecond, MaxWritesPerSecond
Example - Checking limits before creating a node:
checker, err := manager.GetLimitChecker("tenant_a")
if err != nil {
return err
}
node := &storage.Node{
ID: storage.NodeID("user-123"),
Labels: []string{"User"},
Properties: map[string]any{"name": "Alice"},
}
// Check if creating this node would exceed limits
if err := checker.CheckStorageLimits("create_node", node, nil); err != nil {
return fmt.Errorf("cannot create node: %w", err)
}
// Safe to create - limits checked
storage.CreateNode(node)
MaxBytes Enforcement:
MaxBytes enforcement uses exact size calculation, not estimation. When checking limits for a create operation, the actual serialized size of the node/edge is calculated using gob encoding (matching the storage format). The current total storage size is tracked incrementally in DatabaseInfo, and the check verifies that currentSize + newEntitySize <= MaxBytes.
Storage size is tracked incrementally:
- Initialized lazily on first access (calculated from all existing nodes/edges)
- Updated incrementally after successful creates/deletes
- No recalculation needed - O(1) limit checks
Package multidb provides multi-database support for NornicDB.
This package implements Neo4j 4.x-style multi-database support, allowing multiple logical databases (tenants) to share a single physical storage backend while maintaining complete data isolation.
Package multidb provides resource limits for multi-database support.
Package multidb provides multi-database support for NornicDB.
This package implements Neo4j 4.x-style multi-database support, allowing multiple logical databases (tenants) to share a single physical storage backend while maintaining complete data isolation.
Package multidb provides metadata persistence for multi-database support.
Package multidb provides automatic migration of existing unprefixed data.
When upgrading from a pre-multi-database version of NornicDB, existing data without namespace prefixes is automatically migrated to the default database namespace. This ensures backwards compatibility and zero-downtime upgrades.
Migration Process:
- Detects unprefixed data (nodes/edges without "namespace:" prefix)
- Migrates all unprefixed data to default database namespace
- Updates all indexes automatically (via CreateNode/CreateEdge)
- Marks migration as complete in metadata (prevents re-running)
The migration runs automatically in NewDatabaseManager() and is completely transparent to users. No manual steps are required.
Example:
// Before upgrade: data stored as "node-123" // After upgrade: automatically becomes "nornic:node-123" // User access remains the same - no changes needed!
Package multidb provides routing strategies for composite databases.
Routing determines which constituent databases should be queried for a given operation. This enables efficient query execution by only accessing relevant constituents.
Index ¶
- Variables
- type CompositeRouting
- type Config
- type ConnectionLimits
- type ConnectionTracker
- func (t *ConnectionTracker) CheckConnectionLimit(manager *DatabaseManager, databaseName string) error
- func (t *ConnectionTracker) DecrementConnection(databaseName string)
- func (t *ConnectionTracker) GetConnectionCount(databaseName string) int
- func (t *ConnectionTracker) IncrementConnection(databaseName string)
- func (t *ConnectionTracker) TryIncrementConnection(manager *DatabaseManager, databaseName string) error
- type ConstituentRef
- type DatabaseInfo
- type DatabaseManager
- func (m *DatabaseManager) AddConstituent(compositeName string, constituent ConstituentRef) error
- func (m *DatabaseManager) Close() error
- func (m *DatabaseManager) CreateAlias(alias, databaseName string) error
- func (m *DatabaseManager) CreateCompositeDatabase(name string, constituents []ConstituentRef) error
- func (m *DatabaseManager) CreateDatabase(name string) error
- func (m *DatabaseManager) DecrementStorageSize(databaseName string, nodeSize, edgeSize int64)
- func (m *DatabaseManager) DefaultDatabaseName() string
- func (m *DatabaseManager) DropAlias(alias string) error
- func (m *DatabaseManager) DropCompositeDatabase(name string) error
- func (m *DatabaseManager) DropDatabase(name string) error
- func (m *DatabaseManager) Exists(name string) bool
- func (m *DatabaseManager) ExistsOrIsConstituent(name string) bool
- func (m *DatabaseManager) GetCompositeConstituents(compositeName string) ([]ConstituentRef, error)
- func (m *DatabaseManager) GetDatabase(name string) (*DatabaseInfo, error)
- func (m *DatabaseManager) GetDatabaseLimits(databaseName string) (*Limits, error)
- func (m *DatabaseManager) GetDefaultStorage() (storage.Engine, error)
- func (m *DatabaseManager) GetStorage(name string) (storage.Engine, error)
- func (m *DatabaseManager) GetStorageSize(databaseName string) (int64, int64, int64)
- func (m *DatabaseManager) GetStorageWithAuth(name string, authToken string) (storage.Engine, error)
- func (m *DatabaseManager) IncrementStorageSize(databaseName string, nodeSize, edgeSize int64)
- func (m *DatabaseManager) IsCompositeDatabase(name string) bool
- func (m *DatabaseManager) ListAliases(databaseName string) map[string]string
- func (m *DatabaseManager) ListCompositeDatabases() []*DatabaseInfo
- func (m *DatabaseManager) ListDatabases() []*DatabaseInfo
- func (m *DatabaseManager) RemoveConstituent(compositeName string, alias string) error
- func (m *DatabaseManager) ResolveDatabase(nameOrAlias string) (string, error)
- func (m *DatabaseManager) SetDatabaseLimits(databaseName string, limits *Limits) error
- func (m *DatabaseManager) SetDatabaseStatus(name, status string) error
- type FullScanRouting
- type LabelRouting
- type LimitChecker
- type Limits
- type PropertyRouting
- func (r *PropertyRouting) RouteQuery(queryInfo *QueryInfo) []string
- func (r *PropertyRouting) RouteWrite(operation string, labels []string, properties map[string]interface{}) string
- func (r *PropertyRouting) SetDefaultConstituent(constituent string)
- func (r *PropertyRouting) SetPropertyRouting(value interface{}, constituent string)
- type QueryInfo
- type QueryLimits
- type RateLimits
- type RemoteEngineFactory
- type RoutingStrategy
- type StorageLimits
Constants ¶
This section is empty.
Variables ¶
var ( ErrDatabaseNotFound = errors.New("database not found") ErrDatabaseExists = errors.New("database already exists") ErrInvalidDatabaseName = errors.New("invalid database name") ErrMaxDatabasesReached = errors.New("maximum number of databases reached") ErrCannotDropSystemDB = errors.New("cannot drop system database") ErrCannotDropDefaultDB = errors.New("cannot drop default database") ErrDatabaseOffline = errors.New("database is offline") ErrAliasExists = errors.New("alias already exists") ErrAliasNotFound = errors.New("alias not found") ErrInvalidAliasName = errors.New("invalid alias name") ErrAliasConflict = errors.New("alias conflicts with existing database name") ErrDatabaseHasAliases = errors.New("database has aliases - drop aliases first") ErrStorageLimitExceeded = errors.New("storage limit exceeded") ErrQueryLimitExceeded = errors.New("query limit exceeded") ErrConnectionLimitExceeded = errors.New("connection limit exceeded") ErrRateLimitExceeded = errors.New("rate limit exceeded") ErrNotCompositeDatabase = errors.New("database is not a composite database") ErrConstituentNotFound = errors.New("constituent not found") ErrDuplicateConstituent = errors.New("duplicate constituent alias") )
Multi-database error types
Functions ¶
This section is empty.
Types ¶
type CompositeRouting ¶
type CompositeRouting struct {
// contains filtered or unexported fields
}
CompositeRouting combines multiple routing strategies. Tries each strategy in order until one returns a result.
func NewCompositeRouting ¶
func NewCompositeRouting() *CompositeRouting
NewCompositeRouting creates a new composite routing strategy.
func (*CompositeRouting) AddStrategy ¶
func (c *CompositeRouting) AddStrategy(strategy RoutingStrategy)
AddStrategy adds a routing strategy to try in order.
func (*CompositeRouting) RouteQuery ¶
func (c *CompositeRouting) RouteQuery(queryInfo *QueryInfo) []string
RouteQuery tries each strategy until one returns a result.
func (*CompositeRouting) RouteWrite ¶
func (c *CompositeRouting) RouteWrite(operation string, labels []string, properties map[string]interface{}) string
RouteWrite tries each strategy until one returns a result.
type Config ¶
type Config struct {
// DefaultDatabase is the database used when none is specified (default: "nornic")
// This matches Neo4j's behavior where "neo4j" is the default, but NornicDB uses "nornic"
DefaultDatabase string
// SystemDatabase stores metadata (default: "system")
SystemDatabase string
// MaxDatabases limits total databases (0 = unlimited)
MaxDatabases int
// AllowDropDefault allows dropping the default database
AllowDropDefault bool
// RemoteEngineFactory creates a storage engine for a remote constituent.
// If nil, remote constituents are not executable (metadata may still be stored).
RemoteEngineFactory RemoteEngineFactory
// RemoteCredentialEncryptionKey encrypts remote constituent user/password values
// before metadata persistence. If empty, user_password auth mode is rejected.
RemoteCredentialEncryptionKey string
}
Config holds DatabaseManager configuration.
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns default configuration. The default database name is "nornic" (NornicDB's equivalent of Neo4j's "neo4j").
func NewConfigFromDefaultDatabase ¶
NewConfigFromDefaultDatabase creates a DatabaseManager Config from a default database name. This allows the DatabaseManager to use the same default database name as configured in the main NornicDB configuration.
Example:
// Use default database name from main config mainConfig := config.LoadDefaults() dbConfig := multidb.NewConfigFromDefaultDatabase(mainConfig.Database.DefaultDatabase) manager := multidb.NewDatabaseManager(inner, dbConfig)
type ConnectionLimits ¶
type ConnectionLimits struct {
// MaxConnections is the maximum concurrent connections (0 = unlimited).
MaxConnections int `json:"max_connections,omitempty"`
}
ConnectionLimits controls connection count per database.
type ConnectionTracker ¶
type ConnectionTracker struct {
// contains filtered or unexported fields
}
ConnectionTracker tracks active connections per database.
func NewConnectionTracker ¶
func NewConnectionTracker() *ConnectionTracker
NewConnectionTracker creates a new connection tracker.
func (*ConnectionTracker) CheckConnectionLimit ¶
func (t *ConnectionTracker) CheckConnectionLimit(manager *DatabaseManager, databaseName string) error
CheckConnectionLimit checks if a new connection is allowed.
func (*ConnectionTracker) DecrementConnection ¶
func (t *ConnectionTracker) DecrementConnection(databaseName string)
DecrementConnection decrements the connection count for a database.
func (*ConnectionTracker) GetConnectionCount ¶
func (t *ConnectionTracker) GetConnectionCount(databaseName string) int
GetConnectionCount returns the current connection count for a database.
func (*ConnectionTracker) IncrementConnection ¶
func (t *ConnectionTracker) IncrementConnection(databaseName string)
IncrementConnection increments the connection count for a database.
func (*ConnectionTracker) TryIncrementConnection ¶
func (t *ConnectionTracker) TryIncrementConnection(manager *DatabaseManager, databaseName string) error
TryIncrementConnection checks the connection limit and increments atomically.
This avoids the race window of a "check-then-increment" pattern under concurrent connection attempts.
type ConstituentRef ¶
type ConstituentRef struct {
// Alias is the name used within the composite database to reference this constituent.
Alias string `json:"alias"`
// DatabaseName is the actual database name (or alias) that this constituent points to.
DatabaseName string `json:"database_name"`
// Type is the type of constituent: "local" (same instance) or "remote" (another instance).
Type string `json:"type"` // "local", "remote"
// AccessMode controls what operations are allowed: "read", "write", "read_write".
AccessMode string `json:"access_mode"` // "read", "write", "read_write"
// URI points to the remote NornicDB endpoint when Type == "remote".
URI string `json:"uri,omitempty"`
// SecretRef identifies credentials/token material for remote access.
// The actual secret is resolved outside of metadata persistence.
SecretRef string `json:"secret_ref,omitempty"`
// User and Password implement Neo4j-style explicit remote auth:
// ... AT '<url>' USER <user> PASSWORD '<password>'
//
// Password is encrypted before persisting metadata to the system namespace.
// At runtime, DatabaseManager decrypts it before invoking RemoteEngineFactory.
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
// AuthMode defines remote auth behavior:
// - "oidc_forwarding": forward caller Authorization header
// - "user_password": use explicit User/Password for outbound Basic auth
// Empty is treated as "oidc_forwarding" for remote constituents.
AuthMode string `json:"auth_mode,omitempty"`
}
ConstituentRef represents a reference to a constituent database within a composite database.
func (*ConstituentRef) Validate ¶
func (c *ConstituentRef) Validate() error
Validate validates a constituent reference.
type DatabaseInfo ¶
type DatabaseInfo struct {
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by,omitempty"`
Status string `json:"status"` // "online", "offline"
Type string `json:"type"` // "standard", "system"
IsDefault bool `json:"is_default"`
NodeCount int64 `json:"node_count,omitempty"` // Cached, may be stale
UpdatedAt time.Time `json:"updated_at"`
Aliases []string `json:"aliases,omitempty"` // Database aliases (Neo4j-compatible)
Limits *Limits `json:"limits,omitempty"` // Resource limits
Constituents []ConstituentRef `json:"constituents,omitempty"` // Constituent databases (for composite type)
// contains filtered or unexported fields
}
DatabaseInfo holds metadata about a database.
type DatabaseManager ¶
type DatabaseManager struct {
// contains filtered or unexported fields
}
DatabaseManager manages multiple logical databases within a single storage engine.
It provides:
- Database creation and deletion
- Database metadata tracking
- Namespaced storage engine views
- Neo4j 4.x multi-database compatibility
Thread-safe: all operations are protected by mutex.
Example:
// Create manager with shared storage
inner := storage.NewBadgerEngine("./data")
manager := multidb.NewDatabaseManager(inner, nil)
// Create databases
manager.CreateDatabase("tenant_a")
manager.CreateDatabase("tenant_b")
// Get namespaced storage for a tenant
tenantStorage, _ := manager.GetStorage("tenant_a")
// Use storage (isolated to tenant_a)
tenantStorage.CreateNode(&storage.Node{ID: "123"})
func NewDatabaseManager ¶
func NewDatabaseManager(inner storage.Engine, config *Config) (*DatabaseManager, error)
NewDatabaseManager creates a new database manager.
Parameters:
- inner: The underlying storage engine (shared by all databases)
- config: Configuration (nil for defaults)
On creation, initializes:
- System database (for metadata)
- Default database ("nornic" by default, configurable)
func (*DatabaseManager) AddConstituent ¶
func (m *DatabaseManager) AddConstituent(compositeName string, constituent ConstituentRef) error
AddConstituent adds a constituent to an existing composite database.
func (*DatabaseManager) CreateAlias ¶
func (m *DatabaseManager) CreateAlias(alias, databaseName string) error
CreateAlias creates an alias for a database (Neo4j-compatible).
func (*DatabaseManager) CreateCompositeDatabase ¶
func (m *DatabaseManager) CreateCompositeDatabase(name string, constituents []ConstituentRef) error
CreateCompositeDatabase creates a new composite database.
A composite database is a virtual database that spans multiple constituent databases. Queries against a composite database transparently access data from all constituents.
Parameters:
- name: The name of the composite database (must be unique)
- constituents: List of constituent database references
Returns ErrDatabaseExists if a database with this name already exists. Returns ErrInvalidDatabaseName if the name is invalid. Returns an error if any constituent database doesn't exist.
func (*DatabaseManager) CreateDatabase ¶
func (m *DatabaseManager) CreateDatabase(name string) error
CreateDatabase creates a new database.
Parameters:
- name: Database name (must be unique, lowercase recommended)
Returns ErrDatabaseExists if database already exists. Returns ErrMaxDatabasesReached if limit exceeded.
func (*DatabaseManager) DecrementStorageSize ¶
func (m *DatabaseManager) DecrementStorageSize(databaseName string, nodeSize, edgeSize int64)
DecrementStorageSize decrements the tracked storage size for a database.
This should be called after successful node/edge deletion to maintain accurate size tracking for MaxBytes limit enforcement. The sizes should be the same values that were used when the entities were created.
Parameters:
- databaseName: The database to update
- nodeSize: Size in bytes of the node that was deleted (0 if no node deleted)
- edgeSize: Size in bytes of the edge that was deleted (0 if no edge deleted)
Example:
// After successfully deleting a node (size known from creation)
manager.DecrementStorageSize("tenant_a", nodeSize, 0)
// After successfully deleting an edge (size known from creation)
manager.DecrementStorageSize("tenant_a", 0, edgeSize)
Thread-safe: This method is safe to call from multiple goroutines. Defensive: Size is prevented from going negative (resets to 0 if underflow).
func (*DatabaseManager) DefaultDatabaseName ¶
func (m *DatabaseManager) DefaultDatabaseName() string
DefaultDatabaseName returns the default database name.
func (*DatabaseManager) DropAlias ¶
func (m *DatabaseManager) DropAlias(alias string) error
DropAlias removes an alias (Neo4j-compatible).
func (*DatabaseManager) DropCompositeDatabase ¶
func (m *DatabaseManager) DropCompositeDatabase(name string) error
DropCompositeDatabase removes a composite database.
This only removes the composite database metadata. The constituent databases remain unchanged.
func (*DatabaseManager) DropDatabase ¶
func (m *DatabaseManager) DropDatabase(name string) error
DropDatabase removes a database and all its data.
Parameters:
- name: Database name to drop
Returns ErrDatabaseNotFound if database doesn't exist. Returns ErrCannotDropSystemDB for system/default databases.
func (*DatabaseManager) Exists ¶
func (m *DatabaseManager) Exists(name string) bool
Exists checks if a database exists.
func (*DatabaseManager) ExistsOrIsConstituent ¶
func (m *DatabaseManager) ExistsOrIsConstituent(name string) bool
ExistsOrIsConstituent returns true if the name refers to an existing database or a valid composite constituent in dotted "composite.alias" form. This is used by protocol entry paths (Bolt/HTTP) to avoid premature DB-not-found rejection for constituent graph references.
func (*DatabaseManager) GetCompositeConstituents ¶
func (m *DatabaseManager) GetCompositeConstituents(compositeName string) ([]ConstituentRef, error)
GetCompositeConstituents returns the list of constituents for a composite database.
func (*DatabaseManager) GetDatabase ¶
func (m *DatabaseManager) GetDatabase(name string) (*DatabaseInfo, error)
GetDatabase returns info for a specific database.
func (*DatabaseManager) GetDatabaseLimits ¶
func (m *DatabaseManager) GetDatabaseLimits(databaseName string) (*Limits, error)
GetDatabaseLimits returns resource limits for a database.
func (*DatabaseManager) GetDefaultStorage ¶
func (m *DatabaseManager) GetDefaultStorage() (storage.Engine, error)
GetDefaultStorage returns storage for the default database.
func (*DatabaseManager) GetStorage ¶
func (m *DatabaseManager) GetStorage(name string) (storage.Engine, error)
GetStorage returns a namespaced storage engine for the specified database.
The returned engine is scoped to the database - all operations only affect data within that namespace.
func (*DatabaseManager) GetStorageSize ¶
func (m *DatabaseManager) GetStorageSize(databaseName string) (int64, int64, int64)
GetStorageSize returns the current tracked storage size for a database.
Returns:
- totalSize: Total storage size in bytes (sum of all nodes and edges)
- nodeSize: Total size of all nodes in bytes
- edgeSize: Total size of all edges in bytes
The size is tracked incrementally and initialized lazily on first access. This provides O(1) access for limit checking without recalculating from all entities.
Example:
totalSize, nodeSize, edgeSize := manager.GetStorageSize("tenant_a")
fmt.Printf("Database uses %d bytes (%d from nodes, %d from edges)\n",
totalSize, nodeSize, edgeSize)
Thread-safe: This method is safe to call from multiple goroutines.
func (*DatabaseManager) GetStorageWithAuth ¶
GetStorageWithAuth returns a storage engine for the specified database and forwards authToken to remote constituent factories when composite databases include remotes.
func (*DatabaseManager) IncrementStorageSize ¶
func (m *DatabaseManager) IncrementStorageSize(databaseName string, nodeSize, edgeSize int64)
IncrementStorageSize increments the tracked storage size for a database.
This should be called after successful node/edge creation to maintain accurate size tracking for MaxBytes limit enforcement. The sizes should be calculated using the same gob encoding used by the storage engine.
Parameters:
- databaseName: The database to update
- nodeSize: Size in bytes of the node that was created (0 if no node created)
- edgeSize: Size in bytes of the edge that was created (0 if no edge created)
Example:
// After successfully creating a node
nodeSize, _ := calculateNodeSize(node)
manager.IncrementStorageSize("tenant_a", nodeSize, 0)
// After successfully creating an edge
edgeSize, _ := calculateEdgeSize(edge)
manager.IncrementStorageSize("tenant_a", 0, edgeSize)
Thread-safe: This method is safe to call from multiple goroutines.
func (*DatabaseManager) IsCompositeDatabase ¶
func (m *DatabaseManager) IsCompositeDatabase(name string) bool
IsCompositeDatabase checks if a database is a composite database.
func (*DatabaseManager) ListAliases ¶
func (m *DatabaseManager) ListAliases(databaseName string) map[string]string
ListAliases returns all aliases for a database, or all aliases if database is empty.
func (*DatabaseManager) ListCompositeDatabases ¶
func (m *DatabaseManager) ListCompositeDatabases() []*DatabaseInfo
ListCompositeDatabases returns all composite databases.
func (*DatabaseManager) ListDatabases ¶
func (m *DatabaseManager) ListDatabases() []*DatabaseInfo
ListDatabases returns all database info.
func (*DatabaseManager) RemoveConstituent ¶
func (m *DatabaseManager) RemoveConstituent(compositeName string, alias string) error
RemoveConstituent removes a constituent from a composite database.
func (*DatabaseManager) ResolveDatabase ¶
func (m *DatabaseManager) ResolveDatabase(nameOrAlias string) (string, error)
ResolveDatabase resolves an alias or database name to the actual database name.
func (*DatabaseManager) SetDatabaseLimits ¶
func (m *DatabaseManager) SetDatabaseLimits(databaseName string, limits *Limits) error
SetDatabaseLimits sets resource limits for a database.
func (*DatabaseManager) SetDatabaseStatus ¶
func (m *DatabaseManager) SetDatabaseStatus(name, status string) error
SetDatabaseStatus sets a database online/offline.
type FullScanRouting ¶
type FullScanRouting struct{}
FullScanRouting routes all queries to all constituents. This is the default strategy when no routing rules are configured.
func NewFullScanRouting ¶
func NewFullScanRouting() *FullScanRouting
NewFullScanRouting creates a new full-scan routing strategy.
func (*FullScanRouting) RouteQuery ¶
func (r *FullScanRouting) RouteQuery(queryInfo *QueryInfo) []string
RouteQuery returns nil, indicating all constituents should be queried.
func (*FullScanRouting) RouteWrite ¶
func (r *FullScanRouting) RouteWrite(operation string, labels []string, properties map[string]interface{}) string
RouteWrite returns empty string, indicating write should go to first writable constituent.
type LabelRouting ¶
type LabelRouting struct {
// contains filtered or unexported fields
}
LabelRouting routes queries based on node labels. Each label is mapped to one or more constituent aliases.
func NewLabelRouting ¶
func NewLabelRouting() *LabelRouting
NewLabelRouting creates a new label-based routing strategy.
func (*LabelRouting) RouteQuery ¶
func (r *LabelRouting) RouteQuery(queryInfo *QueryInfo) []string
RouteQuery routes a query based on labels.
func (*LabelRouting) RouteWrite ¶
func (r *LabelRouting) RouteWrite(operation string, labels []string, properties map[string]interface{}) string
RouteWrite routes a write operation based on labels.
func (*LabelRouting) SetLabelRouting ¶
func (r *LabelRouting) SetLabelRouting(label string, constituents []string)
SetLabelRouting configures which constituents should be queried for a given label.
type LimitChecker ¶
type LimitChecker interface {
// CheckStorageLimits checks if storage operations are within limits.
// Returns error if limit would be exceeded.
// For create operations, pass the node/edge being created to calculate exact size.
CheckStorageLimits(operation string, node *storage.Node, edge *storage.Edge) error
// CheckQueryLimits checks if query execution is allowed.
// Returns error if limit would be exceeded.
CheckQueryLimits(ctx context.Context) (context.Context, context.CancelFunc, error)
// GetQueryLimits returns the query limits for this database.
GetQueryLimits() *QueryLimits
// GetRateLimits returns the rate limits for this database.
GetRateLimits() *RateLimits
// CheckQueryRate checks if query rate limit is allowed.
CheckQueryRate() error
// CheckWriteRate checks if write rate limit is allowed.
CheckWriteRate() error
}
LimitChecker provides an interface for checking resource limits. This allows storage engines to check limits without depending on DatabaseManager. It implements both storage.LimitChecker and storage.QueryLimitChecker.
type Limits ¶
type Limits struct {
Storage StorageLimits `json:"storage,omitempty"`
Query QueryLimits `json:"query,omitempty"`
Connection ConnectionLimits `json:"connection,omitempty"`
Rate RateLimits `json:"rate,omitempty"`
}
Limits holds resource limits for a database.
All limits are optional (0 = unlimited). Limits are enforced at runtime to prevent any single database from consuming excessive resources.
Example:
limits := &Limits{
Storage: StorageLimits{
MaxNodes: 1000000,
MaxEdges: 5000000,
MaxBytes: 10 * 1024 * 1024 * 1024, // 10GB
},
Query: QueryLimits{
MaxQueryTime: 60 * time.Second,
MaxResults: 10000,
MaxConcurrentQueries: 10,
},
Connection: ConnectionLimits{
MaxConnections: 50,
},
Rate: RateLimits{
MaxQueriesPerSecond: 100,
MaxWritesPerSecond: 50,
},
}
func DefaultLimits ¶
func DefaultLimits() *Limits
DefaultLimits returns default limits (all unlimited).
func (*Limits) GetMaxResults ¶
GetMaxResults returns the maximum number of results (for interface compatibility).
func (*Limits) IsUnlimited ¶
IsUnlimited returns true if all limits are unlimited (default state).
type PropertyRouting ¶
type PropertyRouting struct {
// contains filtered or unexported fields
}
PropertyRouting routes queries based on property values. Useful for database/shard-based routing (e.g., database_id property).
func NewPropertyRouting ¶
func NewPropertyRouting(propertyName string) *PropertyRouting
NewPropertyRouting creates a new property-based routing strategy.
func (*PropertyRouting) RouteQuery ¶
func (r *PropertyRouting) RouteQuery(queryInfo *QueryInfo) []string
RouteQuery routes a query based on property values.
func (*PropertyRouting) RouteWrite ¶
func (r *PropertyRouting) RouteWrite(operation string, labels []string, properties map[string]interface{}) string
RouteWrite routes a write operation based on property values.
func (*PropertyRouting) SetDefaultConstituent ¶
func (r *PropertyRouting) SetDefaultConstituent(constituent string)
SetDefaultConstituent sets the default constituent for values not in the map.
func (*PropertyRouting) SetPropertyRouting ¶
func (r *PropertyRouting) SetPropertyRouting(value interface{}, constituent string)
SetPropertyRouting configures which constituent should be queried for a property value.
type QueryInfo ¶
type QueryInfo struct {
// Labels referenced in the query
Labels []string
// Properties used in WHERE clauses or CREATE/MERGE operations
Properties map[string]interface{}
// Property names that might be used for routing
PropertyNames []string
// Whether this is a write operation
IsWrite bool
// Whether this is a full scan (no specific labels/properties)
IsFullScan bool
}
QueryInfo contains information extracted from a Cypher query for routing decisions.
type QueryLimits ¶
type QueryLimits struct {
// MaxQueryTime is the maximum query execution time (0 = unlimited).
MaxQueryTime time.Duration `json:"max_query_time,omitempty"`
// MaxResults is the maximum number of results returned (0 = unlimited).
MaxResults int64 `json:"max_results,omitempty"`
// MaxConcurrentQueries is the maximum concurrent queries (0 = unlimited).
MaxConcurrentQueries int `json:"max_concurrent_queries,omitempty"`
}
QueryLimits controls query execution per database.
func (*QueryLimits) GetMaxResults ¶
func (q *QueryLimits) GetMaxResults() int64
GetMaxResults returns the maximum number of results (for interface compatibility).
type RateLimits ¶
type RateLimits struct {
// MaxQueriesPerSecond is the maximum queries per second (0 = unlimited).
MaxQueriesPerSecond int `json:"max_queries_per_second,omitempty"`
// MaxWritesPerSecond is the maximum writes per second (0 = unlimited).
MaxWritesPerSecond int `json:"max_writes_per_second,omitempty"`
}
RateLimits controls request rate per database.
type RemoteEngineFactory ¶
type RemoteEngineFactory func(ref ConstituentRef, authToken string) (storage.Engine, error)
RemoteEngineFactory creates storage engines for remote composite constituents. authToken is the original caller's auth token/header value, forwarded to preserve authentication context across distributed constituent queries.
type RoutingStrategy ¶
type RoutingStrategy interface {
// RouteQuery determines which constituents should be queried for a given operation.
// Returns list of constituent aliases to query.
RouteQuery(queryInfo *QueryInfo) []string
// RouteWrite determines which constituent should receive a write operation.
// Returns constituent alias, or empty string if routing is ambiguous (full scan).
RouteWrite(operation string, labels []string, properties map[string]interface{}) string
}
RoutingStrategy defines how queries are routed to constituent databases.
type StorageLimits ¶
type StorageLimits struct {
// MaxNodes is the maximum number of nodes allowed (0 = unlimited).
// When exceeded, create operations fail with: "has reached max_nodes limit (N/M)"
MaxNodes int64 `json:"max_nodes,omitempty"`
// MaxEdges is the maximum number of edges allowed (0 = unlimited).
// When exceeded, create operations fail with: "has reached max_edges limit (N/M)"
MaxEdges int64 `json:"max_edges,omitempty"`
// MaxBytes is the maximum storage size in bytes (0 = unlimited).
// When exceeded, create operations fail with: "would exceed max_bytes limit
// (current: X bytes, limit: Y bytes, new entity: Z bytes)"
//
// Size is calculated exactly using gob serialization (matching storage format).
// No estimation - the actual size of each entity is known before creation.
MaxBytes int64 `json:"max_bytes,omitempty"`
}
StorageLimits controls storage capacity per database.
All limits are enforced at runtime. When a limit is exceeded, the operation fails with a clear error message explaining the quota limits.
MaxBytes Enforcement:
MaxBytes enforcement uses exact size calculation, not estimation. The actual serialized size of each node/edge is calculated using gob encoding (matching the storage format). Storage size is tracked incrementally for O(1) limit checks.
Example:
limits := StorageLimits{
MaxNodes: 1000000, // 1 million nodes
MaxEdges: 5000000, // 5 million edges
MaxBytes: 10 * 1024 * 1024 * 1024, // 10GB
}