Bulk updates and deletions
Bulk mutations change or delete user data across many users in one request. They do not change data immediately. Instead, each bulk mutation submits a bulk job that runs in the background. A job starts as a dry run by default, so you can review what it would do before it touches any data.
For single-user writes that take effect immediately, see Write user data. To follow a job after you submit it, see Monitor and cancel bulk jobs.
Choose a mutation
Four mutations cover the two kinds of user data and the two kinds of change.
| Mutation | Data | Effect |
|---|---|---|
bulkPatchArrayItems | Array items | Merges changes into the value of every selected item. |
bulkDeleteArrayItems | Array items | Deletes the selected items. |
bulkPatchKeyValues | JSON key-value entries | Merges changes into the value of every selected entry. |
bulkDeleteKeyValues | JSON key-value entries | Deletes the selected entries. |
All four mutations return a job that you poll with the bulkJob query.
They require a User Account Token or a Service Account Token with the BULK_DATA_WRITE or ADMIN permission.
See Authentication and permissions for details.
Key-value bulk jobs only work on JSON entries; array bulk jobs operate on array items. Playback progress entries are read-only in the Management API and are never selected, changed, or deleted, even when they are stored under the same key.
Tier and scope
Every bulk mutation takes a tier that you choose explicitly.
The tier sets how far the job reaches within your environment.
It is never inferred from the filter.
| Tier | Reach | Required filter fields |
|---|---|---|
ENVIRONMENT | Every user in the environment, under one key. | key |
APPLICATION | Every user's application-scoped data of one application, under one key. | key, applicationId |
USER | One user's data under one key. | key, userId |
If the filter lacks a field that the tier requires, the request is rejected with a FILTER_CONSTRAINT error and no job is created.
The tier is not the same as the data scope.
The data scope (USER, PROFILE, or APPLICATION) describes how a record was stored.
The tier describes how many users a job can reach.
You can narrow a job further with the optional filter fields profileId, applicationId, and scope.
A scope and the IDs you give with it must agree.
For example, an applicationId only matches application-scoped data, and a request whose IDs contradict its scope is rejected with FILTER_CONSTRAINT.
Dry run by default
Every bulk mutation accepts a dryRun argument that defaults to true.
When you omit it, the job only reports what it would match and change, and it writes nothing.
To change or delete data, you must pass dryRun: false explicitly.
A dry run is a real job. It is queued, runs, appears in the job list, and counts toward the one-active-job limit described in Monitor and cancel bulk jobs.
Recommended workflow
Follow these steps for every bulk change.
- Select. Use
listArrayItemsandlistKeyValuesto look at a sample of the records you want to change, and decide on the tier, filter, and match. - Dry run. Submit the mutation without
dryRun, or withdryRun: true. - Review. Poll the job until it finishes, then read
results. Check thatmatchedandchangedlook reasonable for your intent, and checkrejected. - Run. Submit the same mutation again with
dryRun: false. - Monitor. Poll the job until it reaches a final status, then read
resultsagain. Reviewskippedandrejected.
A dry run is a preview, not a snapshot guarantee. User data can change between the dry run and the real run, and the real run evaluates the data as it is when the job runs. Treat dry run counts as an estimate of scale and a check on your filter and match, not as a promise of what the real run will do.
Patch array items
bulkPatchArrayItems merges a list of match and patch pairs into the values of array items.
mutation PatchArrayItems($input: BulkPatchArrayItemsInput!) {
bulkPatchArrayItems(input: $input) {
job {
id
operation
tier
dryRun
status
submittedAt
}
}
}
{
"input": {
"tier": "ENVIRONMENT",
"filter": {
"key": "content:favorites"
},
"patches": [
{
"match": { "value": { "contentType": "movie" } },
"set": { "category": "film" },
"remove": ["legacyFlag"]
}
]
}
}
Because the example omits dryRun, it submits a dry run.
Add "dryRun": false to the input object to apply the patch.
How patches work
Each pair has an optional match, an optional set, and an optional remove.
matchselects the items the pair applies to. Omit it to apply the pair to every item the filter selects.setis an object whose top-level properties are added to the item value or overwrite existing properties.removeis a list of top-level property names to remove from the item value.
Patches only work on object values. Items whose value is not an object are never changed. Properties that a pair does not set or remove stay as they are.
When several pairs match the same item, their patches are merged in list order, so a later pair wins on every property that it also sets or removes.
A patch does not change the item order, the item count of the array, or the item's modification date.
See Filtering, matching, and patching for the match rules and the limits on set and remove.
Delete array items
bulkDeleteArrayItems deletes array items.
The optional match limits the deletion to items whose content matches.
When you omit match, the job deletes every item that the filter selects.
mutation DeleteArrayItems($input: BulkDeleteArrayItemsInput!) {
bulkDeleteArrayItems(input: $input) {
job {
id
operation
tier
dryRun
status
}
}
}
{
"input": {
"tier": "APPLICATION",
"filter": {
"key": "content:recently-viewed",
"applicationId": "1f0d2c1e-3a4b-4c5d-8e6f-7a8b9c0d1e2f"
},
"match": { "itemId": "movie:100" },
"dryRun": true
}
}
This example previews deletion of the item movie:100 from the content:recently-viewed arrays for one application.
After reviewing the dry run results, submit it with dryRun: false to apply the deletion.
Deleting items also updates the item counts of the arrays they belonged to.
Patch JSON key-value entries
bulkPatchKeyValues works like bulkPatchArrayItems, but it patches the values of JSON key-value entries.
The match of a pair has a required value object that must be a subset of the entry value.
mutation PatchKeyValues($input: BulkPatchKeyValuesInput!) {
bulkPatchKeyValues(input: $input) {
job {
id
operation
tier
dryRun
status
}
}
}
{
"input": {
"tier": "USER",
"filter": {
"key": "settings:playback",
"userId": "8c6d4b2a-1e3f-4a5b-9c7d-0e1f2a3b4c5d"
},
"patches": [
{
"match": { "value": { "quality": "auto" } },
"set": { "quality": "high" }
}
],
"dryRun": true
}
}
Only JSON entries with an object value are patched. Scalar values and progress entries are never changed.
Delete JSON key-value entries
bulkDeleteKeyValues deletes JSON key-value entries.
As with array items, the optional match limits the deletion to entries whose value matches, and omitting it deletes every JSON entry that the filter selects.
mutation DeleteKeyValues($input: BulkDeleteKeyValuesInput!) {
bulkDeleteKeyValues(input: $input) {
job {
id
operation
tier
dryRun
status
}
}
}
{
"input": {
"tier": "ENVIRONMENT",
"filter": {
"key": "settings:legacy"
},
"match": { "value": { "version": 1 } }
}
}
This example is a dry run, because it omits dryRun.
Progress entries stored under the same key are not deleted.
What a submitted job returns
Submitting a mutation returns the queued job.
The job id identifies it for the rest of its life.
Use it with the bulkJob query and the cancelBulkJob mutation.
See Monitor and cancel bulk jobs for how to read progress and results.
Invalid input is rejected before a job is created.
The error extensions identify the problem, for example FILTER_CONSTRAINT for a missing tier field, PATCH_CONSTRAINT for the patch list, a pair's set or remove, and MATCH_CONSTRAINT for an empty or malformed match.
If another job is already queued or running in the environment, the request fails with BAD_USER_INPUT and the reason JOB_LIMIT.
See Errors and limits for the full list.
What a bulk job does not guarantee
- A bulk job has no rollback. If a job stops early, the changes it already applied stay in place. See Partial completion and cancellation.
- A job scans the selected records once. To pick up records that were added or changed afterwards, submit the job again.
- If a record changes between the scan and the write, the job skips that record. See Skipped and rejected records.