Skip to main content

External App Cache Optimization

Overview

External Nextcloud apps calling ObjectService.searchObjectsPaginated() can experience slow response times due to cache thrashing. This guide explains how to optimize caching for external apps accessing OpenRegister programmatically.

The Problem

Cache Thrashing for External Apps

graph TD
A[External App 1] --> B["ObjectService (no user session)"]
C[External App 2] --> B
D[External App 3] --> B
B --> E["Cache Key: 'anonymous'"]

subgraph "Cache Thrashing"
E --> F[App 1 caches results]
F --> G[App 2 invalidates cache]
G --> H[App 3 misses cache]
H --> I[Performance degradation]
end

style I fill:#ffcdd2
style E fill:#fff3e0

Issue: All external apps without user sessions share the same 'anonymous' cache namespace, causing:

  • Cache invalidation conflicts between different apps
  • Poor cache hit rates due to mixed usage patterns
  • Slow responses (2+ seconds instead of <500ms)
  • Resource waste from repeated identical queries

The Solution

Enhanced Cache Isolation

graph TD
A[External App 1] --> B[setExternalAppContext('myapp')]
C[External App 2] --> D[setExternalAppContext('otherapp')]
E[External App 3] --> F[Auto-detection from file path]

B --> G["Cache: 'external_app_myapp'"]
D --> H["Cache: 'external_app_otherapp'"]
F --> I["Cache: 'external_app_detected'"]

subgraph "Isolated Performance"
G --> J[Fast cache hits]
H --> K[Fast cache hits]
I --> L[Fast cache hits]
end

style J fill:#c8e6c9
style K fill:#c8e6c9
style L fill:#c8e6c9

Implementation Guide

Best Performance: Explicitly set your app ID for optimal cache isolation.

<?php
// Get ObjectService from dependency injection
$objectService = \OC::$server->get(\OCA\OpenRegister\Service\ObjectService::class);

// **CRITICAL**: Set your app context for cache optimization
$objectService->setExternalAppContext('myapp');

// Now use ObjectService with optimized caching
$query = [
'@self' => ['register' => 19, 'schema' => 116],
'_limit' => 50,
'_search' => 'test query'
];

$results = $objectService->searchObjectsPaginated($query);

Method 2: Automatic Detection (Fallback)

Good Performance: The system automatically detects your app from the call stack.

<?php
// System automatically detects calling app from file path
// /apps/myapp/lib/Service/DataService.php -> 'myapp'
// /apps-extra/customapp/lib/Controller/ApiController.php -> 'customapp'

$objectService = \OC::$server->get(\OCA\OpenRegister\Service\ObjectService::class);
$results = $objectService->searchObjectsPaginated($query);
// Cache namespace: 'external_app_myapp' (auto-detected)

Method 3: Query Fingerprinting (Last Resort)

Basic Performance: For undetectable apps, uses query patterns for isolation.

<?php
// When app cannot be detected, system creates query-based cache isolation
$results = $objectService->searchObjectsPaginated($query);
// Cache namespace: 'anonymous_a1b2c3d4' (based on query pattern)

Performance Impact

Before Optimization

ScenarioCache Hit RateResponse TimeIssue
Multiple external apps~10%2-5 secondsCache thrashing
Same app, repeated calls~20%1-3 secondsShared anonymous cache
Different query patterns~5%3+ secondsPattern conflicts

After Optimization

ScenarioCache Hit RateResponse TimeImprovement
App with setExternalAppContext()~95%<500ms🚀 10x faster
Auto-detected external app~80%<750ms🚀 4x faster
Query fingerprint isolation~60%<1s🚀 3x faster

Cache Key Structure

Enhanced Cache Key Generation

// Cache key components
$keyData = [
'query' => $query, // Actual query parameters
'user' => $effectiveUserId, // Enhanced user context
'org' => $orgId, // Organization context
'version' => '1.1' // Cache version
];

// Effective user contexts:
// - Regular users: 'user123'
// - External apps: 'external_app_myapp'
// - Anonymous patterns: 'anonymous_a1b2c3d4'

Cache Isolation Benefits

graph TB
subgraph "Cache Namespaces"
A["'external_app_myapp'<br/>MyApp cache entries"]
B["'external_app_otherapp'<br/>OtherApp cache entries"]
C["'user_admin'<br/>Admin user cache entries"]
D["'anonymous_pattern1'<br/>Anonymous pattern cache"]
end

A --> E[No cache conflicts]
B --> E
C --> E
D --> E
E --> F[Optimal cache hit rates]
F --> G[Sub-500ms responses]

style F fill:#c8e6c9
style G fill:#c8e6c9

Best Practices

For External App Developers

  1. Always set app context before making ObjectService calls:

    $objectService->setExternalAppContext('your_app_name');
  2. Reuse ObjectService instances to maintain app context:

    class MyService {
    private ObjectService $objectService;

    public function __construct() {
    $this->objectService = \OC::$server->get(ObjectService::class);
    $this->objectService->setExternalAppContext('myapp');
    }
    }
  3. Use consistent app IDs across your application:

    // Good: Consistent app ID
    $objectService->setExternalAppContext('myapp');

    // Bad: Different IDs fragment cache
    $objectService->setExternalAppContext('myapp_v1');
    $objectService->setExternalAppContext('myapp_production');

For OpenRegister Integration

  1. Check cache performance in logs:

    docker logs master-nextcloud-1 | grep 'External app cache context'
  2. Monitor cache hit rates:

    docker logs master-nextcloud-1 | grep 'Cache hit - returning cached response'
  3. Verify app detection:

    docker logs master-nextcloud-1 | grep 'External app cache context determined'

Cache Invalidation Coordination

Automatic Invalidation

Cache invalidation works seamlessly for external apps:

sequenceDiagram
participant ExtApp as External App
participant ObjService as ObjectService
participant Cache as Cache Layer
participant Admin as Admin User

ExtApp->>ObjService: setExternalAppContext('myapp')
ExtApp->>ObjService: searchObjectsPaginated()
ObjService->>Cache: Store with 'external_app_myapp' key

Admin->>ObjService: Create new object
ObjService->>Cache: Invalidate all related caches
Note over Cache: Including external app caches

ExtApp->>ObjService: searchObjectsPaginated() (same query)
ObjService->>Cache: Cache miss due to invalidation
ObjService-->>ExtApp: Fresh results with new object

Cache Scope

OperationInvalidation ScopeExternal App Impact
Create/Update ObjectRegister + Schema specific✅ Affected apps only
Delete ObjectRegister + Schema specific✅ Affected apps only
Schema ChangesAll related caches✅ All apps using schema
Bulk OperationsGlobal or context-specific✅ Appropriate scope

Performance Monitoring

Debug Logging

Enable cache monitoring for external apps:

// Check if your app context is being detected
$this->logger->debug('Starting ObjectService call', [
'app' => 'myapp',
'query' => $query
]);

$objectService->setExternalAppContext('myapp');
$results = $objectService->searchObjectsPaginated($query);

Log Analysis

# Check cache performance for your app
docker logs master-nextcloud-1 | grep 'external_app_myapp'

# Monitor cache hit rates
docker logs master-nextcloud-1 | grep 'Cache hit.*myapp'

# Check for cache invalidation events
docker logs master-nextcloud-1 | grep 'cache invalidation.*completed'

Migration Guide

Updating Existing External Apps

Before (slow performance):

$objectService = \OC::$server->get(ObjectService::class);
$results = $objectService->searchObjectsPaginated($query); // Shared 'anonymous' cache

After (optimized performance):

$objectService = \OC::$server->get(ObjectService::class);
$objectService->setExternalAppContext('myapp'); // Isolated cache namespace
$results = $objectService->searchObjectsPaginated($query); // App-specific caching

Expected Performance Gains

App Usage PatternBeforeAfterImprovement
High-frequency identical queries2-3s<100ms🚀 20-30x faster
Similar query patterns1-2s<300ms🚀 5-7x faster
Mixed query patterns2-5s<500ms🚀 4-10x faster

Troubleshooting

Common Issues

Issue: Still getting slow responses after setting app context Solution: Verify app context is set before each ObjectService call

Issue: Cache hits not showing in logs
Solution: Enable debug logging and check for cache invalidation conflicts

Issue: Different response times for identical queries Solution: Ensure consistent app ID usage and check for cache invalidation timing

Verification Steps

  1. Check app context detection:

    docker logs master-nextcloud-1 | grep 'External app cache context determined'
  2. Verify cache isolation:

    docker logs master-nextcloud-1 | grep 'external_app_yourappname'
  3. Monitor response times:

    docker logs master-nextcloud-1 | grep 'searchObjectsPaginated.*completed'

Related Documentation: