Lesson 2 · New Relic alert conditions · why a good threshold at 14:00 is a bad threshold at 09:00
Prerequisite: Lesson 1, the four blocks.
This lesson lives entirely in the terms block and the
nrql block that feeds it.
Carrier traffic is not flat. Here is one full day of
CARRIER_INTEGRATION_RESPONSE in production, bucketed by hour of day
in UTC. Read on 2026-08-20.
SELECT count(*) AS total,
filter(count(*), WHERE numeric(status_code) >= 400) AS errors
FROM Log
WHERE event = 'CARRIER_INTEGRATION_RESPONSE'
AND environment = 'prod'
FACET hourOf(timestamp)
SINCE 1 day ago LIMIT 24
| 00 | 2,762,027 | |
| 01 | 2,851,679 | |
| 02 | 2,959,751 | |
| 03 | 2,668,012 | |
| 04 | 2,611,227 | |
| 05 | 2,348,536 | |
| 06 | 2,125,499 | |
| 07 | 2,215,081 | |
| 08 | 1,859,624 | |
| 09 | 1,856,251 ← trough | |
| 10 | 1,915,291 | |
| 11 | 2,106,500 | |
| 12 | 2,503,386 | |
| 13 | 2,895,538 | |
| 14 | 3,416,766 ← peak | |
| 15 | 3,367,537 | |
| 16 | 3,293,321 | |
| 17 | 3,213,499 | |
| 18 | 3,336,587 | |
| 19 | 3,208,309 | |
| 20 | 3,114,863 | |
| 21 | 2,974,362 | |
| 22 | 2,858,659 | |
| 23 | 2,733,860 |
Peak is 1.84× the trough. The error count swings harder still: 113,815 at 09:00 against 263,263 at 14:00, a 2.31× spread.
| 00 | 6.84% | |
| 01 | 6.52% | |
| 02 | 6.54% | |
| 03 | 7.16% | |
| 04 | 6.39% | |
| 05 | 6.49% | |
| 06 | 6.48% | |
| 07 | 5.99% ← min | |
| 08 | 6.72% | |
| 09 | 6.13% | |
| 10 | 7.29% | |
| 11 | 7.49% | |
| 12 | 6.56% | |
| 13 | 7.92% ← max | |
| 14 | 7.71% | |
| 15 | 7.46% | |
| 16 | 7.19% | |
| 17 | 6.60% | |
| 18 | 6.25% | |
| 19 | 6.35% | |
| 20 | 6.21% | |
| 21 | 6.31% | |
| 22 | 6.40% | |
| 23 | 6.92% |
The rate swings 1.32× where the count swings 2.31×. Same system, same day. Only the unit changed.
Set a static threshold on an error count and you have set two different alerts. At 14:00 it trips on a smaller relative degradation than at 09:00. Tune it so 14:00 is quiet, and 09:00 goes deaf. Tune it so 09:00 is sensitive, and 14:00 pages every day.
Of the 1000 alert conditions returned by the New Relic API,
234 use filter() or percentage() to
compute a ratio. Fifty-eight of those sit on
CARRIER_INTEGRATION_RESPONSE, which carries 86 conditions in total,
63 of them enabled. Fifty-one conditions across the account guard a ratio with a
volume floor. This is the shape:
SELECT percentage(count(*), WHERE status != 'success') AS 'Error Rate'
FROM Log
WHERE environment = 'prod'
AND test != true
AND service IN ('auth', 'carrier_auth')
AND event = 'CARRIER_INTEGRATION_RESPONSE'
FACET url
[CFG] Carrier Authentication Requests are Failing More than Usual (id 55396132, policy NOC Policy). Live and enabled, BASELINE, critical 30% and warning 6% over 600 s.
A ratio removes the volume term. That is the whole trick. It does not remove the problem.
Divide by a small denominator and the result jumps. Three failed calls out of four is a 75% error rate. It is also completely normal noise for a carrier that receives four calls in a window at 04:00.
So every serious Shippo condition guards the ratio with a minimum-volume floor. Shippo spells this two different ways. Learn both, because they behave differently.
floor and clamp_minSELECT (floor(count(*) / clamp_min(count(*), 50)) * percentage(count(*), WHERE status != 'success' AND service = 'shipment' AND test != true AND `shippo.integration.response.error.expected` != true AND status_code NOT IN (400, 412) )) AS failure_rate FROM Log WHERE environment = 'prod' AND event = 'CARRIER_INTEGRATION_RESPONSE' AND service = 'shipment' AND test != true AND integration = 'canada_post'
[SHIPMENT] Canada Post Carrier Integration Error Rate (tf) (id 65685767, policy Carrier Monitoring). Live and enabled, critical ABOVE 15 for 1200 s, AT_LEAST_ONCE.
Read the guard on its own. Let n be the number of events in the window.
| n | clamp_min(n, 50) | n / clamp_min(n,50) | floor(...) | Result |
|---|---|---|---|---|
| 4 | 50 | 0.08 | 0 | rate × 0 = 0 |
| 49 | 50 | 0.98 | 0 | rate × 0 = 0 |
| 50 | 50 | 1.00 | 1 | rate × 1 = rate |
| 9000 | 9000 | 1.00 | 1 | rate × 1 = rate |
Below 50 events the whole expression collapses to zero. It
emits a number, and that number is quiet. This matters for what happens next in
the signal block.
IF(count(*) >= 50, ...)SELECT IF(count(*) >= 50,
filter(count(*), WHERE status != 'success' AND status_code >= 500
AND (`shippo.integration.response.error.message` LIKE '%SERVICE.UNAVAILABLE.ERROR%'
OR `shippo.integration.response.error.message` LIKE '%SYSTEM.UNAVAILABLE.EXCEPTION%'))
* 100.0 / count(*)) AS '5xx Errors'
FROM Log
WHERE environment = 'prod' AND test != true
AND event = 'CARRIER_INTEGRATION_RESPONSE'
AND service = 'shipment'
AND url LIKE '%apis.fedex.com/rate/v1/comprehensiverates/quotes%'
AND caller NOT LIKE '%grpc_service%'
AND integration = 'fedex'
[CCAP] FedEx REST Rating - 5xx errors (carrier upstream) (id 66242857, policy Carrier Capabilities (CCAP)). Live and enabled, warning ABOVE 25 for 1800 s, ALL.
Same intent, same floor of 50. Different output below the floor. The
one-argument IF has no else branch, so it returns
nothing, not zero.
Below the floor, spelling one emits 0 and spelling two emits a
gap. A gap is then handled by the signal block. Condition
61106123 sets fillOption: STATIC, fillValue: 0,
which converts that gap back into a zero. The two conditions therefore agree,
but only because the second one is paired with the right fill setting.
Change fillOption to NONE on an IF-guarded
condition and low-volume windows stop producing data at all. Whether that is
harmless or opens a signal-loss incident is decided by the
expiration block. That is lesson 3.
The floor is a count per window, so the window size and the floor are one decision, not two. A 50-event floor over 60 s is a much harder bar than a 50-event floor over 1800 s.
Shippo's window choices across the 1000 sampled conditions:
| Window | Conditions | Typical use |
|---|---|---|
| 60 s | 354 | High-volume signals where a one-minute outage matters. |
| 300 s | 297 | The default choice. Enough volume for most carriers. |
| 600 s | 83 | Paging conditions that must not flap. |
| 3600 s | 71 | Low-volume carriers and slow-moving ratios. |
| 900 s | 42 | A middle ground, used by the FedEx Shippo-origin condition. |
| 1800 s | 41 | Sustained-outage conditions such as [CCAP] FedEx REST Rating - 5xx errors (carrier upstream). |
The rule that follows from the diurnal data: size the window so the trough hour still clears your floor. Check it before you ship, on the quietest hour, for the specific carrier you are alerting on.
SELECT count(*) FROM Log WHERE event = 'CARRIER_INTEGRATION_RESPONSE' AND environment = 'prod' AND integration = 'canada_post' AND service = 'shipment' TIMESERIES 20 minutes SINCE 1 day ago
If that series dips below 50 overnight, a 1200 s window with a 50-event floor goes blind overnight. That may be the correct trade. It must be a decision, not an accident.
status_code is a mixed-type attribute. New Relic's
keyset() reports it as both string and numeric on this
event. Some rows carry a literal '5XX' sentinel.
SELECT count(*) AS total,
filter(count(*), WHERE status_code >= 500) AS ge500,
filter(count(*), WHERE status_code LIKE '5%') AS like5
FROM Log
WHERE event = 'CARRIER_INTEGRATION_RESPONSE' AND environment = 'prod'
AND status_code = '5XX'
SINCE 60 minutes ago
-- ge500: 0 like5: ~1,000
The numeric comparison matches none of them. All three of
[CCAP] FedEx REST Rating - 5xx errors (carrier upstream),
[CCAP] FedEx REST Rating - 5xx burst (Shippo origin) and
[CCAP] FedEx REST Rating - 5xx errors (Shippo origin) filter on
status_code >= 500. Inside their exact filters,
1,393 FedEx rating failures a day are counted as zero. Since
all three alert on a percentage, the numerator loses those events while the
denominator keeps them, so the reported rate is pushed down twice.
These are not legacy conditions. They were rewritten on 20 August, with the
sliding-window and paired-condition thinking from this course applied to them,
and the bug was carried straight through the rewrite. Re-checked the same day:
still broken. Written up in
docs/2026-08-13-fedex-5xx-string-blind-spot.md.
The sentinel is not a FedEx quirk either. UPS produces
33,519 '5XX' rows a day, against FedEx's 4,629.
No UPS condition uses the numeric pattern today, which is luck rather than
design.
Put every comparison you are testing inside one query, as separate
filter() columns. Two separately issued queries cover two different
time windows, and the difference in their totals will look like a semantic
difference. That mistake produced a wrong conclusion about test != true
while writing this lesson. The single-query check disproved it:
test != true and test != true OR test IS NULL return
identical counts, so != already includes NULL here.
1. A condition counts FedEx 5xx events and pages ABOVE 200 per 5-minute window. It was tuned during the 14:00 UTC peak. What happens at the 09:00 trough?
2. In [SHIPMENT] Canada Post Carrier Integration Error Rate (tf), a 20-minute window holds 30 Canada Post events and 20 of them fail. What does the query return?
3. Below its 50-event floor, the FedEx Rating 5xx upstream condition reports 0% rather than a gap. Which setting makes that true?
4. You are writing a ratio condition for a carrier that receives about 40 calls per hour at its overnight trough. Your draft uses a 300-second window with a 50-event floor. What is wrong?
Primary source: New Relic docs, Set thresholds for an alert condition. Read the sections on threshold types and on aggregation windows.
Secondary, for the NRQL functions used above:
NRQL syntax, clauses and functions
See percentage(), filter(), clamp_min(), and IF().
Next: Lesson 3, gaps, fills, and signal loss. What happens when the data simply stops, and why three Shippo conditions answer that question three different ways.
Ask your teacher. Ask for a live check on any carrier's trough volume before you pick a window. That query takes ten seconds and it is the difference between a tuned condition and a guess.