ateapi: add ActorSnapshot lifecycle APIs - #570
Dmitry Berkovich (dberkov) merged 1 commit into
Conversation
a97f6dd to
78cd8e3
Compare
| string worker_pool_name = 12; | ||
|
|
||
| // The latest durable snapshot created for this Actor. | ||
| ObjectRef latest_snapshot = 13; |
There was a problem hiding this comment.
An additional hop to persistence will be required for every resume operation. It introduces latency on the hot path. For 100M+ agents it will increase memory for redis or memory for indexing data in postgress.
Will it be simpler to save ActorSnapshotScope for tagged versions only? It will be cheaper solution and will not be used very often.
There was a problem hiding this comment.
Good point. We can add more data to the Actor so we don’t need to query on the hot path. However this feels like a hack around redis issues vs a real requirement, this could be a single query in Postgres
| } | ||
| return nil, fmt.Errorf("while getting ActorTemplate: %w", err) | ||
| } | ||
| if sourceSnapshot.GetContentScope() == ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL && sourceSnapshot.GetActorTemplateUid() != string(template.GetUID()) { |
There was a problem hiding this comment.
Template ID for data supposed to match too.
I still dont know how exactly Zoe Zhao (@zoez7) will implement the template snapshoting but most likely what needs to be validated is a common layer of snapshot. We need to use data part of the snapshot, If template is full and template version does not match.
For gVisor it still not possible to extract durDir from the snapshot, however it is possible for microVM.
I would validate right now just template ID and create a TODO to fix it, once Zoe added support for multi versioning and Fabricio Voznika (@fvoznika) will add support for gVisor to extract data part from the memory.
|
|
||
| setSpanActorAttributes(ctx, actor) | ||
| return &ateapipb.SuspendActorResponse{Actor: actor}, nil | ||
| return &ateapipb.SuspendActorResponse{Actor: actor, Snapshot: snapshot}, nil |
There was a problem hiding this comment.
The actor already has field "latest_snapshot", what is a reason to return full snapshot as a separate field?
| location := latestActor.InProgressSnapshot | ||
| prefix := strings.TrimSuffix(state.ActorTemplate.Spec.SnapshotsConfig.Location, "/") + "/snapshots/" | ||
| snapshotID := strings.ToLower(strings.NewReplacer(":", "-", "+", "-").Replace(strings.TrimPrefix(location, prefix))) | ||
| lock, err := s.store.AcquireLock(ctx, "lock:actor-snapshot:"+input.Atespace+":"+snapshotID) |
There was a problem hiding this comment.
out of curiosity, why lock is required? The code supposed to add a new instance of actorSnapshot ?
Dmitry Berkovich (dberkov)
left a comment
There was a problem hiding this comment.
I have completed an initial review pass and wanted to share high-level feedback before doing a deep dive into the PR.
My primary concern is generalizing "tagged" snapshots alongside golden and untagged ones. The current design introduces an extra persistence read on the hot path, which offers no advantage in 99% of cases.
Typically, an actor's lifecycle follows a simple sequence: run -> suspend -> run -> suspend. Since tagging is rarely needed, snapshot details can remain internal to the actor rather than being exposed to an external entity.
There are two distinct types of tagging:
Per-actor: Used to roll back to a well-defined state. These snapshots should not be garbage-collected by the retention policy, but they must be deleted when the actor itself is deleted.
Per-atespace: The snapshot lifecycle is tied directly to the atespace and cleaned up when the atespace is deleted. This type is only used during actor creation, and I am not aware of any use case for reverting an actor to this kind of snapshot.
Regarding golden snapshots: the current layer requires a full redesign to support management by CPU type. While the new ActorSnapshot entry could potentially facilitate this, I suggest excluding it for now. We can extend the ActorSnapshot layer to handle golden snapshots once we initiate that broader redesign.
By scoping down the tagging use cases, the entire PR can be significantly simplified.
What do you think?
If the main design is the extra read then I can solve that as per my comment here: #570 (comment)
I'm not sure we know that tagging is rarely needed, we're making that assumption
It's not clear to me that these are the only ones. We specifically discussed having globally scoped snapshots which can be shared outside of the atespace.
I think there are tradeoffs to both approaches. The ways that I can imagine scoping this down would also build us into corners. I think the other viable option would be tracking |
d5cb8b1 to
81c3d37
Compare
| message ActorSnapshotRef { | ||
| oneof reference { | ||
| ObjectRef snapshot = 1; | ||
| string tag = 2; |
There was a problem hiding this comment.
Should we make snapshot tag also atespaced? Otherwise we have no limitation to restore a snapshot that doesn't belong to me.
There was a problem hiding this comment.
Hmm the CreateActor has a scope chheck to prevent that.
But UpdateActor doesn't have, so bascially I can do
- guess a tag
- UpdateActorSnapshot(snapshot: {tag}, scope: GLOBAL)
- CreateActor(source_snapshot: {tag})
to create an actor from others' snapshot
There was a problem hiding this comment.
I'm redoing this system as we speak, so will push soon and then we can chat about it
| return err | ||
| } | ||
| state.Actor = actor | ||
| if actor.GetStatus() == ateapipb.Actor_STATUS_SUSPENDED && actor.GetLatestSnapshot() == nil { |
There was a problem hiding this comment.
When an actor get just created, it will satisfy
actor.GetStatus() == ateapipb.Actor_STATUS_SUSPENDED && actor.GetLatestSnapshot() == nil
So it will error out if suspending it again - I think this makes this step not idempotent anymore.
|
We don't have snapshot retention for now - But this reminds me that for future cascade deletion of atespace(#396), , but we should let it
|
| string in_progress_snapshot = 8; | ||
| string ateom_pod_uid = 9; | ||
| SnapshotInfo latest_snapshot_info = 10; | ||
| reserved 10; |
There was a problem hiding this comment.
No need to worry reserving ids as the change is breaking anyways.
81c3d37 to
6bc5c1a
Compare
|
Updated this PR to reflect the design we converged on:
This deliberately defers tiered Atespaces/higher-level grouping and snapshot GC until concrete requirements justify those concepts. |
6bc5c1a to
317c71d
Compare
| string actor_template_name = 6; | ||
| string actor_template_uid = 7; | ||
| SnapshotContentScope content_scope = 8; | ||
| reserved 9, 10; |
There was a problem hiding this comment.
out of curiosity what it is a reason to have reserved fields for a completely new message
There was a problem hiding this comment.
Removed the reserved numbers and names. This is a new message, so there is no compatibility history to protect.
| return nil, store.ErrFailedPrecondition | ||
| } | ||
|
|
||
| if err := s.deleteMatching(ctx, actorSnapshotTagScanPattern(name)); err != nil { |
There was a problem hiding this comment.
Currently, deletion fails if at least one actor exists. Should this prevent us from following the same pattern for releasing tags?
There was a problem hiding this comment.
Updated to follow the existing non-empty Atespace behavior: deletion now returns FailedPrecondition while any ActorSnapshotTag remains instead of deleting tags.
| if err := validateActorSnapshotRef(req.GetSnapshot(), "snapshot"); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := validateActorSnapshotTag(req.GetTag(), "tag"); err != nil { |
There was a problem hiding this comment.
There is no cross-namespace validation. Do we allow tag snapshots for cross atespace?
There was a problem hiding this comment.
Cross-Atespace tag creation is now rejected with FailedPrecondition. A tag must be owned by the source snapshot's Atespace; PUBLISHED controls cross-Atespace reuse.
| LocalSnapshotInfo local = 3; | ||
| } | ||
| enum SnapshotContentScope { | ||
| // Defaults to FULL for compatibility with existing snapshot configuration. |
There was a problem hiding this comment.
nit:
The proto comment on ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED says "Defaults to ATESPACE", but validateActorSnapshotTagScope (actor_snapshot.go:231) rejects it with InvalidArgument, and the
CreateActor scope switch returns FailedPrecondition for it. Either default needs to be applied or comment needs to be fixed.
There was a problem hiding this comment.
Made ATESPACE the zero/default enum value and removed UNSPECIFIED. Omission now intentionally means Atespace-local.
| return result, nextToken, nil | ||
| } | ||
|
|
||
| func (s *Persistence) TagActorSnapshot(ctx context.Context, atespace, name string, tag *ateapipb.ActorSnapshotTag) (*ateapipb.ActorSnapshotTag, error) { |
There was a problem hiding this comment.
Idempotent re-tag ignores scope — the method returns the existing tag when it points at the same snapshot, even if the requested scope differs. A caller asking for PUBLISHED can silently get back an
ATESPACE tag. Consider treating a scope mismatch as AlreadyExists.
There was a problem hiding this comment.
Fixed. Idempotent re-tagging now requires both the same snapshot and the same scope; a scope mismatch returns AlreadyExists and callers use UpdateActorSnapshotTag to change it.
| return nil, err | ||
| } | ||
| ref := &ateapipb.ActorSnapshotRef{Reference: &ateapipb.ActorSnapshotRef_Tag{Tag: req.GetTag()}} | ||
| lock, _, _, _, err := s.lockActorSnapshot(ctx, ref) |
There was a problem hiding this comment.
nit: Unmapped ErrNotFound — after lockActorSnapshot, a concurrent DeleteAtespace (which only needs the atespace lock, acquired later by the handler) can delete the tag (please take a look on comment in the delete, it might be worth preventing the atespece deletion of tags assigned to it) ; the subsequent store call's ErrNotFound is wrapped
as a generic error (gRPC Unknown) instead of NotFound
There was a problem hiding this comment.
Fixed the error mapping: ErrNotFound from the update is now returned as gRPC NotFound. Atespace deletion also blocks while tags remain, reducing this race while preserving the correct status.
a892685 to
067e731
Compare
|
Updated this PR after #568 merged:
Validation passes: |
067e731 to
0a1bf9e
Compare
ef7b29d
into
agent-substrate:main
Implements #529.
This branch is rebased onto #568's merged storage-layout commit.
ActorSnapshoton every successful suspendActor.latest_snapshot,GetActorSnapshot, and paginated listingatespace/nameATESPACE(the zero/default value) orPUBLISHEDDeleteActorSnapshotkubectl-ateto list/get snapshots, create/update/delete snapshot tags, and create Actors from tagsValidation:
go test ./...make verifyhack/run-e2e-kind.sh ./internal/e2e/suites/demo -v -args --no-color