Skip to content

rfc: dynamic routing candidates - #15

Merged
alanshaw merged 3 commits into
mainfrom
ash/rfc/dynamic-affinity-routing
Sep 8, 2026
Merged

alanshaw merged 3 commits into
mainfrom
ash/rfc/dynamic-affinity-routing

Conversation

@alanshaw

@alanshaw alanshaw commented Jul 23, 2026 •

Copy link
Copy Markdown
Member

📖 Preview

Summary

Proposal for an alternative to the presupposed "static" affinity routing where ALL writes to a given space are routed to a specific storage node.

@alanshaw
alanshaw requested a review from a team July 23, 2026 11:47

@bajtos bajtos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

Copilot AI added a commit to fil-forge/libforge that referenced this pull request Aug 1, 2026
Per RFC fil-one/RFC#15 (dynamic affinity routing), adds an optional
`affinity` field to `AddArguments` that accepts a list of storage-node
DIDs. When present, it constrains routing to the specified nodes.
Regenerates cbor_gen.go and json_gen.go accordingly.
@parkan

parkan commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

I am generally in favor of minimizing central state tracking as much as possible but I'm a bit anxious that this could lead to e.g. dropping multiparts on the floor without an easy way for anyone to find them after clients think everything is ACK'd = pushes dealing with that to Ingot/Guppy actual S3 client impls? if that's acceptable 👍 but I feel like it doesn't quite match S3 semantics

(ref #3 Async-ACK under https://github.com/fil-one/RFC/blob/main/rfcs/2026-05-filone-forge-deployment-proposal.md#the-upload-path)

@alanshaw

alanshaw commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

I'm a bit anxious that this could lead to e.g. dropping multiparts on the floor without an easy way for anyone to find them after clients think everything is ACK'd = pushes dealing with that to Ingot/Guppy actual S3 client impls?

I'm not sure I follow...

@alanshaw alanshaw changed the title rfc: dynamic affinity routing rfc: dynamic routing candidates Aug 6, 2026
@parkan

parkan commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

I'm a bit anxious that this could lead to e.g. dropping multiparts on the floor without an easy way for anyone to find them after clients think everything is ACK'd = pushes dealing with that to Ingot/Guppy actual S3 client impls?

I'm not sure I follow...

consider this situation:

  • client does a multi-part upload with dynamic affinity, ACK'd at S3 boundary
  • node crashes/restarts partway through processing the multipart pieces (i.e. before accept on all chunks)
  • if the client (or something) doesn't keep add task state until everything is actually accepted not just ACK'd -> dangling blob(s) that needs reconciliation and doesn't have a receipt that links it to a provider
  • also what happens if the affinity set changes upon retry?

I might be misunderstanding the receipt retention design? (but the way I'm reading it another problem is that all receipts need to be retained for entire blob lifecycle as the only authoritative source of truth, otherwise removes would break -- the receipt chain + indexer design also triggering some IPNI PTSD but that's neither here nor there)

@parkan

parkan commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

this appears to now be superseded by #20 (I will re-raise the above concern there)

@alanshaw

alanshaw commented Aug 18, 2026 •

Copy link
Copy Markdown
Member Author

consider this situation:

  • client does a multi-part upload with dynamic affinity, ACK'd at S3 boundary
  • node crashes/restarts partway through processing the multipart pieces (i.e. before accept on all chunks)
  • if the client (or something) doesn't keep add task state until everything is actually accepted not just ACK'd -> dangling blob(s) that needs reconciliation and doesn't have a receipt that links it to a provider
  • also what happens if the affinity set changes upon retry?

When we accept, we look up the allocation task receipt (referenced by the /http/put task receipt), so we know which node the data was uploaded to. So it doesn't matter if half the multipart blobs go to one node and half to another, when the affinity set is changed - it's only a problem if the first node were to disappear from the network.

@parkan

parkan commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

right, the receipt chain pins placement per-blob, so mixed placement across affinity changes is fine -- so retries are ok

what I'm more worried about is the crash window: lookup at accept works when things are still churning. if ingot ACKs the S3 request and then dies/cycles before concluding all parts, the receipts exist at sprue but nothing progresses them and (per the removal RFC) a parked blob's only handle is its /blob/add task link/cause

so: ingot must durably persist per-part task links before the S3 ACK, so recovery can resume conclude/accept or abort -- otherwise the parts are stranded until allocation expiry, which I don't think is specified yet?

(encryption makes this more complicated)

@alanshaw

Copy link
Copy Markdown
Member Author

so: ingot must durably persist per-part task links before the S3 ACK, so recovery can resume conclude/accept or abort

It does this already: UploadPart persists the task links and the sealed /http/put invocation before returning the part's 200, so everything needed to conclude or abort is durable locally — recovery doesn't depend on finding receipts at sprue.

The crash window you describe can't open, because accepts are performed on the Complete request path: every part is concluded and its /blob/accept receipt awaited before the 200 goes out. If ingot dies mid-Complete the client never got an ACK, so the upload is still unresolved from its side and it retries or aborts. Abandoned sessions are swept and aborted, releasing the parked allocations — no waiting on allocation expiry.

There is one narrow gap: a session a crash strands mid-Complete is currently reaped without aborting its parked blobs, which do then fall back to allocation expiry. I'll open a PR to fix that.

<!-- rfc-preview-link:start -->
[📖
Preview](https://github.com/fil-one/RFC/blob/ash%2Frfc%2Frouting-policies/rfcs/2026-08-routing-policies.md)
<!-- rfc-preview-link:end -->

## Summary

Spaces are configured to use a **routing policy** which specifies the
candidate nodes that can receive a blob. The policy can change over time
but the reference to it does not.

It is an alternative affinity routing proposal that allows blobs
uploaded to spaces to be routed to a particular storage node _without
having to specify said node in every invocation_.
@alanshaw

alanshaw commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

We're going to go with #20 but this RFC is retained (with deferred notice) for reference.

@alanshaw
alanshaw merged commit b7d7341 into main Sep 8, 2026
2 checks passed
@alanshaw
alanshaw deleted the ash/rfc/dynamic-affinity-routing branch September 8, 2026 15:22
@parkan

parkan commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

so: ingot must durably persist per-part task links before the S3 ACK, so recovery can resume conclude/accept or abort

It does this already: UploadPart persists the task links and the sealed /http/put invocation before returning the part's 200, so everything needed to conclude or abort is durable locally — recovery doesn't depend on finding receipts at sprue.

The crash window you describe can't open, because accepts are performed on the Complete request path: every part is concluded and its /blob/accept receipt awaited before the 200 goes out. If ingot dies mid-Complete the client never got an ACK, so the upload is still unresolved from its side and it retries or aborts. Abandoned sessions are swept and aborted, releasing the parked allocations — no waiting on allocation expiry.

There is one narrow gap: a session a crash strands mid-Complete is currently reaped without aborting its parked blobs, which do then fall back to allocation expiry. I'll open a PR to fix that.

sorry I failed to respond here previously: that design is correct, the framing led me to think it worked differently, this is reasonable to keep for future reference 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants