Skip to content

Task/dogpile orm event invalidation - #3029

Closed
whabanks wants to merge 20 commits into
mainfrom
task/dogpile-orm-event-invalidation
Closed

Task/dogpile orm event invalidation#3029
whabanks wants to merge 20 commits into
mainfrom
task/dogpile-orm-event-invalidation

Conversation

@whabanks

@whabanks whabanks commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary | Résumé

This PR experimentally implements an ORM-event driven invalidation strategy for Service entities. Rather than calling an explicit invalidate_service_cache_keys() function on every path that mutates service and service related data; we instead listen to after_flush, after_commit, and after_rollback sqlalchemy session events, weaving cache invalidation calls into that process. (similar in nature to aspect oriented programming)

Note:
it looks messy currently because it was rebased with 3011 in order to be testable. The bulk of the changes are in:

This nets us:

  • Centralized invalidation logic
  • Reduced code complexity, and improve maintainability particularly on larger models where multiple mutating code paths are involved
  • Tighter control over when we invalidate i.e. if a transaction is rolled back there's no need to invalidate existing cache
  • Since we're handling this logic much closer to the actual time the DB is updated, we reduce the chances of stale cache reads after a successful write.

Related Issues | Cartes liées

Test instructions | Instructions pour tester la modification

  1. Access a service, note the cache key is created
  2. Navigate to service settings and turn off email sending (or change any setting) -> note the cache key was updated
  3. Add or remove a team member from a service -> note the cache key reflects this change

Release Instructions | Instructions pour le déploiement

None.

Reviewer checklist | Liste de vérification du réviseur

  • This PR does not break existing functionality.
  • This PR does not violate GCNotify's privacy policies.
  • This PR does not raise new security concerns. Refer to our GC Notify Risk Register document on our Google drive.
  • This PR does not significantly alter performance.
  • Additional required documentation resulting of these changes is covered (such as the README, setup instructions, a related ADR or the technical documentation).

⚠ If boxes cannot be checked off before merging the PR, they should be moved to the "Release Instructions" section with appropriate steps required to verify before release. For example, changes to celery code may require tests on staging to verify that performance has not been affected.

whabanks and others added 16 commits August 18, 2026 16:12
- Add dogpile-cache to pyproject.toml
- Set up a basic, redis-backed, dogpile cache region with
  `distributed_lock` turned on and `thread_local_lock` turned off to
play nicely in a threaded environment
- Implement a custom cache-key generation strategy to try and match
  our current cache key patterns
- Implement dogpile cached version of `dao_fetch_service_by_id`
- Invalidate cache whenever a service's settings are updated
Instead of implementing per-model/per-dao method invalidation
strategies, implement a broader strategy to allow us to, for example,
invalidate all service related keys when a service is updated.
- Implemented basic json serializer / deserializer instead of
  pickling/unpickling values
- Updates the key structure to reduce redundancy and logically group
  keys by entity
- Updated tests
- Remove explicit expiration time from annotations, let region
  configuration propagate
- Ensure tests use the json serializer/deserializer
- Removed explicit call to `invalidate_group_keys` for now
- Default the cache config's REDIS_URL to localhost if not set
Implement a basic ORM event driven invalidation strategy for service
keys. Listeners are attached to the sqlalchemy session `after_flush`,
`after_commit` and `after_rollback` events which will invalidate service
cache keys under the following conditions
- `service` entity is updated/deleted
- `service_permissions` are updated
- `service_user` list is added to, updated, or removed from
Comment thread app/cache/service_cache_events.py Fixed
Comment thread app/cache/service_cache_events.py Fixed
Comment thread app/cache/service_cache_events.py Dismissed
Comment thread app/caching.py Fixed
Comment thread app/caching.py Fixed
Comment thread app/caching.py Fixed
Comment thread app/dao/services_dao.py Fixed
@jimleroyer

Copy link
Copy Markdown
Member

I am concerned that this approach (and the other PR too but maybe slightly less) might be difficult to scale, i.e. every time a developer wants certain cache to be added, they would have to make sure proper specific events fire, and that these are for service IDs, is that right? And when we want to add a cache specific to users, and templates, and keys, and etc.. we'd have also to add specific glue code for these? The cache isn't generic enough to handle these automatically?

@whabanks

whabanks commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

every time a developer wants certain cache to be added, they would have to make sure proper specific events fire, and that these are for service IDs, is that right?

With the current implementation, yes this would have to be done to accommodate more than just the service entity.

And when we want to add a cache specific to users, and templates, and keys, and etc.. we'd have also to add specific glue code for these? The cache isn't generic enough to handle these automatically?

The baseline dogpile library isn't smart enough to graph and evaluate the potential entity relationships and invalidate corresponding keys on it's own. One possibility to facilitate a generic implementation would be to leverage a registry that centrally stores (and also serves to document) entity <-> cache key dependency metadata.
Then developers only need to update the registry (simple JSON or YAML file?). A mapper could resolve the entity -> cache key dependencies using that registry, and generically invalidate keys / groups of keys.

Something like this:

image

The two new things here would be the registry and dependency resolver.

  • The session events are already in place
  • The change collector would be adjusted to generically map collected ids to an entity type
  • The cache invalidator would be changed to map entities to cache keys base on the registry and execute the invalidation

@whabanks

Copy link
Copy Markdown
Contributor Author

Closing in favour of this PR because I royally forked up the commit history on this PR with a prior rebase. Given there's is still much work to do I'd like to be merciful to reviewers. Will capture the current conversation in that PR as well.

@whabanks whabanks closed this Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants