Skip to content

Commit e470e05

Browse files
committed
Stop storing actor identity in ate Redis's value
The key actor:<atespace>:<id> already carries both fields, so storing them in the value duplicates `~27 bytes plus both name lengths` per actor.
1 parent d8f562d commit e470e05

2 files changed

Lines changed: 86 additions & 20 deletions

File tree

cmd/ateapi/internal/store/ateredis/ateredis.go

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,27 @@ func (s *Persistence) DebugClearAll(ctx context.Context) error {
299299
return err
300300
}
301301

302+
// marshalStoredActor serializes dbActor for storage, clearing the identity
303+
// fields as they are stored in the key instead of the value. It mutates
304+
// dbActor: callers pass a clone they own.
305+
func marshalStoredActor(dbActor *ateapipb.Actor) ([]byte, error) {
306+
dbActor.Atespace = ""
307+
dbActor.ActorId = ""
308+
return protojson.Marshal(dbActor)
309+
}
310+
311+
// unmarshalStoredActor parses a stored actor value and backfills the identity
312+
// from the key components.
313+
func unmarshalStoredActor(data []byte, atespace, id string) (*ateapipb.Actor, error) {
314+
actor := &ateapipb.Actor{}
315+
if err := protojson.Unmarshal(data, actor); err != nil {
316+
return nil, err
317+
}
318+
actor.Atespace = atespace
319+
actor.ActorId = id
320+
return actor, nil
321+
}
322+
302323
func (s *Persistence) GetActor(ctx context.Context, atespace, id string) (*ateapipb.Actor, error) {
303324
dbKey := actorDBKey(atespace, id)
304325

@@ -310,15 +331,11 @@ func (s *Persistence) GetActor(ctx context.Context, atespace, id string) (*ateap
310331
return nil, fmt.Errorf("while getting actor key %q: %w", dbKey, err)
311332
}
312333

313-
actor := &ateapipb.Actor{}
314-
if err := protojson.Unmarshal(dbActorBytes, actor); err != nil {
334+
actor, err := unmarshalStoredActor(dbActorBytes, atespace, id)
335+
if err != nil {
315336
return nil, fmt.Errorf("while unmarshaling actor: %w", err)
316337
}
317338

318-
if actor.GetActorId() != id || actor.GetAtespace() != atespace {
319-
return nil, fmt.Errorf("(impossible) mismatch between stored id/atespace and key")
320-
}
321-
322339
return actor, nil
323340
}
324341

@@ -330,9 +347,9 @@ func (s *Persistence) CreateActor(ctx context.Context, actor *ateapipb.Actor) er
330347
dbActor := proto.Clone(actor).(*ateapipb.Actor)
331348
dbActor.Version = 1
332349

333-
dbActorBytes, err := protojson.Marshal(dbActor)
350+
dbActorBytes, err := marshalStoredActor(dbActor)
334351
if err != nil {
335-
return fmt.Errorf("in protojson.Marshal: %w", err)
352+
return fmt.Errorf("while marshaling actor: %w", err)
336353
}
337354

338355
ok, err := s.rdb.SetNX(ctx, dbKey, dbActorBytes, 0).Result()
@@ -529,22 +546,16 @@ func (s *Persistence) UpdateActor(ctx context.Context, actor *ateapipb.Actor, ex
529546
return store.ErrPersistenceRetry
530547
}
531548
dbActor.Version = currentActor.GetVersion() + 1
532-
if currentActor.GetActorId() != dbActor.GetActorId() {
533-
return fmt.Errorf("actor_id is immutable")
534-
}
535-
if currentActor.GetAtespace() != dbActor.GetAtespace() {
536-
return fmt.Errorf("atespace is immutable")
537-
}
538549
if currentActor.GetActorTemplateNamespace() != dbActor.GetActorTemplateNamespace() {
539550
return fmt.Errorf("actor_template_namespace is immutable")
540551
}
541552
if currentActor.GetActorTemplateName() != dbActor.GetActorTemplateName() {
542553
return fmt.Errorf("actor_template_name is immutable")
543554
}
544555

545-
newVal, err := protojson.Marshal(dbActor)
556+
newVal, err := marshalStoredActor(dbActor)
546557
if err != nil {
547-
return fmt.Errorf("in protojson.Marshal: %w", err)
558+
return fmt.Errorf("while marshaling actor: %w", err)
548559
}
549560

550561
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
@@ -755,7 +766,7 @@ func (s *Persistence) fetchActors(ctx context.Context, master *redis.Client, key
755766
}
756767

757768
var actors []*ateapipb.Actor
758-
for _, cmd := range cmds {
769+
for i, cmd := range cmds {
759770
getCmd, ok := cmd.(*redis.StringCmd)
760771
if !ok {
761772
continue
@@ -767,9 +778,13 @@ func (s *Persistence) fetchActors(ctx context.Context, master *redis.Client, key
767778
return nil, fmt.Errorf("while getting actor: %w", getCmd.Err())
768779
}
769780

770-
actor := &ateapipb.Actor{}
771-
if err := protojson.Unmarshal([]byte(getCmd.Val()), actor); err != nil {
772-
return nil, fmt.Errorf("in protojson.Unmarshal: %w", err)
781+
parts := strings.Split(keys[i], ":")
782+
if len(parts) != 3 {
783+
return nil, fmt.Errorf("bad key format %q", keys[i])
784+
}
785+
actor, err := unmarshalStoredActor([]byte(getCmd.Val()), parts[1], parts[2])
786+
if err != nil {
787+
return nil, fmt.Errorf("while unmarshaling actor: %w", err)
773788
}
774789
actors = append(actors, actor)
775790
}

cmd/ateapi/internal/store/ateredis/ateredis_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"errors"
2020
"fmt"
21+
"strings"
2122
"testing"
2223
"time"
2324

@@ -1071,6 +1072,56 @@ func TestDeleteAtespace_EmptyAfterActorsRemoved(t *testing.T) {
10711072
}
10721073
}
10731074

1075+
func TestActorValue_OmitsIdentity(t *testing.T) {
1076+
mr, s, ctx := setupTest(t)
1077+
defer mr.Close()
1078+
1079+
actor := &ateapipb.Actor{
1080+
ActorId: "a1",
1081+
Atespace: "team-a",
1082+
ActorTemplateNamespace: "default",
1083+
ActorTemplateName: "tmpl",
1084+
Status: ateapipb.Actor_STATUS_SUSPENDED,
1085+
}
1086+
if err := s.CreateActor(ctx, actor); err != nil {
1087+
t.Fatalf("CreateActor failed: %v", err)
1088+
}
1089+
1090+
assertNoIdentity := func(operation string) {
1091+
t.Helper()
1092+
raw, err := mr.Get("actor:team-a:a1")
1093+
if err != nil {
1094+
t.Fatalf("raw get after %s failed: %v", operation, err)
1095+
}
1096+
if strings.Contains(raw, `"actorId"`) || strings.Contains(raw, `"atespace"`) {
1097+
t.Errorf("value after %s duplicates key identity: %s", operation, raw)
1098+
}
1099+
}
1100+
assertNoIdentity("create")
1101+
1102+
got, err := s.GetActor(ctx, "team-a", "a1")
1103+
if err != nil {
1104+
t.Fatalf("GetActor failed: %v", err)
1105+
}
1106+
if got.GetAtespace() != "team-a" || got.GetActorId() != "a1" {
1107+
t.Errorf("GetActor identity = (%q, %q), want (team-a, a1)", got.GetAtespace(), got.GetActorId())
1108+
}
1109+
1110+
got.Status = ateapipb.Actor_STATUS_RUNNING
1111+
if err := s.UpdateActor(ctx, got, got.GetVersion()); err != nil {
1112+
t.Fatalf("UpdateActor failed: %v", err)
1113+
}
1114+
assertNoIdentity("update")
1115+
1116+
actors, _, err := s.ListActors(ctx, "team-a", 10, "")
1117+
if err != nil {
1118+
t.Fatalf("ListActors failed: %v", err)
1119+
}
1120+
if len(actors) != 1 || actors[0].GetAtespace() != "team-a" || actors[0].GetActorId() != "a1" {
1121+
t.Errorf("ListActors did not re-populate identity: %v", actors)
1122+
}
1123+
}
1124+
10741125
func TestDeleteAtespace_EmptyWhileOtherAtespaceNonEmpty(t *testing.T) {
10751126
mr, s, ctx := setupTest(t)
10761127
defer mr.Close()

0 commit comments

Comments
 (0)