Skip to main content

Cache Invalidation System

Overview​

The OpenRegister Cache Invalidation System provides enterprise-grade cache consistency across all CRUD operations, ensuring that cached data remains synchronized with database changes. This system was designed to solve critical performance and consistency challenges while maintaining RBAC compliance and multi-tenant isolation.

Architecture Overview​

Components​

CacheInvalidationService​

Location: lib/Service/CacheInvalidationService.php

Central service that coordinates cache invalidation across all cache layers. Provides methods for:

  • Individual object invalidation: invalidateObjectRelatedCaches()
  • Schema-related invalidation: invalidateSchemaRelatedCaches()
  • Bulk operation coordination: Used by ObjectService bulk methods
  • RBAC-compliant cache management: User and organization-aware cache keys

Integration Points​

Object CRUD Operations​

SaveObject Handler (lib/Service/ObjectHandlers/SaveObject.php):

  • Automatically invalidates caches after object create/update operations
  • Includes register, schema, and object-specific cache clearing

DeleteObject Handler (lib/Service/ObjectHandlers/DeleteObject.php):

  • Automatically invalidates caches after object deletion
  • Ensures deleted objects disappear from collection queries immediately

Schema CRUD Operations​

SchemasController (lib/Controller/SchemasController.php):

  • Invalidates schema-related caches on schema create/update/delete
  • Coordinates with dependent cache layers (collections, facets)

Bulk Operations​

ObjectService (lib/Service/ObjectService.php):

  • saveObjects(): Invalidates caches after bulk create/update operations
  • deleteObjects(): Clears collection caches after mass deletion
  • publishObjects(): Updates facet caches after bulk publication changes
  • depublishObjects(): Synchronizes visibility caches after bulk depublication

Cache Types and Invalidation Strategies​

Search/Collection Cache​

Purpose: Caches complete API responses for collection endpoints Key Pattern: obj_search_{hash} with user and organization context Invalidation: Cleared on any object CRUD operation affecting the collection

Cache Keys:

  • Include user ID for RBAC isolation
  • Include organization ID for multi-tenancy
  • Include query hash for request-specific caching

Entity Cache​

Purpose: Caches frequently accessed schemas and registers Key Pattern: entity_{type}_{ids} Invalidation: Cleared when specific entities are modified

Facet Response Cache​

Purpose: Caches facet calculation results for complex aggregations Key Pattern: Includes query filters and user context Invalidation: Cleared when underlying data changes affect facet counts

Schema-Specific Caches​

Purpose:

  • Schema Config Cache: Schema configuration data
  • Schema Facet Cache: Pre-computed facetable field definitions

Invalidation: Cleared when schemas are created, updated, or deleted

RBAC Compliance​

User-Based Cache Keys​

All cache keys include user and organization context to ensure data isolation:

$keyData = [
'query' => $query,
'user' => $userId, // RBAC isolation
'org' => $orgId, // Multi-tenancy isolation
'version' => '1.0' // Cache versioning
];

Anonymous User Handling​

  • Anonymous users get separate cache namespace
  • Authenticated users have individual cache isolation
  • Cross-organization access prevented through cache key separation

Performance Benefits​

Before Implementation​

  • Stale data: New objects not visible until cache expiry (5+ minutes)
  • Inconsistent facets: Facet counts don't reflect recent changes
  • Cross-user data leakage: RBAC violations through shared caches
  • Bulk operation invisibility: Mass imports invisible until manual cache clear

After Implementation​

MetricBeforeAfterImprovement
New object visibility5+ minutesImmediate100% instant
Facet accuracyStale countsReal-timeAlways accurate
RBAC compliance❌ Shared caches✅ Isolated100% secure
Bulk operation consistency❌ Invisible✅ Immediate100% consistent

Performance Monitoring​

Detailed logging provides visibility into cache invalidation performance:

$this->logger->info('Object cache invalidation completed', [
'register' => $registerId,
'schema' => $schemaId,
'operation' => $operation,
'executionTime' => $executionTime . 'ms',
'invalidatedCaches' => $invalidatedCaches
]);

Usage Examples​

Individual Object Operations​

Cache invalidation happens automatically:

// Create object - caches automatically invalidated
$object = $objectService->createFromArray($data, [], $register, $schema);

// Update object - caches automatically invalidated
$object = $objectService->updateFromArray($uuid, $data, true);

// Delete object - caches automatically invalidated
$result = $objectService->deleteObject($uuid);

Bulk Operations​

Cache invalidation coordinates across bulk operations:

// Bulk save - collection caches invalidated after completion
$result = $objectService->saveObjects($objectsArray, $register, $schema);

// Bulk delete - global cache invalidation
$result = $objectService->deleteObjects($uuidArray);

// Bulk publish - facet caches updated
$result = $objectService->publishObjects($uuidArray, new \DateTime());

Schema Operations​

Schema changes trigger comprehensive cache invalidation:

// Schema update - affects facets, collections, and entity caches
$schema = $schemaMapper->updateFromArray($id, $schemaData);
// Cache invalidation happens automatically in SchemasController

// Schema delete - all dependent caches cleared
$schemaMapper->delete($schema);
// Comprehensive cache cleanup happens automatically

Cache Invalidation Flow​

Individual Object Flow​

Schema Change Flow​

Bulk Operation Flow​

Cache Key Patterns​

Collection Cache Keys​

// Pattern: obj_search_{hash}
// Hash includes: query + user + organization + version
'obj_search_abc123def456...'

Entity Cache Keys​

// Specific entities
'entity_schema_42'
'entity_register_15'

// Multiple entities
'entity_schema_md5(42,43,44)'

// All entities
'entity_schema_all'
'entity_register_all'

Facet Cache Keys​

// Pattern includes user and organization context
'facet_{userId}_{orgId}_{queryHash}'

Error Handling​

Non-Blocking Cache Operations​

Cache invalidation failures never block the primary operation:

try {
// Perform cache invalidation
$this->cacheInvalidationService->invalidateObjectRelatedCaches(/*...*/);
} catch (\Exception $e) {
// Log error but continue with primary operation
$this->logger->warning('Cache invalidation failed', ['error' => $e->getMessage()]);
}

Graceful Degradation​

  • Cache unavailable: System continues without caching
  • Partial invalidation failures: Logs warnings but continues
  • Invalid cache data: Automatically falls back to database

Configuration​

Cache TTL Settings​

// Response cache (collection queries)
private const CACHE_TTL = 300; // 5 minutes

// Entity cache (schemas/registers)
private const ENTITY_CACHE_TTL = 900; // 15 minutes

Environment Detection​

The system automatically detects slow environments and adjusts caching strategies:

private function isSlowEnvironment(): bool
{
// Checks for AC environment variables
// Measures database response time
// Adjusts caching aggressiveness accordingly
}

Monitoring and Debugging​

Debug Logging​

Enable detailed cache logging by checking Docker logs:

# View real-time cache invalidation logs
docker logs -f master-nextcloud-1 | grep -E "(cache.*invalidation|Cache.*invalidation)"

# View recent cache operations
docker logs master-nextcloud-1 --since 10m | grep 'invalidation'

Performance Metrics​

The system logs detailed performance metrics:

{
"message": "Object cache invalidation completed",
"register": 19,
"schema": 116,
"operation": "create",
"executionTime": "2.3ms",
"invalidatedCaches": {
"search": 15,
"facets": 8,
"entity": 2
}
}

Best Practices​

For Developers​

  1. Always use the service methods: Don't bypass cache invalidation by calling mappers directly
  2. Monitor performance: Use debug logging to track cache invalidation overhead
  3. Test cache scenarios: Verify that changes are immediately visible in collection calls

For Operations​

  1. Cache warming: Popular queries are automatically cached on first access
  2. Memory monitoring: Entity caches use distributed storage (Redis/Memcached in production)
  3. Cache clearing: Use clearAllCaches() method for maintenance operations

Future Enhancements​

Planned Improvements​

  • Tagged caching: Use cache tags for more selective invalidation
  • Reverse mapping: Track cache dependencies for precise invalidation
  • Metrics collection: Expose cache hit/miss rates through API
  • Cache warming strategies: Proactive cache population for popular queries

Extension Points​

The CacheInvalidationService can be extended for additional cache types:

public function invalidateCustomCache(string $cacheType, $identifier): void
{
// Add custom cache invalidation logic
}

External App Optimization​

Cache Isolation for External Apps​

Problem Solved: External apps calling ObjectService directly were experiencing cache thrashing due to shared 'anonymous' cache namespace.

Solution Implemented: Enhanced cache key generation with app-specific isolation:

Usage for External Apps​

// **RECOMMENDED**: Set explicit app context
$objectService = \OC::$server->get(\OCA\OpenRegister\Service\ObjectService::class);
$objectService->setExternalAppContext('myapp');
$results = $objectService->searchObjectsPaginated($query);

Performance Impact: External apps can see 10-30x performance improvements with proper app context.

For detailed implementation guide, see: External App Cache Optimization


Related Documentation: