Download Reportworq
⬇ Guide PDF

Scale and size a contribution campaign#

A contribution form is not free. Every form a contributor opens becomes a live workbook in a worker process on the server, and that memory is held for as long as the form stays open. A server admits new contributor sessions only while it has memory to spare, so the cost of one form and the size of the server together decide how many people can contribute at the same time.

This guide is about the decisions. For the click-by-click mechanics of each measurement tool, see Profile and stress-test a campaign; this guide explains what to measure, how to read it, what arithmetic turns it into a server size, and how to confirm the answer while a real round is running.

The worked example is a Q3 budget round: one budget form per cost center, several hundred contributors, all of whom submit in the last week.

Before you start#

The sizing loop#

Run these six steps in order. Steps 1 to 3 repeat until the form is as cheap as you can reasonably make it; steps 4 to 6 happen once per deployment decision.

  1. Design the form. Author it as you want contributors to see it.
  2. Measure its footprint. Profile one form on the Memory profiling tab, and record the numbers.
  3. Tune and re-measure. Change one thing, profile again, and compare. Only a before-and-after pair on the same machine is meaningful.
  4. Size the deployment. Combine the measured cost per form with the server's usable memory to get a concurrency number, then a server count.
  5. Prove it under load. Build a stress plan at the concurrency you sized for, run it, and read what contributors experienced.
  6. Watch the live round. During the real campaign, confirm on the contribution process console (Settings ▸ Performance ▸ Contribution) that the sizing held.

What limits a server#

Two separate ceilings decide when a contributor is turned away. They fail differently, so diagnose them separately.

The memory admission valve. When a contributor opens a form, Reportworq checks every online server (the local one and any load-balanced servers) and steers the new session to the online server with the most available memory that is still able to accept one. A server can accept a session only while its memory used is below 90%. If no server qualifies, the form does not open and the contributor sees:

Unable to launch new Contribution session because all servers are at capacity. Please try again later

This is a hard refusal, not a warning or a queue. It is the reason per-form cost matters: usable capacity is bounded by that 90% wall, not by the machine's total RAM.

The per-worker session cap. Contribution sessions run in worker processes, and Max Sessions per Worker (on Settings ▸ Configuration ▸ Performance, under Contribution) limits how many sessions one worker process hosts. Its default is 1, so by default every live form is its own process. If workers report At capacity while server memory is still comfortable, the session cap is binding, not the form's footprint. See Server configuration.

What drives a form's cost#

Reportworq measures five things about a form's shape. The Memory profiling tab reports sheets, cells, and input cells alongside the workbook size; the Form sizing table on the Stress testing tab adds the formula count.

Measured property Where it appears Why it matters
Cells Memory profiling, Form sizing The count the per-cell cost is multiplied by. This is the dominant term.
Sheets Memory profiling, Form sizing Cells are counted across every sheet, so an extra sheet adds its whole cell block.
Input cells Memory profiling, Form sizing The cells a contributor can edit, and the ones a stress test fills.
Formulas Form sizing Formula cells across the workbook. Recalculation is part of what the profiler exercises.
File size Memory profiling, Form sizing The generated workbook on disk, a quick relative check between forms.

The profiler reports Bytes per cell (at peak), which is simply the measured peak cost divided by the cell count. Read the relationship in that direction and it stays honest: peak cost is roughly bytes per cell multiplied by cell count, so the reliable way to make a form cheaper is to make it smaller. Removing an unused sheet, or trimming a form down to the range contributors actually fill, removes cells and therefore removes cost, and the profiler will show you exactly how much.

Two things the measurements do not tell you, so treat claims about them as judgment rather than fact:

One campaign setting is a genuine, verifiable memory lever. Manual calculation mode (in the campaign's Options section) stops the form recalculating on every edit; contributors press Recalculate instead. Under manual calculation the server releases the session's workbook 30 seconds after the last action that needed it, and rebuilds it on demand when the next one arrives, so an idle contributor who has left a form open costs far less than one in automatic mode. The trade is that a contributor waits for the reload on their next action. For large forms filled slowly over a day, that trade is usually worth making. See Author a contribution campaign.

Measure the footprint#

Use the Memory profiling tab in this order. The click path for each run is in Profile and stress-test a campaign.

  1. Profile your heaviest single form first. Select one form, leave Flip through sheets and Enter sample data on, and run it. This gives the detailed per-form footprint.
  2. Tune, then re-run the same profile. Change one thing at a time and compare against the first run. Do the comparison on the same machine; the absolute numbers move with hardware, the deltas do not.
  3. Profile several forms together. Select the forms a real round will have open at once and run again. This is the run that produces the marginal cost per form, and it is the number the sizing arithmetic uses.

Read the results like this:

Number What it tells you How to use it
Peak per form (avg) The working memory one open form costs at its busiest, averaged over the scored cycles. The headline cost of a form. Compare before and after every change.
Steady (just opened) The cost right after the form opens, before any interaction. The floor a form costs simply by being open, which is what most contributors hold most of the time.
Bytes per cell (at peak) Peak cost divided by the cell count. A size-independent comparison between forms. Use it to spot a form that is expensive for its size.
Held after close Memory still held after the form closed, measured against the idle floor. A retention signal. A small figure is normal; a figure that climbs cycle after cycle is not.
Worker baseline What a worker process costs before any form is open. Add it per session when workers host one session each, which is the default.
Marginal cost per form The true cost of each additional concurrent form, measured after the first. The concurrency number. Use this, not the first-form cost, for the arithmetic.
First form The first concurrent form's cost, which carries one-time warm-up. Do not multiply this. It is a one-off.

The single-form result also shows an estimate reading ~N of this form open at once on the profiling machine. It is derived from the free memory on the profiling machine, taking 80% of it and dividing by the average peak per form. Treat it as indicative only: it reflects the machine that ran the profile rather than production, it ignores the worker baseline, and it ignores the per-worker session cap. Use it as a sanity check on the arithmetic below, not as the answer.

Turn the measurements into a sizing answer#

Work through the arithmetic with your own measured numbers. The figures below are illustrative.

What was measured for the Q3 budget round:

What the server offers:

Step 1: decide the memory you will plan to. The valve refuses at 90% used, so never plan to consume the full 90%. Plan to 80% and keep the last ten points as headroom for spikes and for other work on the machine.

80% of 64 GB                = 51.2 GB
minus the 12 GB idle floor  = 39.2 GB  (40,140 MB available for sessions)

Step 2: work out the cost of one concurrent contributor. This depends on Max Sessions per Worker.

Step 3: divide.

Default (one session per worker):  40,140 / 280 = 143 concurrent forms
Multiplexed (10 per worker):       40,140 / 130 = 308 concurrent forms

Step 4: convert concurrency into contributors. Nothing measures how many of your contributors are online simultaneously; you have to estimate it from how the round behaves. If roughly one in four of the roster is filling a form at the busiest moment, a server carrying 143 concurrent forms supports a roster of about 570 people.

Step 5: convert contributors into servers. For a roster of 1,200 at the same one-in-four peak, the round needs about 300 concurrent forms. At 143 per server that is 2.1 servers, so provision three. Always round up: the consequence of being one server short is contributors being refused, not contributors waiting.

The assumptions this arithmetic rests on. State them alongside the answer, because each one can move it:

Prove the experience with a stress test#

The arithmetic says how many forms fit. A stress test says whether contributors have an acceptable experience at that number. Build the plan on the Stress testing tab so the run answers the sizing question:

Then read the Analysis tab against these questions:

Read the outcome this way:

If a run fails, halve the concurrency and run again. The concurrency at which the findings disappear is your real per-server number, and it replaces the arithmetic.

Watch a live round#

Settings ▸ Performance ▸ Contribution is the operations console for the contribution runtime. It shows the present moment only, refreshing every 10 seconds, and it keeps no history. There are no timings, no percentiles, and no record of yesterday. It also identifies a session by input-form name only, so with two campaigns live at once you cannot attribute load to one or the other from this screen. Use it to confirm that the sizing held and to act when it did not, and use Campaign profiling for anything you need to measure or keep.

Full field-by-field detail is in Contribution processes. During a live round, three readings matter:

Two readings on this screen are easy to misread:

When the valve starts refusing sessions, free memory now and re-size afterwards:

  1. Select the server whose memory percentage is highest, then its hottest worker.
  2. Kill session on a session that is clearly abandoned. The contributor's unsaved edits are recorded and the form lock is released, so this reclaims a stuck session without losing work.
  3. Force GC on the worker to reclaim what it can.
  4. Drain a worker so it stops taking new sessions and exits when its current ones end, or Shutdown it if you accept ending its live sessions.
  5. After the round, treat the refusal as a sizing failure, not an incident. Re-measure the form, redo the arithmetic, and add memory or a server.

Red flags and what to do#

Symptom Likely cause What to do
Contributors are told all servers are at capacity The deployment is undersized for the peak, or the round peaked higher than estimated Free memory on the hottest server now, then redo the sizing arithmetic with the real peak and add memory or a server
Workers read At capacity while server memory is comfortable The per-worker session cap is binding, not memory Raise Max Sessions per Worker on Settings ▸ Configuration ▸ Performance, then re-measure the per-session cost, which falls when sessions share a worker baseline
Held after close climbs cycle after cycle The form is retaining memory after it closes Re-run the profile to confirm, download the CSV, and raise it with support. Until it is resolved, size on the retained figure as well as the peak
Bytes per cell far above your other forms The form is expensive for its size, for a reason the profiler does not attribute Remove one suspected element at a time and re-profile. Let the deltas identify the cause
A slow tail finding on opening a form Contention at peak concurrency, so most contributors are fine and the unlucky ones wait Re-run at half the concurrency. The level at which the finding disappears is the per-server number to size to
A slowdown finding on any action Something accumulates over the run, so a long round degrades Do not open the round at this size. Re-run at lower concurrency and confirm the finding is gone
High Served (lifetime) against a short Uptime Workers are recycling often, so contributors repeatedly pay a cold start Review Recycle Worker After Sessions on Settings ▸ Configuration ▸ Performance; its default is 25
A server shows a last seen note instead of workers The server has not reported for more than 5 minutes and is treated as offline Check the server. While it is offline it takes no sessions, so the remaining servers absorb the whole round

Notes and limits#

Feedback on this page

Comments, questions, requests, or something missing or unclear? Email us - the page you are on is filled in for you.

Email feedback on this page

Or write to support@reportworq.com directly.