Performance budgets, measurements, SLOs and alerts
Every number here is either a limit in the code or a measurement quoted from a commit message on feat/magento-10-10. All measurements are from the local Magento 2.4.7-p4 test stack (Mage-OS 1.0.6, PHP 8.3, Docker, developer mode, no sample data, against a mock Conviro or the backend code run locally) unless a line says production. Nothing on this branch is deployed, so there are no production measurements of the integration yet. The 1.5.0 section (Customer identity and My orders) quotes the commits of the 1.5.0 branches instead.
Storefront and admin impact
| What | Budget / limit | Measured (local) | Source |
|---|---|---|---|
| Queueing one event (outbox INSERT) in a storefront/admin request | 20 ms (p95) | p50 0.8 ms / p95 2.0 ms | 52fc1635 |
| same, later runs | p95 3.26 ms | 90c79bc8 | |
| same | p50 1.39 ms / p95 3.48 ms | fff8da0a | |
| Order placement with Conviro down | no wait on Conviro | order placed in 264 ms; events queued and delivered later, once | 52fc1635, outbox-e2e.sh |
| Config page (Integration Health card), Conviro down | 0.61 s, verdict "Action required: Connection" | fff8da0a, admin-health.sh | |
| Config page, Conviro black-holed (no answer) | 3 s connect timeout per call; Conviro's view cached 60 s; version check cached 12 h (15 min after a failure) | 7.46 s once (health call + cold version-check call), then 0.56 s from cache | fff8da0a |
With Conviro unreachable, a config page render can therefore wait about 3.5 s once a minute, up to about 7 s when the version cache is cold too. Storefront pages never call Conviro from PHP; the widget loads asynchronously in the browser — since 1.4.1 by default only on the first interaction (or after 4 s), and without a version.json request on most page views (Widget loading).
Delivery and sync limits (code)
| Limit | Value | Where |
|---|---|---|
| Delivery cron | every minute, one process (lock) | crontab.xml, Cron/DeliverEvents.php |
| Batch / time budget per run | 50 events / 50 s | EventDelivery::run |
| HTTP per event | 15 s total, 5 s connect | EventTransport |
| Claim lock | 300 s | EventOutbox::LOCK_SECONDS |
| Retry schedule | +30 s, +2 min, +10 min, +30 min, +2 h, +8 h; 7 attempts | RetryPolicy |
| Sync job batch | 200 items per job type per cron run (every minute) | SyncJobRunner::BATCH |
| Sync back-pressure | no new batch while ≥ 1,000 events wait | SyncJobRunner::MAX_PENDING |
| Configurable children per product event | 50 | ProductPayloadBuilder::MAX_CHILDREN |
| Attributes per product event | 30 | ProductPayloadBuilder |
Cart items per cart.updated | 10 | Observer/CartSave.php |
| Connect-minted API key | 600 requests / minute | magento-connect.service.ts |
A minute cron alone spreads the outbox wait over 0–60 s, so delivery lag p95 is about 57 s before any network or processing time (SLO.md).
Conviro-side read limits (code)
| What | Value |
|---|---|
| Store REST request timeout | 10 s default (magento-http.ts) |
| Live product + price cache | 5 min per store view; stock re-read after 1 min |
| Store topology cache | 1 h |
| Order lookup cache | 5 min; customer profile 10 min; cart 2 min |
| Nightly KB pull salability batches | 50 SKUs per call, quantity calls 8 at a time (measured: 2 salability calls for 60 SKUs) |
A product event drops cached reads for its SKU in every store view with one Redis prefix scan; a re-sync of an unchanged product skips the scan (fff8da0a).
SLIs, SLOs and alerts
The authority is apps/api/src/modules/magento/SLO.md with the numbers in magento-slo.ts (853499e4); this is a summary. Readable by Conviro staff at GET /api/super-admin/magento/slo?window=1h|6h|24h|7d|30d (super-admin JWT).
| SLO | Objective | Target | Window |
|---|---|---|---|
ingest_success | 2xx / (2xx + 5xx) on POST /v1/api/magento/events | ≥ 99.5 % | 30 d |
ingest_lag_p95 | p95 occurredAt → processed | ≤ 120 s | 30 d |
ingest_lag_p99 | p99 occurredAt → processed | ≤ 300 s | 30 d |
live_read_success | live reads answered 2xx or 404 | ≥ 99 % | 30 d |
live_read_p95 | p95 live-read time to response headers | ≤ 1,500 ms | 30 d |
The 1.5.0 Customer Hub adds four identity SLOs and nine alert rules: Customer identity and My orders.
Why not plan §15's 99.9 % / 60 s: a 5xx is retried seven times over about 10 h 43 min (RetryPolicy::describe()), so a per-attempt 99.5 % means well above 99.9 % of events accepted eventually; and a minute cron puts p95 lag near 57 s by design.
Alert rules (evaluated every 5 minutes over 1 h and 6 h, sent through Conviro's ops channel when the firing set changes, daily while it lasts, once on recovery; volume floors of 20 events / 50 live reads): ingest fast / slow burn (14.4× / 6× budget), lag p95 / p99, signature failures (≥ 10 rejections and ≥ 20 % of attempts in 1 h), any dead-lettered (invalid) answer, 409 share > 5 %, live-read fast / slow burn, live-read p95 > 1,500 ms, ≥ 10 live reads answered 401/403, ≥ 10 answered 429, ≥ 3 verifications in 1 h ending other than oauth1 / bearer.
Not observable from Conviro (visible in the module's Integration Health card instead): the outbox backlog and retries, a stuck re-sync, a module below the security minimum. Requests refused before the interceptor (unknown key, missing scope, rate limiter, body size) are not counted.
Production baseline (read-only, 2026-09-24 07:19 UTC, from SLO.md): 33 Magento integration rows, 2 active (one workspace), none with an authMode yet; the events endpoint is not deployed, so no ingest traffic exists to measure. The first 30-day window starts with the deploy.
Customer identity and My orders (1.5.0, unreleased)
Status 2026-09-27. Implemented on the Conviro backend branch
feat/magento-1.5.0-customer-hub— ADR-IDENTITY-001 Phase 5 P5-01c6995ce0(SLIs), P5-02d433f2c3(store vs Conviro attribution), P5-0353dd77d0(SLOs and alert rules) — unit-tested, not merged, not deployed: nothing in this section is measured in production. Targets, thresholds and volume floors were set on 2026-09-28 and are reviewed after 30 days of production data (MAGENTO_SLO_TARGETS,MIN_VOLUMEinmagento-slo.ts). The authority isapps/api/src/modules/magento/SLO.mdon that branch; this is a summary. What a merchant does about a failure: customer identity runbook.
SLOs
| SLO | What is measured | Target | Window |
|---|---|---|---|
identity_exchange_p95 | Conviro's clock from the challenge it issued to its answer to the exchange: the widget's same-origin call to your store's /conviro/identity/bootstrap and both network legs are inside it; the browser's rendering is not. Successful exchanges only. | p95 ≤ 1 s (1,000 ms) | 30 d |
my_orders_first_card_p95 | Conviro's server time of a verified shopper's first My orders read, request received → answered (your store's order route included). Each API replica counts its own "first" — an approximation. The browser's rendering is not in it. | p95 ≤ 2 s (2,000 ms) | 30 d |
my_orders_success | verified reads answered (loaded, empty, not found) or refused for security (signed out, expired, switched off) / (those + Conviro-caused + contract failures) | ≥ 99.9 % | 30 d |
my_orders_store_failure_share | verified reads that failed at your store / all verified reads | reported separately, no target, never in the budget above | 30 d |
- Store-caused (reported, never burns Conviro's budget): timeout,
store_error(a 5xx, 429, redirect or unreadable answer from the store),store_auth_failed,permission_missing,feature_absent(module older than 1.5.0),store_auth_mode(legacy bearer token). - Conviro-caused:
conviro_error(an error inside Conviro, P5-02) and Conviro's own read rate limit.contract_error(module and backend disagree on the answer's shape) counts against Conviro until triaged. - A sign-out is not a failure anywhere: the store's
403 binding_revokedon the next read is a security refusal, and verified reads (endpoint kindidentity_orders) are left out of the olderlive_read_*SLOs. - Percentiles are estimated from the histogram buckets (100, 250, 500, 1,000, 1,500, 2,500, 5,000, 10,000 ms) the way Prometheus'
histogram_quantiledoes, linearly inside the bucket; the 2-second target lies inside the 1,500–2,500 ms bucket, so a p95 near it is an estimate.
Alerts (Conviro operations)
Evaluated every 5 minutes with the other Magento rules and sent to Conviro's ops channel (Telegram / Slack) when the firing set changes; the message header takes the worst severity (critical > warning > info). Volume floors: 20 exchanges, 50 verified reads — below them nothing fires.
| Alert | Severity | Fires when |
|---|---|---|
my_orders_fast_burn | critical | Conviro-caused failure share over 1 h > 1.44 % (14.4 × the 0.1 % budget), ≥ 50 reads |
my_orders_slow_burn | warning | the same over 6 h > 0.6 % (6 ×), ≥ 50 reads — only when the fast burn does not fire |
my_orders_first_card_latency | warning | first-read p95 over 1 h > 2,000 ms, ≥ 50 first reads |
identity_exchange_latency | warning | exchange p95 over 1 h > 1,000 ms, ≥ 20 exchanges |
identity_exchange_failures | warning | failed share over 1 h > 20 %, too_many left out, ≥ 20 exchanges |
identity_invalid_spike | warning | ≥ 20 invalid exchanges in 1 h (a key mismatch after a restore, an unmapped store view, or an attack) |
identity_replayed_share | warning | replayed > 5 % of ≥ 20 exchanges in 1 h (a cache in front of a store serving the bootstrap) |
my_orders_contract_errors | warning | any contract_error read in 1 h (module / backend mismatch) |
my_orders_permission_missing | info | any read refused for a missing order-listing grant in 1 h — a fact, never a page |
Store-caused failures and sign-outs page nobody at Conviro. The labels are closed sets — no workspace, store URL, customer, session, principal, order or binding id — so an alert says what happened, not to whom; the API logs carry the workspace of a failing request.
What you see as a merchant
- **Magento admin, Customer Experience & Identity card › At Conviro:** Activity — the last 24 hours' verifications succeeded / failed and the top failure reason, what it means and a link to its runbook gate or troubleshooting row; My orders — whether your store refused Conviro's order request (permission missing, sign-in failed). The card shows no latency and no SLO figure.
- **Conviro dashboard, Integrations › Magento › store › Customer identity (7 / 30 days): verified conversations, verifications by reason, My orders requests and reads, Order read failures: N at the store, M at Conviro (plus version mismatches), and Order read time (p95, estimate)** with its median — per workspace, from the daily counts' coarser buckets (≤ 500, ≤ 1,000, ≤ 2,000, ≤ 5,000, > 5,000 ms) and over all reads, not only the first card. The store / Conviro split is Phase 5 P5-14 (
afb631fc, same backend branch, not deployed). - The platform SLOs and alerts above are Conviro's own; they carry no per-store label and are not shown to merchants.
Measured so far
| What | Where | Result |
|---|---|---|
The verification legs, local stack against the mock Conviro (P1-18, 555e7ce1, branch feat/magento-1.5.0-e2e, 8 rounds) | Magento 2.4.7-p4 (Mage-OS 1.0.6, PHP 8.3, developer mode), dev/tests/identity-bridge.sh with mock-conviro.mjs IDENTITY=1 | challenge p95 6 ms, bootstrap p95 90 ms, exchange p95 3 ms; all three p95 99 ms — measured locally, with a mock instead of the real backend, so it is not identity_exchange_p95 |
identity_exchange_p95 itself (the real backend's clock) | — | not measured anywhere yet (the local latency run is Phase 5 P5-16, not done) |
my_orders_first_card_p95 | — | not measured anywhere yet; the local order-route E2E (P2-18, 3f586ba6) checked the answers, not the time |
my_orders_success, my_orders_store_failure_share | — | not measured: no verified traffic exists |
| Production | — | nothing: the backend is not deployed — ölçüm, kodla doğrulanamaz |
How to measure locally
Not yet run end to end on this branch; the steps the Phase 5 rows P5-16 (local latency run) and P5-17 (Docker) are meant to script:
- Run the Conviro API of
feat/magento-1.5.0-customer-hubwith Redis (without Redis nothing is recorded) and the 1.5.0 module on the local Magento stack; switchmagento_identity_bridgeandmagento_my_orderson for the test workspace and Enable signed customer identity / Show verified shoppers their own orders on for the website. - Sign in as a test customer, open the chat (the bridge runs on open, not on page load) and ask for My orders; repeat past the floors (20 exchanges, 50 reads) — below them a p95 is noise.
- Read
GET /api/super-admin/magento/slo?window=1hwith a super-admin JWT (| jq '.slos, .alerts'):identity_exchange_p95,my_orders_first_card_p95,my_orders_success,my_orders_store_failure_shareand any identity alert that would fire. - A store-caused failure (stop the store's PHP-FPM, or withdraw the order-listing grant) must raise
my_orders_store_failure_share(andmy_orders_permission_missingas info) and leavemy_orders_successunchanged. - The browser's own time (chat opened → order card painted) is in neither SLI: time it in the browser's developer tools on the local stack.
Report every number from these steps as local, with the commit and the stack, never as production.