Skip to content

Batch: say what it costs and how long it takes - #19

Merged
ExaltedTrou6 merged 1 commit into
mainfrom
docs/batch-cost
Aug 7, 2026
Merged

Batch: say what it costs and how long it takes#19
ExaltedTrou6 merged 1 commit into
mainfrom
docs/batch-cost

Conversation

@ExaltedTrou6

@ExaltedTrou6 ExaltedTrou6 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What and why

A live run (50 deals in one batch) caught the documentation in a half-truth.

1. "ONE rate-limit token instead of fifty" — true of the frequency limit only

llms.txt said:

Use batch for many calls. 50 calls in one request cost ONE rate-limit token instead of fifty.

The sentence reads as "a batch is free". It saves nothing on the resource-intensity limit: the portal still runs all fifty commands and still charges their time.

Measured on a live portal. The operating counter is kept per method, and a batch is charged on its own:

add probe-a          duration= 1.623 operating=   6.027     <- crm.deal.add's counter
batch of 20          duration=12.655 wall=12.03s            <- 20 x crm.deal.add
add probe-c          duration= 1.557 operating=   6.828     <- crm.deal.add's counter

jump = 0.801 for 20 commands in a batch — exactly one create, the probe-c one

That is, the write method's counter looks almost untouched after a batch. Whereas the counter of batch itself, over two consecutive batches of 15 creates:

[peek] baseline         batch.operating=  14.770
[write] batch 15 x crm.deal.add  wall=  8.07s
[peek] after batch #1   batch.operating=  18.759     (+3.989)
[write] batch 15 x crm.deal.add  wall=  8.85s
[peek] after batch #2   batch.operating=  23.123     (+4.364)

The cost is there, it is just in a different bucket. The wording now is: a batch moves the pressure from one limit to the other; it does not remove it.

2. Nowhere was it said how long a batch takes

Measured: 50 crm.deal.add = 28.5 seconds in one HTTP request (20 commands — 12.0 s, 15 commands — 8.1 s), roughly 0.6 s per command, the commands running sequentially — the envelope's date_start and date_finish are a whole duration apart.

The consequence is sharp and it costs data:

client := b24.NewClient(webhookURL,
	b24.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}))  // not enough

The connection is cut after the portal has created part of the deals. That is an ambiguous failure — the SDK does not replay it, and rightly so: a replay would create them a second time — but the result is lost, and with it the identifiers of everything already created. The SDK's HTTP client has no timeout by default, so this catches only the code that set one.

Where it was fixed

llms.txt (item 6 + a new trap under "Traps that cost data"), README.md (the batch section), the godoc of Batch and CallBatch, doc.go, CHANGELOG.md.

CHANGELOG: added to the 0.1.0 section, into the rest.batch item, as a description of what is true — not as a "Fixed". 0.2.0 is not published yet, the wrong wording never went out; the command-order bug was handled exactly the same way last time.

Checks

  • go build ./...
  • go vet ./...
  • gofmt -l . — no output
  • go test -race ./...

Compatibility

  • The change is additive: no code touched at all, only comments

Tests

The PR is a documentation one, but one claim in the text is worth being held by a test rather than by prose: TestBatchCutShortByAClientTimeoutLosesWhatItCreated. The server sleeps longer than the client timeout and only then answers with a result carrying an identifier — that is, the entities have already been created. What is checked is that the caller gets an error and a nil result (the identifiers are unreachable) and that the batch was sent once: a replay would create everything a second time. Run with -count=20 -race — it does not flake.

The existing TestBatchIsNotRetriedOnAmbiguousFailure covered a 502; this one covers precisely a connection cut on a long batch, the very shape the documentation now warns about.

llms.txt promised that "50 calls in one request cost ONE rate-limit
token instead of fifty", which is half true and reads as if a batch were
free. It saves the FREQUENCY limit. It does not save the work.

Measured on a live portal. The resource-intensity counter (operating) is
kept per method, and a batch is charged on its own: after a batch of 20
crm.deal.add, crm.deal.add's own counter had risen by exactly the ONE
add that went through a separate call, while the batch counter gained
about 4 seconds for each of two consecutive 15-command batches. Watching
the write method's counter therefore shows nothing while the cost is
real.

Nowhere did the docs say how long a batch runs. The commands execute one
after another inside a single HTTP request: 50 crm.deal.add took 28.5
seconds, 20 took 12.0, 15 took 8.1 — about 0.6s each. That is longer
than most default timeouts, and the failure is the expensive kind. An
http.Client{Timeout: 30s} cuts the connection after the portal has
already created part of the deals: an ambiguous failure the SDK rightly
does not replay, with the ids of everything created lost alongside it.

The behaviour the text now leans on is pinned by a test rather than left
to the prose: a batch cut short by a client timeout is sent once, comes
back with an error and no result.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ExaltedTrou6 ExaltedTrou6 reopened this Aug 7, 2026
@ExaltedTrou6
ExaltedTrou6 merged commit 4d6ff9e into main Aug 7, 2026
2 of 4 checks passed
@ExaltedTrou6 ExaltedTrou6 changed the title Батч: сказать, чего он стоит и сколько идёт Batch: say what it costs and how long it takes Aug 7, 2026
@ExaltedTrou6
ExaltedTrou6 deleted the docs/batch-cost branch August 7, 2026 15:32
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.

1 participant