Connect by JBRH Open Connect

Handling more volume, end to end

Four limits bind, and rarely the one people expect. Worker capacity refuses calls at a load of 0.85 per core; the plan's daily allowance holds sends; the model quota opens a breaker on a 429; and query cost grows with the business rather than with traffic. Measure statements before adding anything, because the fourth limit is the one that looks like all the others.

Status
Available What this means
Audience
both
Channels
phoneemail
In the app
#/billing, #/calls, #/home
Last verified
Product version
6.3.2

The four limits, in the order they usually bind#

LimitWhat you seeWhere it is setWhat it does when reached
Worker capacityCalls refused before they ringWORKER_BUSY_LOAD, 0.85 one-minute load per corering answers not_ready, so the follow-up drain retries rather than losing the call
Daily allowanceApproved replies sit and do not goThe workspace's planThe send waits; the approval is recorded and the item stays in the queue until it goes
Model quota or budgetScheduled work quietly skippedai_quota, budget_refusalA breaker opens on a 429 or a revoked key: scheduled work is skipped, a caller already on the line is still attempted
Query costEvery screen slows at once, in proportion to the businessNowhere — it is a property of the queryNothing refuses. It just gets slower, which is why it is found last

The chain more volume runs through#

  1. TRIGGER — traffic rises: more inbound calls, more mail per hour, more contacts in the workspace.
  2. USER / EXTERNAL EVENT — customers arrive faster than before; nothing in the product is switched on to cause it.
  3. AUTH / WORKSPACE RESOLUTION — unchanged per request, but every request still resolves a workspace and is filtered three times: the allowlist, the ORM kernel and row-level security.
  4. INGEST / REQUEST — mail is fetched per mailbox and calls arrive per line. Burst handling and the cursor decide whether a spike is absorbed or dropped.
  5. CANONICAL RECORD — more rows. This is where query cost begins to bind, because a query that reads a slice of rows changes its answer as the slice stops covering the set.
  6. CLASSIFICATION / RESEARCH / REASONING — one loop thinks. Adding processes does not add thinkers; the engine tick is not the place to scale.
  7. KNOWLEDGE + MEMORY + RULES — unchanged by volume. The per-call budgets are per call, not per workspace.
  8. AUTONOMY / APPROVAL — a rising queue in Needs You is the first human-visible symptom of a limit, and it is usually the allowance rather than the engine.
  9. ACTION / PROVIDER — capacity is checked here. A worker over the busy load refuses new calls; a worker reporting no load reads as free, because refusing calls on an old worker's silence would be worse than the saturation it guards.
  10. RESULT — refusals are outcomes, not errors: not_ready retries, a budget refusal plays the spoken closed-line message instead of leaving a caller in silence.
  11. RELATIONSHIP / TIMELINE / MEMORY — unchanged, but timeline and 360 views are exactly the screens that N+1 queries make slow first.
  12. AUDIT / USAGE / NEEDS YOU — usage and cost are per workspace and per call, so the ledger tells you which line or mailbox grew.
  13. NEXT — change one limit, re-measure, and only then look at the next. Two changes at once make the improvement unattributable.

Measure statements, not seconds#

Wall-clock timing on a small workspace hides the failure mode that matters, which is a query count that grows with the number of rows rather than with the number of users. Counting statements makes it visible at any size. These are the measured reductions on this codebase:

  • The Owner's customer list: 5,574 statements to 3.
  • The triage strip: 83 to 11, and flat with page size.
  • The workspace console: 89 to 17, also flat.
  • Home: 133 to 15, flat.

Flat with page size is the property to insist on. A screen that costs eleven statements at twenty rows and eleven at two hundred will not become an incident later; one that costs eleven at twenty and four hundred at two hundred already is one.

The counting bug that arrives with growth#

A headline number computed from a page of rows is correct until the business outgrows the page, and then it is quietly wrong. Both of these were real.

  • The conversation list read 400 rows and did counting, filtering, searching and paging in Python over that slice. On a 430-conversation workspace the Closed tab read 0 and page five was empty.
  • prospect_summary derived every headline number from the newest 500 rows. On a 540-contact workspace it reported 500 organisations when there were 530, and 0 already contacted when there were 40.

Neither raised an error, and neither was visible on a small workspace. When volume rises, the first thing to distrust is any number that could have been counted in the application rather than by the database.

What to change, in what order#

  1. Count statements on the slowest screen at two workspace sizes.

    Result You learn immediately whether the cost is per request or per row. Only the second kind gets worse on its own.

  2. Fix anything that grows with row count before adding capacity.

    Result Capacity added underneath a query that grows is capacity spent to stand still.

  3. Then look at worker capacity, using the load the workers report.

    Result The busy threshold and the number the worker hands the media server as its own admission control are the same 0.85, so the gate and admission control cannot disagree.

  4. Then the allowance and the budget.

    Result These are commercial limits, not engineering ones. A customer's use is bounded by their plan; the Owner has no plan and no gates.

  5. Deploy with drain time in mind.

    Result Draining allows 180 seconds for calls in progress, and the service unit allows 210 before the process is killed. A heartbeat file forgets a process not seen for an hour, so a stopped worker stops counting as capacity.

Questions#

Will more workers make replies faster?

No. Workers decide how many calls can run at once, not how quickly any one of them answers. Reply time is the model's first token plus end-of-turn detection, with a measured floor of 3.3 seconds median on the best call — adding capacity does not move it.

What happens to a caller when there is no capacity?

The call is refused before a row exists, as not_ready, so a follow-up drain retries it rather than recording a failure. A caller refused for budget hears the spoken closed-line message in the business's own name instead of silence.

Does volume change what Connect is allowed to do?

Only through the allowance. Autonomy, suppression and consent are unaffected by load; the plan decides how much may be sent in a day, and reaching it holds work rather than widening any permission.