spicedb icon indicating copy to clipboard operation
spicedb copied to clipboard

Introduce singleton relations

Open artie-shevchenko opened this issue 2 months ago • 6 comments

Problem Statement

That's basically adding a support for "at most once relationship" restriction on a relation. So that if we write a new relationship for a singleton relation that already has an existing one, the existing one is deleted automatically.

Similar to assigning a field value in a Java class - the old value just goes away. That's something everyone is so used to and that's definitely one of the first confusion points for SpiceDB newbies from my experience, that they can't enforce "at most once relationship" restriction for certain relations in SpiceDB schemas.

Solution Brainstorm

Just some sort of a marker on a relation, I don't think I have any preferences for the syntax.

artie-shevchenko avatar Oct 22 '25 06:10 artie-shevchenko

As a workaround, have you tried exploring the optional_preconditions field on WriteRelationshipsRequest solve your use case?

jzelinskie avatar Oct 22 '25 20:10 jzelinskie

As a workaround, have you tried exploring the optional_preconditions field on WriteRelationshipsRequest solve your use case?

@jzelinskie that would just fail the write, not remove the existing relationship, right?

artie-shevchenko avatar Oct 22 '25 23:10 artie-shevchenko

Etcd's version of "preconditions" lets you specify an operation to perform when the condition is true and another one when the condition is false.

message TxnRequest {
  repeated Compare compare = 1;
  repeated RequestOp success = 2;
  repeated RequestOp failure = 3;
}

That could potentially solve this in a slightly more flexible way?

ecordell avatar Oct 22 '25 23:10 ecordell

As a workaround, have you tried exploring the optional_preconditions field on WriteRelationshipsRequest solve your use case?

@jzelinskie that would just fail the write, not remove the existing relationship, right?

A single write request can look like this:

  • Precondition: old relationship exists
  • Precondition: no other relationships exists
  • Update: delete old relationship
  • Update: add new relationship

This request handles the replacement atomically or fails if any preconditions aren't met. However, this request doesn't handle the case where the old relationship isn't already in SpiceDB, though.


Etcd's version of "preconditions" lets you specify an operation to perform when the condition is true and another one when the condition is false.

message TxnRequest {
  repeated Compare compare = 1;
  repeated RequestOp success = 2;
  repeated RequestOp failure = 3;
}

That could potentially solve this in a slightly more flexible way?

This is another interesting idea that needs to be discussed more in terms of expanding/reworking preconditions. This primitive would be able to handle my above case and the case where the old relationship isn't present in the same request.


A third option, which would require the most design discussion and is what I think the OP is getting at is to define invariants in the schema and have this be part of the validation step. This adds new complexity and latency concerns because it would require request validation to include data from the datastore in addition to the existing system that only requires the request and schema. The benefit to this is a pretty big one, though: a client cannot accidentally break the invariant.

jzelinskie avatar Oct 23 '25 18:10 jzelinskie

because it would require request validation to include data from the datastore in addition to the existing system that only requires the request and schema.

In this case, we'd basically rewrite it into the invariants you suggested above. In fact, we could do "both" by adding invariants on the preconditions first, and then having schema-defined ones that just inject their own preconditions automagically

josephschorr avatar Oct 23 '25 18:10 josephschorr

@jzelinskie the following has an assumption that we know the old relationship:

A single write request can look like this:

  • Precondition: old relationship exists
  • Precondition: no other relationships exists
  • Update: delete old relationship
  • Update: add new relationship

Probably in most cases you can get the expected old relationship from the main DB, but even in those cases we still then need to implement getting the real old relationship from SpiceDB if the precondition fails.

There are other approaches as well, but with their own (performance) downsides.

With invariant on a relation it would be so much better.

My 2 cents on the Etcd's version of "preconditions": such flexibility looks cool and powerful, but the problem is preconditions are already not a simple construct, and that would make them even more complicated. You can definitely expect SpiceDB experts to love it, but thinking about normal developers who bump into SpiceDB code several times per year - that's a pretty cruel option :D

artie-shevchenko avatar Nov 23 '25 21:11 artie-shevchenko