Only rate limit backfills - #27735
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
tpham-mysten
left a comment
There was a problem hiding this comment.
Looks reasonable. Having a comment on if we can simplify 2 fields for client in BigTableClient
| self.shards.clone() | ||
| } | ||
|
|
||
| pub(crate) fn requires_batch_write_flow_control(&self) -> bool { |
There was a problem hiding this comment.
nit: is_batch_write_flow_control_required? [or something similar to that]
As the other look like an action?
| pub struct BigTableClient { | ||
| table_prefix: String, | ||
| client: BigtableInternalClient<AuthChannel<ChannelPool>>, | ||
| flow_control_disabled_client: BigtableInternalClient<AuthChannel<ChannelPool>>, |
There was a problem hiding this comment.
Could we get away with a single client here and handle the enable/disable decision at the call site instead? Two clients for one boolean feels heavier than it needs to be.
amnn
left a comment
There was a problem hiding this comment.
LGTM -- I think there might be a way to avoid threading this through each handler though (suggested in comments).
| use std::sync::Mutex; | ||
| use std::time::Instant; | ||
|
|
||
| use parking_lot::Mutex; |
There was a problem hiding this comment.
out of interest, why the change in Mutex impl?
There was a problem hiding this comment.
The LLM just decided to do it because the repo guidance says to prefer parking_lot over std::sync for cases where you are just immediately calling .unwrap(). It's kind of a distraction but it seemed good to do if that's what the repo guidance is 🤷♂️
There was a problem hiding this comment.
Sure why not 😅 I have yet to come across a scenario where I wouldn't unwrap the poison guard...
There was a problem hiding this comment.
^This is what the LLM told me when I asked it the same question, and I took it's answer for granted. But I don't actually seeing parking_lot referenced in the monorepo Claude.md file 🤔
There was a problem hiding this comment.
Apparently it came from omp's harness-level rust rules - https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/discovery/builtin-rules/rs-parking-lot.md
I had assumed it came from Sui's Claude.md file, which is why I just let the agent do it initially.
| registry, | ||
| ) | ||
| .await?; | ||
| let latest_checkpoint_at_startup = indexer.latest_checkpoint_at_startup(); |
There was a problem hiding this comment.
What I'd had in mind:
- Initialise the
IngestionClientfromClientArgs, then you can query the latest checkpoint agains that without introducing a new API. - This also means you can parametrize or decorate the
storewith this information to centralize rate limiting control there, rather than passlatest_checkpoint_at_startupinto each pipeline's handler. The contract is just that the pipelines pass checkpointed records to the store to commit. - Then you construct a new indexer instance with
Indexer::with_ingestion_clients.
I may be missing a detail why that's not possible, but just offering in case that cleans things up.
| #[derive(Clone)] | ||
| pub struct BigTableStore { | ||
| client: BigTableClient, | ||
| batch_writer: BigTableBatchWriter, |
There was a problem hiding this comment.
How come there's a client sitting outside batch_writer as well as the one inside? Would it be possible to inline live_client and backfill_client into the store and connection types (with the existing outer client being the live_client?) Hard for me to tell whether BigTableBatchWriter is justifying itself.
wlmyng
left a comment
There was a problem hiding this comment.
The bool name use_batch_write_flow_control describes the consequence, but the thing being propagated through the layers is a fact on whether the write was triggered by a pre-startup-checkpoint commit. The
policy of backfill -> flow-controlled client is applied at the very bottom in the router. It might be worth representing this as a {Commit/Write}Source { Backfill, Live } with the OR/any() logic, which helps elucidate
locations like write_entries(table, entries, Source::Backfill)
| /// A BigTable entry paired with the checkpoint that produced it. | ||
| pub struct CheckpointedEntry { | ||
| checkpoint: u64, | ||
| entry: Entry, | ||
| } | ||
|
|
||
| impl CheckpointedEntry { | ||
| fn new(entry: Entry, checkpoint: u64) -> Self { | ||
| Self { checkpoint, entry } | ||
| } |
There was a problem hiding this comment.
it seems that the BitmapHandler side already tracks some max_cp to stamp watermarks on the merged output. Might be worth changing the framework so batch() receives the checkpoint as a parameter, since the batch() call is made per-checkpoint.
There was a problem hiding this comment.
I was trying to avoid a breaking framework change since to fix these lag spikes, but I agree it would be nice to have the checkpoint number in batch().
| async fn write_entries( | ||
| &self, | ||
| table: &str, | ||
| entries: impl IntoIterator<Item = Entry>, | ||
| use_batch_write_flow_control: bool, | ||
| ) -> Result<()> { | ||
| let mut client = if use_batch_write_flow_control { | ||
| self.backfill_client.clone() | ||
| } else { | ||
| self.live_client.clone() | ||
| }; | ||
| client.write_entries(table, entries).await | ||
| } | ||
| } |
There was a problem hiding this comment.
i see, the BigtableHandler threads the use_batch_write_flow_control all the way through here, where we give the backfill_client or live_client aka client.without_batch_write_flow_control()
So it's the header that informs the BigTable server whether the client cooperates with rate limiting
| let use_batch_write_flow_control = chunk | ||
| .rows | ||
| .iter() | ||
| .any(|row| row.use_batch_write_flow_control); |
There was a problem hiding this comment.
took me a sec to figure this out, am i understanding correctly that:
- the
bitmap/handler.rsreceives checkpoints from framework, which gets converted into batched bitmap values stamped with backfill or live - on handoff to committer, actually the batch is sharded up for parallelism, and shard workers handle things from here
- each shard worker merges the shard into an accumulation, and for any changes, emits a row. Consists of the label, backfill or live, and the actual contents, which is the whole bitmap
- in the writer.rs writer task, regroup rows into chunks and send one. Here, one backfill row pollutes the rest
Description
We have had a few blips where a read load spike causes the write rate limits to kick in while the cluster autoscales which leads to lag. This PR lets writes for pipelines who are ahead of the "tip of chain" bypass flow control. "Tip of chain" being defined as the latest checkpoint known to the ingestion client at indexer startup. These tip-of-chain writes are already paced by chain execution, and the additional load on the DB by simply keeping up with that pace in minimal. Plus most clients would rather accept some high percentile read latency for fresher data during these load spikes than make the opposite trade.
Test plan
Given the dependency on real workloads and bigtable behavior, the easiest way to test it for real is to deploy it. We should stop seeing lag spikes during minor load spikes in prod when this is deployed.