Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

End to end support for experimental first-class relationship expiration feature #2152

Open
wants to merge 25 commits into
base: main
Choose a base branch
from

Conversation

josephschorr
Copy link
Member

Fixes #1262 by superseding it

When the flag is enabled, relationship expiration can be used in schema via the use expiration directive and the expiration keyword on relation's:

use expiration

definition user {}

definition resource {
  relation viewer: user with expiration
}

When a relationship reaches its expiration, it is immediately filtered from all relationship queries, making it appear as-if it was immediately deleted.

Subsequently (typically 24 hours later), the relationship will be actually removed from the underlying datastore to free space.

Warning

Currently, relationships are filtered using the wall clock found within the datastore, which has the following consequences:

  1. For multi-master datastores such as CRDB and Spanner, there may be some slight skew between nodes on clocks, which could lead to different results if queries are called very close to the expiration time for relationship(s) to be returned
  2. Caches are not updated if a relationship expires within the caching window, e.g. if a permission answer is computed at time N, a relationship used as part of that computation expires at time N+1 and the cache entry is valid until N+10, the caller will see the cached answer even if the relationship has expired - this is in line with caches reflecting the state of the world when they were computed, but is a change from current behavior when using caveats for expiration
  3. at_exact_snapshot calls, as they are made to the datastore, will reflect the expiration as of "now", which means they can return different results depending on when they are invoked. This is a change from current behavior when using caveats for expiration. Note that this may be changed in the future to instead use the timestamp of the revision itself; this change is currently under discussion

Note

This PR does not yet implement returning of the expires_at in CheckPermissionResponse and CheckPermission debug information. That will be done in a followup PR.

@josephschorr josephschorr requested review from vroldanbet and a team as code owners December 3, 2024 19:13
@github-actions github-actions bot added area/CLI Affects the command line area/schema Affects the Schema Language area/api v1 Affects the v1 API area/datastore Affects the storage system area/tooling Affects the dev or user toolchain (e.g. tests, ci, build tools) labels Dec 3, 2024
tstirrat15
tstirrat15 previously approved these changes Dec 3, 2024
Copy link
Contributor

@tstirrat15 tstirrat15 left a comment

Choose a reason for hiding this comment

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

See comments, otherwise LGTM

// even if an error happened, garbage would have been collected. This makes sure these are reflected even if the
// worker eventually fails or times out.
gcRelationshipsCounter.Add(float64(collected.Relationships))
gcTransactionsCounter.Add(float64(collected.Transactions))
gcNamespacesCounter.Add(float64(collected.Namespaces))
gcExpiredRelationshipsCounter.Add(float64(expiredRelationshipsCount))
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this being a float just because that's how Prometheus implements counters?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes

Columns(colExpiresAt, colMetadata).
Values(expiresAt, config.Metadata.AsMap())
// Write metadata onto the transaction.
metadata := config.Metadata.AsMap()
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the creation here also attach it to the transaction? Otherwise it looks like it's being constructed and modified but not attached.

Copy link
Member Author

Choose a reason for hiding this comment

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

Line 362 executes the insert statement for it; I fixed the reference

@@ -549,7 +590,9 @@ func (cds *crdbDatastore) processChanges(ctx context.Context, changes pgx.Rows,
return
}

if err := tracked.SetRevisionMetadata(ctx, rev, details.After.Metadata); err != nil {
adjustedMetadata := details.After.Metadata
Copy link
Contributor

Choose a reason for hiding this comment

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

For my own edification - when you set something equal to a struct rather than a pointer to that struct, is it functionally creating a copy of it, which is what lets us safely delete the key?

Copy link
Member Author

Choose a reason for hiding this comment

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

In this case, its safe because we're not reusing it anywhere else

@@ -115,6 +118,7 @@ func NewPermissionsServer(
MaxLookupResourcesLimit: defaultIfZero(config.MaxLookupResourcesLimit, 1_000),
MaxBulkExportRelationshipsLimit: defaultIfZero(config.MaxBulkExportRelationshipsLimit, 100_000),
DispatchChunkSize: defaultIfZero(config.DispatchChunkSize, 100),
ExpiringRelationshipsEnabled: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Meaning it defaults to enabled? Or is this something that should look like the above?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, for testing. We then explicitly disable it when starting SpiceDB, as its default is false

@@ -480,6 +481,30 @@ func TestDeleteRelationshipViaWriteNoop(t *testing.T) {
require.NoError(err)
}

func TestWriteExpiringRelationshinps(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
func TestWriteExpiringRelationshinps(t *testing.T) {
func TestWriteExpiringRelationships(t *testing.T) {

@@ -84,6 +84,7 @@ func (c *Config) Complete() (RunnableTestServer, error) {
MaxLookupResourcesLimit: c.MaxLookupResourcesLimit,
MaxBulkExportRelationshipsLimit: c.MaxBulkExportRelationshipsLimit,
DispatchChunkSize: defaultMaxChunkSize,
ExpiringRelationshipsEnabled: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here - should this reference the config?

Copy link
Member Author

Choose a reason for hiding this comment

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

No; I think its fine to enable by default in the test server

MaxPreconditionsCount: 50,
MaximumAPIDepth: 50,
MaxCaveatContextSize: 0,
ExpiringRelationshipsEnabled: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Here as well

tstirrat15
tstirrat15 previously approved these changes Dec 3, 2024
Copy link
Contributor

@tstirrat15 tstirrat15 left a comment

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/api v1 Affects the v1 API area/CLI Affects the command line area/datastore Affects the storage system area/schema Affects the Schema Language area/tooling Affects the dev or user toolchain (e.g. tests, ci, build tools)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GC of relationships expired via caveats
2 participants