Consent check benchmark
What one consent check took on one test machine, how it was measured, and what the figures do not promise.
These are the figures of one run of the consent check on one shared workstation. They describe that run only. They are not a promised response time.
What was measured#
| Fact | Value |
|---|---|
| Script | scripts/bench-consent-check.mjs in the repository |
| Date | 3 October 2026, 15:27 to 15:32 UTC |
| Code | This release, built on commit 86ec182 |
| API version | Server package 1.0.0 |
| Call | POST /sdk/v1/consent/check with one identifier, purposeKey: "marketing", no partnerKey and no alsoKnownAs |
| Method | 500 checks to warm up, then 60 seconds at each number of calls in flight |
| Unknown people | 10% of calls used an id nobody holds |
Set-up#
| Fact | Value |
|---|---|
| Host | One developer workstation, shared. The API, PostgreSQL and the script ran on it, and the script called the API over loopback. |
| Other load | Other work kept running, including a video-encoding job that used up to about 6 cores at times. Load average was 4.4, 7.5 and 12.1 at the start and 13.6, 10.6 and 12.1 at the end. |
| CPU | 11th Gen Intel Core i9-11900, 8 cores, 16 threads, 1 socket |
| Memory | 93 GiB |
| Node.js | v20.20.2, for the API and the script |
| API | 1 replica, one Node.js process, NODE_ENV=development, compiled JavaScript. The request log wrote one line per request to a file. Database pool of 20. |
| PostgreSQL | 18.6 in the postgres:18-alpine container, with its default settings |
| Redis | Off. The budgets were counted in the API process. |
| Budgets | SDK_CHECK_MAX_PER_MINUTE=600000 and RATE_LIMIT_SDK_MAX=600000. A live secret key with consent:check, 600,000 requests a minute and 600,000 checks a minute. No budget refused a call. |
| Database | A scratch copy of a development database: 52,520 records, 54,731 events and 6,688 linked identifiers after seeding |
| People | 20,000 people bench-<n> under USER_ID on one form with five purposes. Every third was linked to a phone number and checked by it. Every fourth refused marketing. Every tenth was withdrawn afterwards. |
The budgets were raised to 600,000 so they did not shape the figures. With the defaults, a key may check 600 identifiers a minute, and at most 6,000 unless the operator raises it. One address may make 3,000 calls to /sdk a minute.
Results#
The script's own table, unchanged:
| Calls in flight | Checks | Per second | p50 ms | p95 ms | p99 ms | Max ms | Errors |
|---|---|---|---|---|---|---|---|
| 1 | 11971 | 200 | 4.7 | 7 | 8.4 | 33.4 | None |
| 8 | 25010 | 417 | 17.8 | 29.5 | 35 | 66.5 | None |
| 32 | 22858 | 381 | 88.5 | 103.2 | 110.8 | 249.7 | None |
The answers matched how the people were seeded: about 10% no_record, 9% withdrawn and 18% refused.
{"concurrency":1,"checks":11971,"perSecond":200,"p50":4.7,"p95":7,
"p99":8.4,"max":33.4,"errors":{},"reasons":{"granted":7470,
"refused":2212,"no_record":1216,"withdrawn":1073},"seconds":60}
{"concurrency":8,"checks":25010,"perSecond":417,"p50":17.8,"p95":29.5,
"p99":35,"max":66.5,"errors":{},"reasons":{"granted":15722,
"refused":4538,"withdrawn":2189,"no_record":2561},"seconds":60}
{"concurrency":32,"checks":22858,"perSecond":381,"p50":88.5,"p95":103.2,
"p99":110.8,"max":249.7,"errors":{},"reasons":{"no_record":2249,
"granted":14435,"withdrawn":2088,"refused":4086},"seconds":60.1}Throughput stopped rising between 8 and 32 calls in flight. A short sample at 32 in flight showed the single API process at about 86% of one core. One process was the limit on this host, so more calls in flight only made each one wait longer.
What the figures do not tell you#
- Your network. Your systems also wait for the trip to your deployment and back, which depends on where it runs.
- A promise. There is no service-level commitment on how fast a check answers.
- Default budgets. The run used raised budgets. With the defaults, the budgets refuse calls long before these rates.
- Your hardware. The host was shared and busy. Run the script on your own deployment to measure it:
API_URL=https://consent.example.in \
PIEDAP_SECRET_KEY=sk_… \
FORM_KEY=in-banner PURPOSE_KEY=marketing SEED=1 \
node scripts/bench-consent-check.mjsSEED=1 writes 20,000 people first, so run it only against a database made for the run. The key needs consent:check and raised budgets.