ITADN

Endpoint CRUD does not invalidate CreateMessageApp cache, causing missed deliveries to new endpoints

#2191Openorhanrauf 创建于 2026-02-20
O
orhanraufcommented
## Summary When an endpoint is created (or updated/deleted), the `CreateMessageApp` cache is not invalidated. This causes the delivery worker to use a stale endpoint list for up to 30 seconds (the TTL), meaning newly created endpoints miss all messages dispatched during that window. I understand this is not a usual case. We only discovered this in our E2E test suite that creates multiple endpoints in a short period and expects immediate delivery. In normal production usage the 30s window is unlikely to matter, but for any workflow where an endpoint is created and a message follows shortly after, the delivery is silently lost. ## Reproduction 1. Set `cache_type = "memory"` (the default) or `cache_type = "redis"` 2. Send a message to an application (this populates the `SVIX_CACHE_APP_v3_{org}_{app}` cache) 3. Within 30 seconds, create a new endpoint on that application 4. Send another message **Expected:** The new endpoint receives the second message. **Actual:** The delivery worker uses the cached `CreateMessageApp` (which doesn't include the new endpoint) and never attempts delivery. The message exists in the DB but is never dispatched to the new endpoint. ## Root cause `CreateMessageApp::layered_fetch` in [`message_app.rs`](https://github.com/svix/svix-webhooks/blob/main/server/svix-server/src/core/message_app.rs) caches the application and **all its endpoints** with a 30s TTL: ```rust // worker.rs - called for every message dispatch let Some(create_message_app) = CreateMessageApp::layered_fetch( cache, db, None, msg.org_id.clone(), msg.app_id.clone(), Duration::from_secs(30), ).await? let endpoints = create_message_app .filtered_endpoints(trigger_type, &msg.event_type, msg.channels.as_ref()) ``` But the endpoint CRUD handlers in [`endpoint/crud.rs`](https://github.com/svix/svix-webhooks/blob/main/server/svix-server/src/v1/endpoints/endpoint/crud.rs) (`create_endp_from_data`, `update_endp_from_data`, and `delete_endpoint`) write to Postgres but **never invalidate or update the cache**. ## Impact This is especially problematic when: - Messages are dispatched frequently (keeping the cache permanently warm) - Endpoints are created and expected to receive events immediately (e.g., a user subscribes and triggers a job in quick succession) - Multiple instances share a Redis-backed cache ## Workaround Setting `SVIX_CACHE_TYPE=none` avoids the issue but loses the performance benefit of caching. ## Suggested fix Invalidate (delete) the `AppEndpointKey` cache entry when an endpoint is created, updated, or deleted: ```rust // In create_endp_from_data / update_endp_from_data / delete_endpoint: let cache_key = AppEndpointKey::new(&app.org_id, &app.id); let _ = cache.delete(&cache_key).await; ``` This is a one-line addition in each handler and ensures the next `layered_fetch` hits Postgres with fresh data.
2 条评论