want to make sure an user has at most one order assigned
Due to some performance constraints I cannot guarantee this constraint with unique indexes.
I flat out don't believe that,
absent benchmark results.
But let's assume there exists no technology
that can support your required read and write
transaction rates.
I will design for ten billion user records,
and steady-state one thousand orders per second.
... receive two requests at the same instant, for the same user, only one of them should assign the order to the user and both should return the assigned order. And I have several replicas of the microservice deployed.
Use Postgres
or any other major RDBMS.
Partition
your user table, and add a NULLable currentOrder attribute to it.
Here the microservice replicas are not interesting,
but the number of nodes hosting your partitioned relation is.
... and both should return the assigned order.
That's fine, at the API level.
The database transaction will be an UPDATE of the user
WHERE currentOrder IS NULL, and it will fail if two
overlapping transactions are attempted, which is what we want.
The loser can re-query a moment later to retrieve
the winning order ID, and return that as the API response.
There's nothing fancy going on here,
as hash of user ID has brought both
clients to interact with the node
hosting the user of interest.
We don't need 2PC or ancillary log tables
or app-visible distributed locking,
since a SQL UPDATE suffices.
tl;dr: Rely on RDBMS features (partition across nodes,
high availability, locking, transactions) to deliver required API functionality
at required performance level.
EDIT
what do I win with [an RDBMS] approach?
ACID transactions
are kind of a big deal.
They let the infrastructure sweat the details,
rather than the app developer.
Being well tested, they are more likely to get the details right.
Here is what I know about your chosen NoSQL persistence layer:
https://en.wikipedia.org/wiki/Cosmos_DB#Partitioning
Cosmos DB added automatic partitioning capability in 2016 with the introduction of partitioned containers. Behind the scenes, partitioned containers span multiple physical partitions with items distributed by a client-supplied partition key.
That sounds like it should be enough to accomplish what you want,
which is getting clients in a horizontally-scaled high-availability
setup to agree to send their updates to a single arbiter node.
Does Cosmos expose a powerful enough API to meet your needs,
in the way that Postgres and other ACID solutions do? I don't know,
I have never used that product.
If your chosen NoSQL approach needs help from other technologies
on the side, this might be a good juncture to list out the
pros & cons, weighing whether relaxing ACID guarantees is a
good match for your business needs.
Redis is a nice cache that I have used to good effect.
I have seen operational issues when it is viewed as
a Source of Truth for an entity which appears only in Redis.
https://redis.io/docs/manual/transactions
What about rollbacks?
Redis does not support rollbacks of transactions ...
Maybe this is an easy-to-use paradigm which app developers
seldom get wrong. I couldn't say, as I have not used that
aspect of Redis. I definitely find some value in how
the various mainstream ACID technologies have been
battle tested in racy conditions.
Reduced risk and reduced testing cost may tip the ROI scales.
We're worried about races happening when updating a User record.
It is perfectly fine for currentOrder to
be an opaque Mongo guid -- there's no need
for a FK relationship from User to Order.
Storing user records in a backend which lacks
convenient transactional support sounds like
you're choosing to shoulder some additional
burdens up at the app layer. Whether that is
worth it is a business tradeoff.
Some teams are good at testing for races.
History shows that it's not an easy thing to get right.