Skip to content

bulkOperationsSingleTransaction: bulk update and delete return documents that were rolled back #18494

Open

Description

@Livog

Describe the Bug

bulkOperationsSingleTransaction is documented as: "When true, bulk update and delete operations will process documents one at a time in separate transactions instead of all at once in a single transaction." The DocumentDB and Cosmos DB compatibility options turn it on by default.

With it on, a failing document rolls back the documents processed before it, but the operation still returns them in docs as updated or deleted. Callers, including the REST bulk endpoints the admin list view uses, are told a write happened when it didn't.

In the reproduction, hooks refuse to update or delete the post titled fail. The posts are a, b, fail, c, d:

Operation Returned in docs Actually in the database
bulk update (sorted by createdAt) a, b, c, d updated only c, d updated
bulk delete a, b, c, d deleted all five posts still there

Expected: every document has its own transaction, so a, b, c, d are updated or deleted, and only fail is reported in errors.

Where it happens in payload@3.90.2 (dist/collections/operations):

  1. update.js:37 and delete.js:28 start one transaction for the whole operation. The per-document initTransaction(req) (update.js:206, delete.js:92) then returns false, because req already has that transaction, so no document gets its own.
  2. A failing document's catch calls killTransaction(req) (update.js:291, delete.js:225). That rolls back the shared transaction, including the documents already returned in docs, and removes it from req, so the documents after it start their own transactions.
  3. For delete, docs.map(async ...) at delete.js:85 starts every document at once, so the loop at delete.js:238-242 doesn't run them one at a time. That part is also reported in payload.delete({ where }) silently fails to delete records — concurrent deleteOne race condition (regression since 3.41.0) #16075. fix: prevent bulk operation data loss and surface upload failures #18266 makes bulk operations sequential but keeps the operation-level transaction, so the rollback above still happens with it.

In 4.0.0-canary.37, delete.js:29 already skips the operation-level transaction when the option is on and processes documents one by one. update.js:38 still starts it, so bulk update has the same problem on v4.

Skipping the operation-level initTransaction when bulkOperationsSingleTransaction is on, in both update.ts and delete.ts (as canary's delete does), and running the delete documents one after another fixes it: with 3.90.2's dist patched that way, both tests in the reproduction pass.

Link to the code that reproduces this issue

https://github.com/Livog/payload-bulk-operations-single-transaction-repro

Reproduction Steps

  1. Clone the reproduction (created with create-payload-app -t blank, MongoDB). The second commit holds all changes from the template.
  2. pnpm install
  3. pnpm test:int

No database setup is needed: the test starts a single-node MongoDB 7.0.24 replica set with mongodb-memory-server.

Both tests fail, and the console shows what Payload returned next to what is in the database:

bulk update {
  reportedUpdated: [ 'a', 'b', 'c', 'd' ],
  reportedErrors: [ 'This post cannot be updated' ],
  actuallyArchived: [ 'c', 'd' ],
  notArchived: [ 'a', 'b', 'fail' ]
}
bulk delete {
  reportedDeleted: [ 'a', 'b', 'c', 'd' ],
  reportedErrors: [ 'This post cannot be deleted' ],
  stillInDatabase: [ 'a', 'b', 'c', 'd', 'fail' ]
}

Files:

  • src/payload.config.ts: mongooseAdapter({ bulkOperationsSingleTransaction: true })
  • src/collections/Posts.ts: hooks that refuse to update or delete the post titled fail. The delete hook waits one second first, standing in for async work, so the other posts are processed before it refuses.
  • tests/int/bulk-operations-single-transaction.int.spec.ts: seeds the posts, runs the bulk update and the bulk delete, and compares the result with the database.

Which area(s) are affected?

area: core, db: mongodb

Environment Info

Binaries:
  Node: 22.14.0
  npm: 10.9.2
  Yarn: 1.22.22
  pnpm: 10.34.6
Relevant Packages:
  payload: 3.90.2
  next: 16.3.3
  @payloadcms/db-mongodb: 3.90.2
  @payloadcms/graphql: 3.90.2
  @payloadcms/next/utilities: 3.90.2
  @payloadcms/richtext-lexical: 3.90.2
  @payloadcms/translations: 3.90.2
  @payloadcms/ui/shared: 3.90.2
  react: 19.2.6
  react-dom: 19.2.6
Operating System:
  Platform: darwin
  Arch: arm64
MongoDB: 7.0.24, single-node replica set (mongodb-memory-server 10.4.3)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions