Skip to content

Add Blob Versioning Support to Azurite - #2735

Open
Rodolfo Orozco Vasquez (rorozcov) wants to merge 92 commits into
Azure:mainfrom
rorozcov:users/rorozcov/blobversioning
Open

Rodolfo Orozco Vasquez (rorozcov) wants to merge 92 commits into
Azure:mainfrom
rorozcov:users/rorozcov/blobversioning

Conversation

@rorozcov

@rorozcov Rodolfo Orozco Vasquez (rorozcov) commented Aug 13, 2026 •

Copy link
Copy Markdown

Title: Add Blob Versioning Support to Azurite

Summary

Adds Azure Blob Storage versioning support to Azurite, enabling applications to create, list, retrieve, and delete previous blob versions during local development.

Closes #665.

Account model

This work introduces the account model abstraction designed in collaboration with the Azurite team. The account model provides a central place for account-scoped feature configuration so additional Azure Storage capabilities can be added without coupling them directly to server startup or individual blob handlers.

Blob versioning is currently the first and only feature backed by this abstraction. The design supports Azurite's multi-account mode, allowing each configured account to enable or disable versioning independently. Accounts without an explicit versioning setting retain the existing non-versioned behavior for backward compatibility.

Motivation

Blob versioning automatically preserves previous blob states after modification or deletion. Without emulator support, applications that depend on version-aware workflows cannot be tested locally before deployment to Azure.

Implementation

  • Adds an account-level AccountModel setting for enabling blob versioning.
  • Supports file-based and inline JSON account configuration, including multiple accounts.
  • Persists account configuration in Loki metadata storage.
  • Creates version IDs as ISO 8601 timestamps when version-producing blob operations occur.
  • Supports retrieving and deleting a specific version through versionId.
  • Supports listing versions through includeVersions with version-aware continuation markers.
  • Preserves previous versions as immutable records while tracking the current version.
  • Adds versioning behavior across block, append, and page blob operations.
  • Adds VS Code extension settings for account configuration.
  • Preserves backward compatibility by defaulting accounts without versioning configuration to versioning disabled.

Configuration

Blob versioning can be configured using:

  • --accountConfigFilePath for JSON configuration files.
  • --accountConfigAsJson for inline JSON configuration.

Configured accounts must also be present in AZURITE_ACCOUNTS for authentication.

Behavior

  • Block blob writes create versions except for Put Block.
  • Page and append blobs create versions for Put Blob, Put Block List, Set Blob Metadata, and Copy Blob.
  • Put Page and Append Block do not create versions.
  • Previous versions can be read or deleted using their version ID.
  • Version IDs use ISO 8601 timestamps with seven fractional-second digits. JavaScript supplies the millisecond timestamp, and the final four digits distinguish versions created within the same millisecond.

Limitations

This change does not currently support:

  • Soft-delete integration
  • Blob expiration with versioning
  • SAS URIs targeting specific versions
  • Version-level immutability policies

Validation

  • TypeScript build and lint pass.
  • Blob version pagination tests pass.
  • Startup and persisted-data upgrade regression tests pass with versioning disabled.

…tore. Now local testing needed. Saving progress
…tests with versioning enabled. Must add versioning related checks

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new continuation-token encoding for version-aware pagination is ambiguous for certain valid blob names and can break paging behavior, so it should be made robust before merge.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

src/blob/persistence/PageWithDelimiter.ts:226

  • Continuation tokens for name+timestamp mode are currently built by joining [name, timestamp] with the hard-coded __version_marker__ separator. If a blob name contains that substring, the token becomes ambiguous and marker.split(VERSIONING_MARKER) (e.g., in LokiBlobMetadataStore.listBlobs) will fail and return 400, breaking pagination for valid blob names. Consider encoding the marker tuple as an unambiguous format (e.g., JSON string or base64 of JSON) and accepting the legacy joined format for backward compatibility.
    src/blob/persistence/LokiBlobMetadataStore.ts:166
  • PR description mentions version IDs using JavaScript millisecond precision rather than Azure's 7 fractional digits, but the implementation converts timestamps to 7 fractional digits and even uses a per-blob sub-millisecond counter (see generateVersionId). Please align the PR description (and any external docs) with the implemented 7-digit RFC3339 versionId format so expectations match runtime behavior.
  • Files reviewed: 59/61 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@rorozcov

Copy link
Copy Markdown
Author

Rodolfo Orozco Vasquez (@rorozcov) could you please update changelog and readme file, and add use case in regression test also.

Will do! Give me 1-2 days at most.

Akanksha Jain (@jainakanksha-msft)

Your comments have been addressed. I am merging main into my branch again.

I am also working on the pagination continuation token and wanted your input. Initially, I made it very simple to keep it backwards compatible with how azurite used to do it. However, it is technically opaque from Azure's point of view so I am thinking of making it opaque as well rather than azurite's current use of the blob name as the continuation token and my PR's use of blobname__Version_marker__timestamp.

Let me know what you think. If not once I merge main we should be good to go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Continuation token encoding in PageWithDelimiter is ambiguous for certain blob names and can break listing pagination, and there is also a mislabeled test tag in the Azurite parity suite.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 59/61 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread src/blob/persistence/PageWithDelimiter.ts Outdated
Comment thread tests/blob/apis/versioning.azurite.parity.test.ts Outdated
Co-authored-by: rorozcov <44987991+rorozcov@users.noreply.github.com>
@jainakanksha-msft

Copy link
Copy Markdown
Member

Rodolfo Orozco Vasquez (Rodolfo Orozco Vasquez (@rorozcov)) could you please update changelog and readme file, and add use case in regression test also.

Will do! Give me 1-2 days at most.

Akanksha Jain (Akanksha Jain (@jainakanksha-msft))

Your comments have been addressed. I am merging main into my branch again.

I am also working on the pagination continuation token and wanted your input. Initially, I made it very simple to keep it backwards compatible with how azurite used to do it. However, it is technically opaque from Azure's point of view so I am thinking of making it opaque as well rather than azurite's current use of the blob name as the continuation token and my PR's use of blobname__Version_marker__timestamp.

Let me know what you think. If not once I merge main we should be good to go

Use base64url-encoded, versioned JSON containing:

type BlobListMarkerV1 = {
v: 1;
name: string;
timestamp: string;
recordId: number;
};
Ensure that listing uses the same tuple for:
Sorting records
Filtering records after the marker
Creating the next marker
This solution completely resolves the Blob-name collision, provides deterministic pagination, supports future changes, and keeps the public token appropriately opaque without introducing server-side state.

Co-authored-by: rorozcov <44987991+rorozcov@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are documentation/test labeling issues in the current diff that should be corrected before merge, in addition to the overall size/surface area of the change.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

tests/blob/apis/versioning.azurite.parity.test.ts:185

  • Files reviewed: 59/61 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread README.md Outdated
@rorozcov

Copy link
Copy Markdown
Author

Copilot Fix the code for all comments in this review thread.

When a review comment includes a suggested change, apply the suggestion exactly.

Do not make changes beyond what is described in the linked review thread.

Copilot AI review requested due to automatic review settings September 22, 2026 12:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/blob/utils/utils.ts
Comment thread src/blob/handlers/BlobHandler.ts
Comment thread src/blob/handlers/BlobHandler.ts
Comment thread src/common/EnvironmentFunctions.ts
Comment thread ChangeLog.md Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessTierChangeTime: date
},
snapshot: "",
isCommitted: true,
// accessTierInferred
},
snapshot: "",
isCommitted: true,
);

const blobServerFactory = new BlobServerFactory();
this.server = await blobServerFactory.createServer(env, accountModelStore);
Comment thread src/blob/BlobServer.ts
Comment on lines +80 to +82
if (!configuration.accountModelStore) {
throw new Error("Account model store must be provided in BlobConfiguration");
}
Comment thread src/blob/utils/utils.ts
Comment on lines +185 to +189
if (
versionId !== undefined &&
versionId !== "" &&
!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}Z$/.test(versionId)
) {
Preserve account configuration and version history while adopting upstream lease validation ordering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical and moderate correctness and compatibility findings remain, and validation was not executed.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 5 High severity · 2 Medium severity

Open (7)

Comment on lines 711 to +715
account: sourceAccount,
container: sourceContainer,
blob: sourceBlob,
snapshot
snapshot: snapshot,
versionId: versionId
public async fill(
reader: (offset: number) => Promise<BlobType[]>,
namer: (item: BlobType) => string,
markerFunc: (item: BlobType) => BlobListMarkerTuple,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable Blob versioning

5 participants