CodamAIDocs
Topicdone

Files and transaction

File contents are only prepared while the request runs and take effect after the commit. If the request fails, the file storage stays as it was.

Variants
successerror while preparing the fileerror after preparingerror after the commitdeleting

What this is about

A file model has two parts: the record in the database and the content in the file storage. See What a file model is.

The database has a transaction, the file storage does not. CDMS therefore lets the file storage follow the database: while the request runs, every change to the content is only prepared. It takes effect once the database has committed the record. If the request fails before that, CDMS throws away what it prepared.

Preparing and taking effect

Every change to content has two steps:

Stepwhenwhat happensvisible?
Preparewhile the request runsUploaded content is placed under a private name next to its final place. For a rollback, the requested version is copied there. A deletion is only noted. fileSize, mimeType and fileVersion are known from here on.no
Take effectafter the commitFor audited models the previous content is kept as a version, then the prepared content is moved into place with a single rename. A noted deletion removes the content and its versions.yes

The record needs fileSize, mimeType and fileVersion when it is saved. CDMS therefore reads them from the prepared content, which is byte for byte the content that becomes current later.

How an upload runs

sequenceDiagram
    participant C as Client
    participant D as CDMS
    participant F as File storage
    participant DB as Database
    C->>D: POST /fileasset/create/upload (record + file)
    D->>D: check role
    D->>F: prepare content (private name, not visible yet)
    D->>D: before hooks, validation
    D->>DB: save record, flush
    D->>DB: read back
    D->>DB: COMMIT
    D->>F: make content take effect (rename)
    D-->>C: 200 with the record

The rename happens in one step. So anyone downloading the file at that moment always gets a complete content, never a half-written one.

Where it can fail

An upload with an error

When: All steps succeed.

  1. 1
    CDMS→File storage
    prepares the content
  2. 2
    CDMS→Database
    saves the record, commit
  3. 3
    CDMS→File storage
    makes the content take effect

Result: Record and content match.

When: The file storage cannot take the content over, for example because the volume is full.

  1. 1
    CDMS→File storage
    preparing fails
  2. 2
    CDMS→Client
    500 file-not-saved, the database is rolled back

Result: Nothing is saved, neither record nor content.

When: The content is prepared, then something else fails: validation (422), a child's role (403), a hook, a database rule.

  1. 1
    CDMS→File storage
    prepares the content
  2. 2
    CDMS
    a later step fails
  3. 3
    CDMS→Database
    rolls back
  4. 4
    CDMS→File storage
    throws the prepared content away
  5. 5
    CDMS→Client
    error response

Result: Database and file storage are unchanged. When replacing, the previous content is still current.

When: The record is committed, but the rename in the file storage fails. This is very rare, because all that is left is a rename within one directory.

  1. 1
    CDMS→Database
    commit
  2. 2
    CDMS→File storage
    rename fails
  3. 3
    CDMS→Client
    500 file-not-saved

Result: The record is saved, the content is still the old one. The prepared content stays in the storage, it is the only copy. Sending the request again brings both back together.

Decision table

Database and file storage after a request
OperationRequest successful?State afterwards
create with fileyesrecord and content present
create with filenoneither record nor content
replace contentyesnew content current, for audited models the old one as a version
replace contentnorecord and previous content unchanged
deleteyesrecord and content including versions gone
deletenorecord, content and versions stay
any of the threecommit yes, rename or delete in the storage noerror response; the record is new, the storage still old

For deleting see Deleting file models, for restoring Rollback for files. Both follow the same pattern.

Concurrent uploads

Two requests that write new content to the same record at the same time would rename one after the other in the storage, and the later content would silently win. That is why CDMS locks the record before it prepares the content. The second request gets 409 and has not placed anything in the storage yet. See Concurrent changes.

How to deal with it

What comes next

Sources in the code and the knowledge base
  • CDMS/cdms-system-layer – AbstractLayer.recursivePrepare (stageFile before hooks, validation and save; lockForUpdate), recursivePatch (file branch), recursiveDelete (stageDeletion), rollbackFileContent (stageRollback), followTransaction (onCompletion)
  • CDMS/cdms-localfs-storage – LocalFSFileController.stageFile, stageDeletion, stageRollback, Staged.publish/discard; docs/adr/ADR-016-content-becomes-current-by-atomic-rename.md, ADR-021-content-changes-take-effect-after-the-commit.md
  • commons-persistence – DatabaseRequestContext.onCompletion, commitThreadTransactions, markRollbackOnly, closeEntityManager
  • CDMS/cdms-persistence-database – AbstractDatabasePersistence.rollback (discardCompletions)
  • CDMS/cdms-integrationtest – AbstractFileRollbackTest (aRefusedDeleteKeepsTheContentAndItsHistory, aRefusedReplaceKeepsTheCurrentContent, aRefusedRollbackKeepsTheCurrentContent, aRefusedCreateLeavesNoUploadBehind)
  • documentation/30-daten-und-persistenz/05-dateien-und-storage.md (consistency between blob and metadata)
Search