Concurrency in Veeam is usually tuned by a method nobody would defend out loud. Task slots get raised until jobs start failing or the storage starts complaining, then lowered until the failures stop, and the resulting numbers stay in place for years. It works, in the sense that the environment eventually stops breaking. It also leaves no information about which limit was actually binding.
The cost of that method is not the time it takes. It is that the settings left behind are arbitrary. Nobody can say whether the environment is running near what the hardware allows or well below it, and when a new workload arrives the same blind adjustment starts again.
The alternative is not complicated. Task slots govern specific, knowable things, they interact in a predictable order, and in an object storage design the resource that runs out first is usually the same one. A baseline and one change at a time turns concurrency from folklore into a short series of measurements.
A task in Veeam is a unit of work against one object, usually one virtual disk or one agent volume, depending on the job type. A task slot is permission for one such unit to be in flight. The number configured on a component is a ceiling on simultaneous work, not an allocation of bandwidth and not a reservation of anything physical.
On a backup proxy, task slots bound how many disks are read and processed at once. The processing is real work, since the proxy handles deduplication, compression and encryption before anything leaves it, so proxy slots translate into CPU and memory demand. A common rule of thumb pairs each concurrent task with a physical core, but the right number depends on the compression level and on what else the proxy does.
On a repository, task slots bound how many tasks may write to that repository at once. This is a different constraint with a different consequence: when repository slots are the binding limit, proxies sit idle waiting for permission rather than waiting on data. Telling those two conditions apart is most of the diagnostic work, and it is visible in the job session detail rather than in the summary.
The three limits form a chain, and the smallest one decides the outcome. Adding proxy slots when the repository is the constraint produces more tasks waiting in a queue, not more throughput. Adding repository slots when the proxies are already saturated produces tasks that run more slowly rather than more tasks finishing.
The gateway adds a fourth link that is easy to overlook because it is not always visible as a separate object. With an object storage repository, traffic is marshalled by a gateway server before it reaches the storage, and that server has its own concurrency and its own network interface. Raising repository slots without checking what the gateway can carry moves the bottleneck rather than removing it, which is why gateway placement and sizing belongs in the same conversation as slot counts.
There is also a scheduling dimension. Concurrency limits only matter during overlap, and overlap is a property of the schedule rather than of the settings. A change that looks like a throughput improvement is sometimes two jobs that stopped colliding. Recording the overlap window alongside the measurements keeps that clear.
With an object storage target, the first thing to saturate is rarely the storage. Object writes are spread across many nodes and many disks, and the aggregate the storage can absorb is typically larger than what a small number of gateways and proxies can generate. The constraint is usually on the path rather than at the end of it.
In practice the order tends to be gateway network interface first, then proxy CPU, then repository slot count, then the storage itself. Each has a signature. A saturated interface shows flat throughput at a round number with low CPU everywhere. Saturated proxy CPU shows high utilization and falling per task rates. A slot ceiling shows queued tasks with everything idle.
Per task throughput is the most useful single figure, because it separates the two ways a limit can appear. If total throughput rises as tasks are added and per task throughput holds steady, there is headroom. If total throughput stays flat while per task throughput falls in proportion, a shared bottleneck has been reached and further slots only divide the same capacity into smaller pieces.
| Symptom | Likely binding limit | What to check |
|---|---|---|
| Tasks queue while proxy CPU stays low | A slot ceiling, on the proxy or the repository | Concurrent task counts against the configured maximum on each |
| Proxy CPU near saturation through the job | Too many concurrent tasks for the core count available | Cores per running task, compression level, other roles on that host |
| Total throughput flat while tasks are added | A shared bottleneck already reached | Per task throughput before and after the slot increase |
| Object requests slow, storage nodes idle | Network path or gateway capacity, not the storage | Interface utilization on the gateway during the job |
| Failures and retries only during peak overlap | Concurrency above what the path sustains under load | Which jobs overlap, and whether staggering removes the errors |
| One job slow, others unaffected | A source side limit rather than a concurrency limit | Bottleneck attribution in the session detail for that job |
A baseline is a normal run, measured before anything is adjusted, with the numbers written down: total duration, processing rate, per task throughput, and the bottleneck attribution the job session reports. Without it, every later measurement is compared against an impression.
The method is deliberately slow. Change one value, run the same jobs against the same data, compare the same four numbers. Two changes at once produce a result that cannot be attributed, and in concurrency tuning the two changes often push in opposite directions, so the combined result looks like nothing happened. Repeating a measurement at least once matters as well, since backup runs vary night to night for reasons unrelated to any setting.
Increases should stop while they are still working. When an increment produces a smaller gain than the one before it, the useful range has ended, and the last setting before that point is the one worth keeping. Pushing to the edge leaves no margin for a night when something else is running, which is how a tuned environment turns into a fragile one. The same measurements are useful on the recovery side, where the slowest step in a restore is often a concurrency limit rather than a media limit.
ARTESCA is object storage software used as a backup target, presenting an S3 compatible API and supporting immutable backup storage through S3 Object Lock. From the perspective of concurrency tuning it behaves as an S3 object repository, which means the Veeam side settings under discussion are the ordinary ones: repository task slots, gateway selection, and the proxy configuration feeding them.
Because it runs on infrastructure the customer operates, the network between the gateway and the storage is the customer's own, as is the placement of both. That is where most of the tunable headroom sits. Decisions about interface counts, subnet boundaries and where gateway roles run are made locally, and they have more effect on achievable concurrency than any single setting inside the backup software.
The settings themselves should be written down somewhere outside the console, with the date and the reason. A slot count with no recorded rationale is indistinguishable from a default, and the next person to look at it starts guessing again. One page per repository is enough: the values, the baseline, and what changed last.
Re-measurement belongs on a schedule rather than on an incident. Quarterly is reasonable for most environments, and after any change to proxies, repositories, the network path or the job schedule. Workload growth moves the binding limit without anyone touching a setting, so a configuration that was correct a year ago is not evidence that it is correct now. Where the symptom is a window that no longer finishes, the concurrency numbers and the phases of the backup window should be read together, since the two questions have the same underlying answer.