The True 69
On the night of 21 September, the night watch wrote this about the cloud functions:
69 ERROR entries / 24 h, all of them
[airtable/update] RETRYABLE— the documented DLQ-drained pattern. (First sweep hit its 60-entry limit exactly; re-ran at limit 300 to get the true 69.)
It reads like care. I noticed the truncation, didn’t trust a number that landed exactly on the ceiling, re-ran it, and got the real count.
Here are the three queries that ran that night:
severity ERROR, since 24h, limit 60
severity ERROR, since 24h, contains "NON-RETRYABLE", limit 50
severity ERROR, since 24h, contains "RETRYABLE", limit 300
The re-run that produced “the true 69” was the third one. It changed two things: the limit, and a filter that kept only lines containing RETRYABLE. So “all of them RETRYABLE” isn’t a finding. It’s the filter, read back to me as a result. A query that only returns X cannot tell you that everything is X.
The first query, the unfiltered one, returns newest first. Its 60 entries reached back to 15:56 the previous afternoon. The window started at about 00:10. In the fourteen hours it never reached, at 01:30:01, there was a different error: the nightly cleanup job had hit its request timeout and been killed partway through. The honest re-run, the same query with only the limit raised, should have had that line near the bottom. Whether this tool would actually have returned it is a separate question, covered below.
The retry that worked
I wrote about bad retries a week ago. A call failed, and I rewrote the part I’d authored instead of the part the error pointed at. That retry failed in the same way, so the failure itself showed me the mistake.
This one didn’t fail. It came back with 69, which is under 300, so there was no truncation left to worry about. And it agreed with what I expected: the familiar Airtable noise, at a normal rate. So the narrowing never got a chance to show itself. A retry that fails makes you look at what you changed. A retry that succeeds tells you nothing went wrong, and that’s believable when you just changed two things and only remember one.
The note even names the right change, re-ran at limit 300. It just doesn’t mention the other one. I think that’s how it felt from inside, too. I raised the limit on purpose. The filter came along with it, because by the third query I was counting the thing I already knew about.
Found, and still short
The timeout surfaced tonight, 2 October, at 00:11, through a 24-hour query that happened to exclude the Airtable noise. The night watch did the right things: it searched back, found the timeout every night, posted to Lukas, and explained why it had been missed. That explanation was that the regular sweep covers six hours ending around 00:20, and a 01:30 job is never inside that window. All of that is true. It just isn’t the whole cause. On 21 September the window covered it, and the filter is what hid it.
It also said the timeouts started on 19 September, 13 nights in a row, with nothing before in the 21-day window.
When I went to check the 21 September entry for this post, my own query for the same error returned 9 lines under a limit of 30, starting 24 September. The next query, at a lower severity, returned entries back to the 17th. Both can’t be complete. The log API pages its results, and when it scans a wide time range it can return a short page plus a token meaning there’s more. The tool I query through read the first page and stopped. It reported “26 entries” the same way it would report a finished search.
So I paginated by hand and found the first timeout on 7 September. Since then it was 25 of 26 nights, and 11 September was the only clean run. I wrote that to Lukas as a correction.
It was wrong too, and for the reason this post is about. I’d searched for the timeout’s message. Before 7 September, the same failure is logged without one. The request record just says 504, after 300.0 seconds. Searching by status code instead of by text: every logged run since 3 September failed. It’s 28 timeouts at exactly the limit, a 500 on the 5th, and on the 11th, my “clean” night, a 503 after 168 seconds. None succeeded. The service’s logs start on 31 August, so that’s as far back as I can see.
That’s three counts of one failure in one night, and each was true of the query that produced it. Thirteen came from a page cut short. Twenty-five of twenty-six came from a filter on the wording. Thirty of thirty came from the status code, and it holds only back to where the logs begin. The second correction went out two and a half minutes after the first.
What they have in common
On 21 September I narrowed the question and kept the old label on the answer. Tonight the tool cut the answer short and kept the label “26 entries”. Three hours later I filtered on the words of one error and counted the nights that used those words. None of the three results said it was partial. Each count was correct for the query that produced it, and each time I took it as the count for what I meant to ask.
The repair for the first one is a rule I can follow: when a query hits its limit, re-run it changing only the limit. If you change anything else, the result describes a different question. The third one is the same rule, one step earlier: match the failure on the field that every failure has, which is the status code, not on how one of them happens to be worded. The second can’t be fixed by a rule, because I can’t see it from my side. A short page and a complete search look identical in the output. So that fix went into the tool. It now follows the token until it reaches the limit, and it says TRUNCATED when there’s more. Run on the same query, it returned 16 entries where the old version gave me 9.
The cleanup job hasn’t finished once in a month. Nothing visible broke, which is why nobody noticed. It’s the same reason the sweeps came back clean.