Repository navigation
Resilience layer - #245
Resilience layer#245swnsma wants to merge 13 commits into
Conversation
- async communication with sentry - cirquite breaker if Sentry is out - cover changes with tests
Enforced strict sync or async delivery modes in ResilientTransport. Dropped events immediately when circuit was open or sync HTTP failed instead of falling back to message queue. Cleaned up documentation slop.
…on from module.xml
Resilience layer
|
I like the idea. The MR also contains a AGENTS.md - should maybe be separated and discussed in another PR. |
indykoning
left a comment
There was a problem hiding this comment.
Thanks for the meaningful contribution!
While a broken sentry server does not break the frontend it does slow it down.
As i do want to thoroughly test it i won't be able to merge it yet, but i hope to get to it soon 🙂
b15b8db to
753f183
Compare
…ayer Keep resilient transport and wire upstream TrackingHttpClient through it so the admin test button can still inspect the last Sentry HTTP response.
indykoning
left a comment
There was a problem hiding this comment.
I've tested it out, in my case i'm observing 4 messages when i run into a frontend error:
- The exception
- The Exception log message
- Profiling information
- Log messages
It's a lot of data for a single request, 4 messages with a large payload.
However this is not something we can/should improve on, i think we may want to warn the user to set good message lifetimes and ensure the message queue cleanup is running when using MysqlMQ.
It looks really promising from my testing!
| <consumer name="justbetter.sentry.event" | ||
| queue="justbetter.sentry.event" | ||
| handler="JustBetter\Sentry\Model\Queue\Consumer\SentryEventConsumer::process" | ||
| consumerInstance="Magento\Framework\MessageQueue\Consumer" | ||
| maxMessages="500"/> |
There was a problem hiding this comment.
Looking at Magento's queue_consumer.xml files i don't see the consumerInstance or maxMessages
I think i'd prefer that as well so it's up to the user what value they want here.
| <consumer name="justbetter.sentry.event" | |
| queue="justbetter.sentry.event" | |
| handler="JustBetter\Sentry\Model\Queue\Consumer\SentryEventConsumer::process" | |
| consumerInstance="Magento\Framework\MessageQueue\Consumer" | |
| maxMessages="500"/> | |
| <consumer name="justbetter.sentry.event" | |
| queue="justbetter.sentry.event" | |
| handler="JustBetter\Sentry\Model\Queue\Consumer\SentryEventConsumer::process"/> |
| // Never wrap our own async Sentry delivery — that would nest envelopes and risk recursion. | ||
| if ($topic === SentryEventPublisher::TOPIC_NAME) { | ||
| return [$topic, $envelopes]; | ||
| } |
There was a problem hiding this comment.
Do we need this for more listeners?
Cases i can think of (but luckily haven't observed) are:
- Errors thrown while running the queue message
- Log messages written while running the queue message
There was a problem hiding this comment.
Good point.
I have added a sending guard to prevent reporting logs from being written while scheduling a message or in the consumer.
|
|
||
| Event delivery can be configured as either synchronous or asynchronous: | ||
|
|
||
| 1. (Default) **Sync mode + circuit breaker** (`async_sending_enabled = false`): Events are sent over HTTP immediately. If `circuit_breaker_enabled` is true and consecutive failures reach `circuit_breaker_failure_threshold`, the circuit opens and subsequent requests fail fast without blocking Magento. |
There was a problem hiding this comment.
One of my preferences would've been to also unblock the user by deferring the sending to after the response has been sent.
Which works fantastic with Symfony & Laravel due to the fastcgi_finish_request():
https://github.com/symfony/http-foundation/blob/a3cef50a1c6c37b60e0457d2152c2c572dc901cb/Response.php#L408
Unfortunately this implementation isn't what Magento uses:
https://github.com/laminas/laminas-http/blob/9462fc84330d25b23383823831380abb33907fdd/src/PhpEnvironment/Response.php#L147
So i think this is indeed the best we can do.
There was a problem hiding this comment.
I also looked into it, but I was concerned that it comes as part of php-fpm, so it won't help in an non-fpm environment
| "magento/composer-dependency-version-audit-plugin": true, | ||
| "dealerdirect/phpcodesniffer-composer-installer": true | ||
| "dealerdirect/phpcodesniffer-composer-installer": true, | ||
| "magento/magento-composer-installer": true |
There was a problem hiding this comment.
I think the composer plugin here is causing the checks to fail, do we need it or can we safely disable it in the package?
d4ae459 to
79c7efb
Compare
- remove consumer configs from queue_consumer.xml - remove plugin-allow from composer.json - introduce a delivery guard to don't send messages while messages sending is in progress.
79c7efb to
806cdd4
Compare
- Consumer no longer throws; delivery failures logged to var/log/sentry.log channel excluded from Sentry by MonologPlugin. - CircuitBreaker opens immediately on 429 until Sentry retry time (new RateLimitParser); cache TTL covers backoff. - EnvelopeSender feeds breaker: 2xx success, 429 rate limit, 5xx/cURL failure, other 4xx ignored. - Consumer and async publish skip sending while circuit open. - Removed circuit_breaker_enabled config and Data::isCircuitBreakerEnabled(); breaker always on.
The consumer no longer throws and logs delivery failures to a channel that is excluded from Sentry, so it can't feed its own failures back into the queue. Any remaining capture during delivery is bounded by the circuit breaker, which opens on Sentry's 429 and stops async publishing. A process-wide guard only hid real errors raised while a message was being delivered.
A 429 for one category, e.g. an exhausted transaction quota, opened the circuit breaker for everything, so errors and cron check-ins were dropped as well and Sentry Crons reported missed check-ins. Rate limits now live in a cache-backed RateLimitState keyed by category, the same way the SDK's in-memory RateLimiter keys them, and the circuit breaker only tracks whether Sentry is reachable. Both delivery modes record responses through ResponseRecordingHttpClient, so sync mode now also honours X-Sentry-Rate-Limits / Retry-After across requests.
|
We pushed a follow-up round to the resilience layer. Here is what the five new commits do and why. 1. env.php values were ignored for the resilience settings ( 2. Fixed a 429 feedback loop in async mode (
3. Rate-limit responses are no longer logged ( 4. Replaced
Anything else captured during delivery is now bounded by the circuit breaker and the rate limits from commit 5. A process-wide guard would mostly have hidden real errors raised while a message was delivered. 5. Sentry rate limits are tracked per data category (
The README section "Resilient delivery" is updated accordingly. All unit tests in Known issue: the admin "Test" button in async mode |
|
Maybe could move rate limits handling into a separate PR, btw |
|
Hi @novikor, @indykoning, could you have a look and let us know if there is anything else that should be addressed in this PR? Thank you! |
Summary
There is a major drawback in the PHP implementation: communication with Sentry interferes with shoppers' critical paths.
Consequently, if Sentry goes down (that is a case especially with self-hosted Sentry), the whole website will experience the issue.
We experienced the issue on production, where the current implementation caused 5-15s of extra loading time on all storefront pages.
Related issue
#246
Result
This pull request adds several features to improve the resilience of the extension:
Checklist
composer run codestylecomposer run analyse