-
Notifications
You must be signed in to change notification settings - Fork 6
feat(github): explain throttle reasons with inline tips and a reference doc #1072
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d6dfdc8
feat(github): explain throttle reasons with inline tips and a referen…
aparajon 0f9c677
docs: read redo-aware as active threads over threshold, note Aurora s…
aparajon 466fad7
docs: use a replica-lag example that satisfies its own threshold
aparajon 4ff2f65
docs: explain why the throttle doc link is not derived from the GitHu…
aparajon 21a92ac
fix(github): suppress partial tips and stop blaming application load
aparajon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| # Throttle reference | ||
|
|
||
| <!-- BEGIN TOC (auto-generated by `make docs-toc`) --> | ||
|
|
||
| ## Table of Contents | ||
|
|
||
| - [redo-aware](#redo-aware) | ||
| - [threads-running](#threads-running) | ||
| - [commit-latency](#commit-latency) | ||
| - [Throttled with no reason](#throttled-with-no-reason) | ||
|
|
||
| <!-- END TOC (auto-generated by `make docs-toc`) --> | ||
|
|
||
| When a schema change's progress bar carries a `(throttled)` annotation, the | ||
| engine's throttler is deliberately pausing the copy or the checksum verify to | ||
| protect the database. Throttling is backpressure, not a hang: the work resumes | ||
| on its own as soon as the pressure signal clears. A brief throttle needs no | ||
| attention; only a sustained one is worth the per-signal checks below. | ||
|
|
||
| The annotation's tooltip shows the raw reason reported by the engine and a | ||
| short tip; the PR comment version also links this document. Reasons follow | ||
| the grammar `<signal> <observed> <op> <threshold>`. When several signals | ||
| throttle at the same time, the reasons are joined with `; `. | ||
|
|
||
| This page explains each signal: what it measures, why the engine pauses on it, | ||
| and what to look at when the throttle lasts longer than expected. | ||
|
|
||
| ## redo-aware | ||
|
|
||
| ``` | ||
| redo-aware 4 > 3 | ||
| ``` | ||
|
|
||
| Read this as: active threads greater than the instance's threshold. The | ||
| engine selects this algorithm when it detects an Aurora source. It counts | ||
| threads actively executing queries (from `performance_schema`) and subtracts | ||
| threads parked on Aurora's redo-log flush wait, which consume no CPU. When | ||
| the active count exceeds the instance's budget (vCPUs plus headroom), the | ||
| copy pauses until threads free up. The count deliberately includes the | ||
| copy's own read and apply threads, which are genuine CPU load, so on a small | ||
| instance the copy throttles against its own footprint. In the example, 4 | ||
| active threads exceed a budget of 3 on a 2-vCPU instance, a state the copy's | ||
| own threads can reach with little or no application load. | ||
|
|
||
| **When to act.** Usually nothing: the throttle is self-limiting, trading copy | ||
| speed for CPU headroom. Because the copy's own threads count toward the | ||
| budget, a throttled copy on a small or even idle instance is normal and is | ||
| not evidence of application overload. If the copy must finish sooner, move | ||
| to a larger instance class; raising the copy's own concurrency does not help | ||
| while this signal is active, since the extra threads count against the same | ||
| budget. | ||
|
|
||
| ## threads-running | ||
|
|
||
| ``` | ||
| threads-running 21 > 18 | ||
| ``` | ||
|
|
||
| The same thread-budget protection as [redo-aware](#redo-aware), measured more | ||
| coarsely: the global `Threads_running` counter compared against the | ||
| instance's budget. The engine falls back to this signal when it lacks the | ||
| `performance_schema` access the redo-aware signal needs. Unlike redo-aware, | ||
| threads parked on redo-log waits count as load, so this signal is more | ||
| conservative. In the example, 21 running threads exceed a budget of 18 on a | ||
| 16-vCPU instance. | ||
|
|
||
| **When to act.** Same as redo-aware. Granting the engine's user read access | ||
| to `performance_schema` upgrades the signal to redo-aware. | ||
|
|
||
| ## commit-latency | ||
|
|
||
| ``` | ||
| commit-latency 112.4ms >= 100ms | ||
| ``` | ||
|
|
||
| The average commit latency on the database has crossed the engine's | ||
| threshold, the right-hand value in the reason (SchemaBot configures 100ms, | ||
| auto-enabled on Aurora). Slow commits mean the storage layer is saturating, | ||
| so the copy backs off before write latency degrades for the application. | ||
|
|
||
| **When to act.** A sustained throttle points at storage pressure: check the | ||
| instance's write IOPS and commit latency metrics. Do not expect a | ||
| co-occurring redo-aware reason as confirmation: redo-aware subtracts exactly | ||
| the threads parked on redo-log waits, so a saturated redo log makes its | ||
| count fall rather than rise, and commit-latency is the signal designed to | ||
| notice. A sustained commit-latency throttle on its own says the instance is | ||
| undersized for the combined application and copy write load. | ||
|
|
||
| ## Throttled with no reason | ||
|
|
||
| A throttler that predates reason reporting, or one that implements no reason | ||
| extension, reports the throttled flag with an empty reason. The progress | ||
| surfaces show the bare `(throttled)` annotation with no tooltip. The | ||
| backpressure semantics are the same; only the explanation is missing. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package ui | ||
|
|
||
| import "strings" | ||
|
|
||
| // ThrottleDocURL points at the throttle reference doc, which explains each | ||
| // throttle signal and how to remediate it. Rendered next to a throttle tip so | ||
| // an operator can jump from the one-line tip to the full prose. The URL is | ||
| // deliberately the project's canonical public home, not derived from the | ||
| // configured GitHub host: that host serves users' schema repos, which do not | ||
| // carry this project's docs, so a host-derived link would always be broken. | ||
| const ThrottleDocURL = "https://github.com/block/schemabot/blob/main/docs/throttle.md" | ||
|
|
||
| // ThrottleTip translates an engine throttle reason into a short operator-facing | ||
| // tip. Reasons follow the grammar "<signal> <observed> <op> <threshold>", with | ||
| // several concurrently-throttling signals joined by "; ", so the tip is keyed | ||
| // on each part's leading signal token. Signals that read the same to a user | ||
| // (the two active-thread variants) share a tip, and duplicate tips collapse. | ||
| // A reason containing any unrecognized signal yields no tip at all — a partial | ||
| // explanation would silently bind to signals it does not cover, so an | ||
| // unrecognized signal always degrades the whole reason to its raw text rather | ||
| // than a wrong explanation. | ||
| func ThrottleTip(reason string) string { | ||
| seen := map[string]bool{} | ||
| var tips []string | ||
| for part := range strings.SplitSeq(reason, ";") { | ||
| part = strings.TrimSpace(part) | ||
| if part == "" { | ||
| continue | ||
| } | ||
| tip := throttleSignalTip(part) | ||
| if tip == "" { | ||
| return "" | ||
| } | ||
| if seen[tip] { | ||
| continue | ||
| } | ||
| seen[tip] = true | ||
| tips = append(tips, tip) | ||
| } | ||
| return strings.Join(tips, "; ") | ||
| } | ||
|
|
||
| // throttleSignalTip maps one reason part to its tip by the leading signal | ||
| // token. The wording states what the pause protects, so a user reads a slowed | ||
| // bar as deliberate backpressure rather than a hang. The thread-budget tip | ||
| // stays neutral about whose load crossed the threshold: the engine counts its | ||
| // own copy threads toward the budget, so the pause is not evidence of | ||
| // application overload. | ||
| func throttleSignalTip(part string) string { | ||
| signal, _, _ := strings.Cut(part, " ") | ||
| switch signal { | ||
| case "redo-aware", "threads-running": | ||
| return "backing off while the database's active threads exceed its budget" | ||
| case "commit-latency": | ||
| return "backing off while database writes commit slowly" | ||
| } | ||
| return "" | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package ui | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| // TestThrottleTip verifies that every throttle signal the engine can emit maps | ||
| // to a short operator-facing tip, composite reasons collapse shared meanings, | ||
| // and any unrecognized signal suppresses the whole tip so the raw reason | ||
| // stands alone rather than binding a partial explanation to the wrong signal. | ||
| func TestThrottleTip(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| reason string | ||
| want string | ||
| }{ | ||
| {"redo-aware threads", "redo-aware 4 > 3", | ||
| "backing off while the database's active threads exceed its budget"}, | ||
| {"threads-running fallback shares the thread-budget tip", "threads-running 21 > 18", | ||
| "backing off while the database's active threads exceed its budget"}, | ||
| {"commit latency", "commit-latency 112.4ms >= 100ms", | ||
| "backing off while database writes commit slowly"}, | ||
| {"composite reasons join their tips", "redo-aware 4 > 3; commit-latency 112.4ms >= 100ms", | ||
| "backing off while the database's active threads exceed its budget; backing off while database writes commit slowly"}, | ||
| {"composite duplicate meanings collapse", "redo-aware 4 > 3; threads-running 21 > 18", | ||
| "backing off while the database's active threads exceed its budget"}, | ||
| {"unrecognized signal yields no tip", "mock throttler (always throttled)", ""}, | ||
| {"unrecognized segment suppresses the whole tip", "disk-usage 95% > 90%; commit-latency 112.4ms >= 100ms", ""}, | ||
| {"signal token must match exactly", "redo-awareness 4 > 3", ""}, | ||
| {"empty segments are ignored", "commit-latency 112.4ms >= 100ms; ", | ||
| "backing off while database writes commit slowly"}, | ||
| {"empty reason yields no tip", "", ""}, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| assert.Equal(t, tt.want, ThrottleTip(tt.reason)) | ||
| }) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.