firebase-js-sdk icon indicating copy to clipboard operation
firebase-js-sdk copied to clipboard

FR: Add Firestore FieldValue.min() and FieldValue.max() for atomic numeric comparisons

Open rscotten opened this issue 2 months ago • 8 comments

Operating System

not relevant

Environment (if applicable)

not relevant

Firebase SDK Version

not relevant

Firebase SDK Product(s)

Firestore

Project Tooling

not relevant

Detailed Problem Description

✨ Feature Request: Add FieldValue.min() and FieldValue.max() for atomic numeric and timestamp comparisons

Background

Firestore provides several atomic field transformation helpers such as:

  • FieldValue.increment(n: number)
  • FieldValue.arrayUnion(...elements: any[])
  • FieldValue.arrayRemove(...elements: any[])

These allow developers to update documents without first fetching their current state, which prevents race conditions and reduces read costs.

For example, FieldValue.arrayUnion appends unique elements if they don’t already exist, and all of these helpers automatically create the field if it doesn’t yet exist.

This makes Firestore updates concise, atomic, and efficient.


Problem

There is currently no built-in way to perform atomic minimum or maximum updates on a field.

Developers must:

  1. Fetch the document,
  2. Compare the existing value with the new one client-side, and
  3. Write the result back.

This introduces both extra read costs and race condition risks in concurrent update scenarios.


Proposed Solution

Add two new atomic field transformations:

FieldValue.min(value)
FieldValue.max(value)

These would compare the provided value against the existing field value and update the field to the smaller or larger of the two.

If the field does not exist, Firestore should initialize it with the provided value (consistent with existing FieldValue semantics).


Use Case

I’m building an ecommerce SaaS platform for rental tracking. Each inventory item tracks the timestamp of its last rental activity:

item.lastActivityAt = <timestampMilliseconds>

The system must maintain this field as the latest known return date across thousands of concurrent rental line items.


Example

  • SKU with quantity = 2
  • One unit is rented this morning for 10 weeks (return date = +10 weeks)
  • Another unit is rented this afternoon for 1 week

Without atomic max support:

  • The later update (1-week rental) could overwrite the earlier one (10-week rental) if both updates happen close together.

The correct logic should be:

item.lastActivityAt = max(existingValue, newReturnDate)

Currently, this requires a fetch-compare-update cycle for every line item, which is both costly and inefficient:

  • ~10,000 line items processed per day → ~10,000 extra reads
  • Higher latency and risk of stale writes

Benefits

  • Enables atomic max() and min() updates similar to increment()
  • Avoids race conditions in concurrent write scenarios
  • Reduces read/write load by eliminating the need to prefetch documents
  • Applies broadly across use cases:
    • Tracking latest timestamps (lastSeenAt, lastLoginAt, etc.)
    • Maintaining running bounds (minPrice, maxTemperature, etc.)
    • Recording latest event times (latestUpdateAt, lastActivityAt, etc.)
  • Keeps Firestore’s expressive and consistent update API style

Suggested API

await docRef.update({
  lastActivityAt: FieldValue.max(newReturnDate)
});

Alternative Considered

Using Cloud Functions or a scheduled reconciliation process to serialize updates and compute maxima server-side.

However, this approach introduces latency, cost, and code complexity.

Having a server-side atomic comparison primitive would be faster, cheaper, and more reliable.


Summary

Adding FieldValue.max() and FieldValue.min() would be a natural and powerful extension of Firestore’s atomic update family.

It would dramatically simplify code, reduce operational costs, and prevent concurrency bugs for developers managing high-volume, multi-writer workloads.

rscotten avatar Oct 09 '25 19:10 rscotten

Hi @rscotten. I noticed you opened this same feature request on the server sdk (https://github.com/googleapis/nodejs-firestore/issues/2435) also. Could you clarify whether you are asking for this feature on the client sdk (the firebase-js-sdk repository) or the server sdk (the nodejs-firestore repository)? Based on your description is seems that you are asking for this in the client sdk.

One thing I'd like to point out is that the FieldValue.increment, FieldValue.arrayUnion, and FieldValue.arrayRemove operations are not fully "atomic" in the client sdks. This is because if the network connection gets dropped after sending, for example, the FieldValue.increment request to the backend then the acknowledgement of the write may be lost, in which case the client will send the FieldValue.increment again, once network connectivity is restored, resulting in an increment by 2 instead of 1. Obviously, this is rare, but happens occasionally.

If you need true atomicity guaranteed then the only way to do that is via Firestore transactions which allow performing read-modify-write operations atomically.

dconeybe avatar Oct 14 '25 16:10 dconeybe

Hi @dconeybe, I need it both for the client (web browser) SDK and server NodeJS (admin) SDK.

Noted regarding the FieldValue functions not being atomic and to use transactions instead. Thanks for pointing that out. I guess I knew that on some level. I have document listeners with a FieldValue.increment(1) function counting newly created documents. When bulk creating tens of thousands of new documents at once, the resulting count is always short of the actual count, presumably because the cloud function was overloaded and some percentage of FieldValue.increment(1) operations were dropped/lost. I usually have to run a clean-up function to update the actual count, but the FieldValue.increment(1) works well enough for most other use-cases.

rscotten avatar Oct 14 '25 17:10 rscotten

After discussing with the team it turns out that this feature, FieldValue.min() and FieldValue.max(), was actually a previous project that almost got completed. The good news is that the backend ostensibly has support for this feature (as can be seen in the proto rpc definition) and, therefore, only client SDK work is remaining.

Please take a look at the link above to the proto rpc definition and read the documentation to ensure that the behavior satisfies your use case.

There is a possibility that we could work on it as a background project, but I can't make any commitments at this point as to when it would be publicly available. I will post updates here though.

dconeybe avatar Oct 14 '25 18:10 dconeybe

@dconeybe thanks for referencing that proto RPC definition. I read through it and that all works for me. Thank you!

rscotten avatar Oct 14 '25 19:10 rscotten

hi @dconeybe, hope you're well. Has there been any movement on this?

rscotten avatar Nov 10 '25 20:11 rscotten

Hi @rscotten, no, there has not yet been any movement. It is still on our radar though and I've assigned it to @MarkDuckworth, who has agreed to move it forward. I can't provide an ETA but I'd hope to see it released in early 2026.

dconeybe avatar Dec 01 '25 19:12 dconeybe

@dconeybe Awesome and thank you!

rscotten avatar Dec 01 '25 23:12 rscotten

Hi @dconeybe & @MarkDuckworth,

I'd like to contribute the client-side implementation of FieldValue.min() and FieldValue.max() for both the firebase-js-sdk and @google-cloud/firestore.

I understand from the discussion that:

  1. The backend proto already supports these transforms (minimum and maximum operations)
  2. The main work needed is client-side SDK implementation
  3. The pattern should follow existing transforms like increment()

I've examined the codebase and believe the changes would primarily involve:

  • Adding the two public static helpers to FieldValue class
  • Wiring them through the existing serializer (following the increment pattern)
  • Adding comprehensive unit and integration tests
  • Updating TypeScript definitions and documentation

Could you confirm if you're open to an external contributor picking this up? If so, I can open a PR in the next few days with the implementation.

Also, a few clarifying questions:

  1. Are there any specific design considerations or edge cases I should be aware of?
  2. Should the implementation support both numbers and timestamps (as indicated in the proto)?
  3. Are there any existing WIP branches or design docs I should review?

Thank you for considering this contribution!

Best regards, Aditya

singhaditya73 avatar Dec 08 '25 16:12 singhaditya73