Skip to content

DM-52067: Make Sasquatch dispatch robust to an unresponsive or broken proxy - #492

Merged
mfisherlevine merged 2 commits into
mainfrom
tickets/DM-52067
Oct 15, 2026
Merged

mfisherlevine merged 2 commits into
mainfrom
tickets/DM-52067

Conversation

@mfisherlevine

@mfisherlevine mfisherlevine commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Rapid analysis publishes metric bundles from its worker pods through the SasquatchDatastore, where a Sasquatch outage should not fail the pipeline task doing the put.

  • No HTTP request had a timeout, so a proxy that accepted connections but never answered blocked the put, and the task, indefinitely. Every request now has one (30 s by default), set by the datastore's "timeout" config entry.
  • The cluster id lookup parsed the JSON body before checking the status, so an error response's error document escaped as a bare KeyError rather than SasquatchDispatchFailure, and failed the put. It now checks the status first.

Also fixes SingleValueRepacker for cp_verify results tables. Their per-detector and per-exposure rows have no amplifier, and set() over the masked values raised "unhashable type: 'MaskedConstant'", so analyzeFlatDetCore never wrote its flatTestsByDate plot.

@mfisherlevine
mfisherlevine force-pushed the tickets/DM-52067 branch 4 times, most recently from f500d81 to 8d5f036 Compare October 9, 2026 18:03
Comment on lines +177 to +183
except Exception:
# Publishing is best-effort: a broken proxy, or a bug in
# preparing the records, must never fail the butler put and
# with it the pipeline task.
log.exception(
"Unexpected error dispatching metric bundle %s to Sasquatch; it was not published.", ref
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@czwa I'm not sure whether you think this is correct or not - I actually don't quite know what the "right" behaviour is here, but this is at least mostly in keeping with the behaviour right above with the warning.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a question for slack and #dm-analysis-tools. This looks reasonable to me, and I agree with the comment that the butler put should always succeed (unless that's the problem).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I am not sure about. Currently if there is some sort of unexpected failure, bps marks it as such and things like re-tries can happen, this will break that existing behavior. There were also some sublties of datastores I don't remember off the top of my head related to how to signal in a chained datastore for others to keep trying (though I think that might be the DatasetTypeNotSupportedError). I'm not quite sure what is best here. It may be the change in behavior is not important, but it also may cause issues where people don't find issues because things just disappear.

You could preserve existing behavior with a swallow_exception or some kind of field, that defaults to false, but you could set to true in your specific butler config file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I've dropped it - I wasn't thinking about bps (I never use it). This is now handled in RA anyway: a new butler wrapper swallows any failed metric-bundle put so there's not actually any need to change the default for everyone else (this was written first and I forgot to remove it when I made the new butler-wrapper in RA). What's left just makes a hung or erroring proxy end up as the existing SasquatchDispatchFailure warning, instead of hanging or failing the task, which I think is good for everyone.

Comment thread tests/test_sasquatchDatastore.py Outdated
self.assertIs(mock_method.call_args[0][0], bundle)

def testPutSurvivesUnexpectedDispatchError(self) -> None:
# Publishing to Sasquatch is best-effort: an unexpected error from

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And likewise, this assertion here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same answer, I'm afraid.

@czwa czwa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a post on #dm-analysis-tools pointing interested people to this PR would be useful. I'm not an expert on requests, but those changes look reasonable to me (and with unit tests).

Comment on lines +177 to +183
except Exception:
# Publishing is best-effort: a broken proxy, or a bug in
# preparing the records, must never fail the butler put and
# with it the pipeline task.
log.exception(
"Unexpected error dispatching metric bundle %s to Sasquatch; it was not published.", ref
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a question for slack and #dm-analysis-tools. This looks reasonable to me, and I agree with the comment that the butler put should always succeed (unless that's the problem).

Comment thread tests/test_sasquatchDatastore.py Outdated
self.assertIs(mock_method.call_args[0][0], bundle)

def testPutSurvivesUnexpectedDispatchError(self) -> None:
# Publishing to Sasquatch is best-effort: an unexpected error from

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same answer, I'm afraid.

for i in range(len(uniquePanelKeys)):
repackedData[f"{uniquePanelKeys[i]}_x"] = []
repackedData[f"{uniquePanelKeys[i]}"] = []
repackedData: dict[str, list] = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hoping that a future ticket will make this obsolete, but I think this makes sense.

# LookupError and TypeError catch valid JSON of the wrong shape,
# ValueError a body that is not JSON at all.
raise SasquatchDispatchFailure("Could not retrieve the cluster id for the specified url") from e
self._cluster_id = str(cluster_id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads like a weird unrelated change that I don't quite understand. Is the idea that you want to move the cluster_id determination until after raise for status so it does not get set if there is an exception? Even if that is the case, why move the self._cluster_id stringification so far away, and outside of try? Is it trying to have the minimum amount of things inside the try block? I can kind of guess at things, but this feels a bit like just code churn, though low impact I admit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, fair. The only real bug is parsing the body before checking the status. When the proxy sends back a 4xx/5xx, the error JSON has no data key, so you get a bare KeyError that gets past the except RequestException and fails the put. I've cut it back: now, compared with main, it's just raise_for_status() moved above the parse, plus the timeout.

Comment on lines +177 to +183
except Exception:
# Publishing is best-effort: a broken proxy, or a bug in
# preparing the records, must never fail the butler put and
# with it the pipeline task.
log.exception(
"Unexpected error dispatching metric bundle %s to Sasquatch; it was not published.", ref
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I am not sure about. Currently if there is some sort of unexpected failure, bps marks it as such and things like re-tries can happen, this will break that existing behavior. There were also some sublties of datastores I don't remember off the top of my head related to how to signal in a chained datastore for others to keep trying (though I think that might be the DatasetTypeNotSupportedError). I'm not quite sure what is best here. It may be the change in behavior is not important, but it also may cause issues where people don't find issues because things just disappear.

You could preserve existing behavior with a swallow_exception or some kind of field, that defaults to false, but you could set to true in your specific butler config file.

mfisherlevine and others added 2 commits October 14, 2026 16:08
Rapid analysis publishes metric bundles from its worker pods through
the SasquatchDatastore, where a Sasquatch outage should not fail the
pipeline task doing the put.

- No HTTP request had a timeout, so a proxy that accepted connections
  but never answered blocked the put, and the task, indefinitely. Every
  request now has one, 30 s by default, set by the datastore's "timeout"
  config entry.
- The cluster id lookup parsed the body before checking the status, so
  an error response's JSON error document raised a bare KeyError rather
  than SasquatchDispatchFailure, and failed the put.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cp_verify's results tables stack per-detector and per-exposure rows,
which have no amplifier, in with the per-amplifier rows. Those come back
masked, and set() raised "TypeError: unhashable type: 'MaskedConstant'",
so analyzeFlatDetCore never wrote its flatTestsByDate plot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@natelust natelust left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear I only reviewed the dispatcher bit of this, but it looks fine to me with the changes. Those look good now. I'll leave the rest to Chris

@czwa czwa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've re-read all the files that github told me I hadn't viewed, and I think it still makes sense.

@mfisherlevine
mfisherlevine merged commit a14a3ff into main Oct 15, 2026
14 checks passed
@mfisherlevine
mfisherlevine deleted the tickets/DM-52067 branch October 15, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants