Batch message-board subscriber reads into one SQLite result (#47050)

## Why

Posting to the local agent message board fetched each subscriber row across SQLite's worker thread while holding a write transaction.

## What changed

Use `json_group_array(agent)` to fetch subscribers as a single JSON array, then deserialize it before collecting notification recipients.

## Testing

Extend the local board test to verify that posting to a channel with no subscribers succeeds without sending notifications.

GitOrigin-RevId: ad294e19c78ee501f1de2084f7c02fa96f53418d
This commit is contained in:
jif
2026-09-21 15:27:08 +00:00
committed by copyberry
parent f484f7228d
commit 0e69d3bd9b
2 changed files with 22 additions and 2 deletions
@@ -248,14 +248,17 @@ impl LocalAgentMessageBoard {
)
}
};
// Return one JSON array to avoid handing each subscriber row across
// SQLite's worker thread while the write transaction is held.
let subscribed = sqlx::query_scalar::<_, String>(
"SELECT agent FROM subscriptions WHERE board=? AND target=?",
"SELECT json_group_array(agent) FROM subscriptions WHERE board=? AND target=?",
)
.bind(self.identity.to_string())
.bind(target_key(&target)?)
.fetch_all(&mut *tx)
.fetch_one(&mut *tx)
.await
.map_err(storage_error)?;
let subscribed: Vec<String> = serde_json::from_str(&subscribed).map_err(storage_error)?;
for recipient in subscribed {
recipients.insert(ThreadId::from_string(&recipient).map_err(storage_error)?);
}
@@ -165,6 +165,23 @@ async fn shared_handles_resume_posts_and_preserve_subscription_rules() {
)
.await
.unwrap();
// Posting still succeeds when the subscriber lookup returns an empty array.
resumed
.post(
root,
PostRequest {
request_id: "no-subscribers".into(),
destination: PostDestination::Channel("proofs".into()),
text: "saved without notifications".into(),
agents_to_notify: Vec::new(),
},
)
.await
.unwrap();
assert_eq!(
*host.notifications.lock().unwrap(),
vec![(child, metadata.clone())]
);
resumed
.set_subscription(
root,