Skip to content

more caching to improve performance#868

Open
Remo wants to merge 4 commits intoflat3:5.xfrom
ortic:performance-optimization-pr
Open

more caching to improve performance#868
Remo wants to merge 4 commits intoflat3:5.xfrom
ortic:performance-optimization-pr

Conversation

@Remo
Copy link
Copy Markdown
Contributor

@Remo Remo commented Mar 11, 2026

Full disclaimer: Code was generated by AI. I did test it on a real project myself though.

Performance Optimizations

This branch introduces several optimizations to reduce the overhead of LOData's discovery and bootstrapping process, which previously ran expensive operations on every HTTP request.

Problem

With many registered entity models (e.g. 50+), every request triggered:

  1. DBAL schema introspectionlistTableDetails() queried database metadata for each table
  2. Column-to-property conversion — Each DBAL column was mapped to an OData DeclaredProperty, involving type resolution, default value handling, and annotation setup
  3. Eloquent-specific processing — Hidden/visible filtering, cast overrides, and model defaults were applied per-model
  4. Reflection scanningOperation::discover() scanned each model class for LodataOperation attributes via reflection
  5. Repeated object creationgetModel() called App::make() and getDatabase() created a new DBAL connection on every invocation

The existing Discovery::remember() cache (controlled by LODATA_DISCOVERY_TTL) only cached the raw DBAL Table object — all subsequent processing still ran on every request.

Changes

1. Full Property Discovery Caching (SQLSchema)

Files: src/Drivers/SQL/SQLSchema.php

Replaced the old two-step approach (cache raw Table, re-process every request) with a fully cached pipeline:

  • buildPropertyDescriptors() — Produces a serializable array of property descriptors containing type names, nullability, defaults, source names, and key information. This is the expensive part that gets cached.
  • discoverProperties() — Caches the full descriptor array via Discovery::remember(), then hydrates into OData DeclaredProperty objects (cheap).
  • columnToTypeName() — Maps DBAL column types to string type names for serialization.
  • resolveType() — Resolves type name strings back to OData Type instances.
  • resolveColumnName() — Handles namespaced column name resolution.

Cache key format: sql.properties.{connection}.{table}

2. Eloquent-Specific Discovery Caching (EloquentEntitySet)

Files: src/Drivers/EloquentEntitySet.php

Overrides the SQLSchema trait methods to incorporate Eloquent-specific logic into the cached descriptors:

  • buildPropertyDescriptors() — Extends the base descriptors with:
    • Hidden/visible property filtering
    • Eloquent cast type overrides (for both properties and primary keys)
    • Model default values from $model->getAttributeValue()
  • discoverProperties() — Uses model-scoped cache keys to avoid collisions between models sharing the same table.
  • castToTypeName() — Maps Eloquent cast names (boolean, array, integer, datetime, enums, etc.) to serializable type name strings.
  • resolveType() — Handles Eloquent-specific type names like collection_string, double, enum:ClassName, int64_or_uint64.

Cache key format: sql.properties.{connection}.{table}.{modelClass}

3. Model Instance Caching (EloquentEntitySet)

Files: src/Drivers/EloquentEntitySet.php

getModel() previously called App::make() on every invocation, creating a new Eloquent model instance each time. Now caches the instance in a $modelInstance property.

4. DBAL Connection Caching (SQLConnection)

Files: src/Drivers/SQL/SQLConnection.php

getDatabase() previously created a new DBAL instance (wrapping DoctrineConnection/PDO) on every call. Now caches the instance in a $dbalInstance property.

5. Operation Discovery Fast-Path (Operation)

Files: src/Operation.php

Added an early-exit check in Operation::discover(): before entering the full reflection loop (which calls Discovery::getReflectedMethods() and iterates all public methods), first scans for LodataOperation attributes. If none are found — which is the common case for Eloquent models — the method returns immediately, skipping the expensive reflection loop entirely.

Cache Invalidation

After database schema changes (e.g. migrations), the discovery cache must be cleared:

php artisan cache:clear

The cache TTL is controlled by LODATA_DISCOVERY_TTL in your .env:

Value Behavior
0 (default) No caching
null Cache forever (until manually cleared)
300 Cache for 300 seconds

Performance Impact

With 58 registered entity models, measured on an OData endpoint (/odata/Users?$top=1):

Scenario Response Time
Cold boot (empty cache) ~730ms
Warm boot (cached) ~90ms

@Remo Remo marked this pull request as draft March 11, 2026 04:49
@Remo Remo marked this pull request as ready for review March 11, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant