Skip to content

Commit 7b41ff1

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 069ba08 commit 7b41ff1

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
@@ -307,6 +307,27 @@ func (s *Persistence) DebugClearAll(ctx context.Context) error {
307307
return err
308308
}
309309

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

@@ -318,15 +339,11 @@ func (s *Persistence) GetActor(ctx context.Context, atespace, id string) (*ateap
318339
return nil, fmt.Errorf("while getting actor key %q: %w", dbKey, err)
319340
}
320341

321-
actor := &ateapipb.Actor{}
322-
if err := protojson.Unmarshal(dbActorBytes, actor); err != nil {
342+
actor, err := unmarshalStoredActor(dbActorBytes, atespace, id)
343+
if err != nil {
323344
return nil, fmt.Errorf("while unmarshaling actor: %w", err)
324345
}
325346

326-
if actor.GetActorId() != id || actor.GetAtespace() != atespace {
327-
return nil, fmt.Errorf("(impossible) mismatch between stored id/atespace and key")
328-
}
329-
330347
return actor, nil
331348
}
332349

@@ -338,9 +355,9 @@ func (s *Persistence) CreateActor(ctx context.Context, actor *ateapipb.Actor) er
338355
dbActor := proto.Clone(actor).(*ateapipb.Actor)
339356
dbActor.Version = 1
340357

341-
dbActorBytes, err := protojson.Marshal(dbActor)
358+
dbActorBytes, err := marshalStoredActor(dbActor)
342359
if err != nil {
343-
return fmt.Errorf("in protojson.Marshal: %w", err)
360+
return fmt.Errorf("while marshaling actor: %w", err)
344361
}
345362

346363
ok, err := s.rdb.SetNX(ctx, dbKey, dbActorBytes, 0).Result()
@@ -538,22 +555,16 @@ func (s *Persistence) UpdateActor(ctx context.Context, actor *ateapipb.Actor, ex
538555
return store.ErrPersistenceRetry
539556
}
540557
dbActor.Version = currentActor.GetVersion() + 1
541-
if currentActor.GetActorId() != dbActor.GetActorId() {
542-
return fmt.Errorf("actor_id is immutable")
543-
}
544-
if currentActor.GetAtespace() != dbActor.GetAtespace() {
545-
return fmt.Errorf("atespace is immutable")
546-
}
547558
if currentActor.GetActorTemplateNamespace() != dbActor.GetActorTemplateNamespace() {
548559
return fmt.Errorf("actor_template_namespace is immutable")
549560
}
550561
if currentActor.GetActorTemplateName() != dbActor.GetActorTemplateName() {
551562
return fmt.Errorf("actor_template_name is immutable")
552563
}
553564

554-
newVal, err := protojson.Marshal(dbActor)
565+
newVal, err := marshalStoredActor(dbActor)
555566
if err != nil {
556-
return fmt.Errorf("in protojson.Marshal: %w", err)
567+
return fmt.Errorf("while marshaling actor: %w", err)
557568
}
558569

559570
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
@@ -768,7 +779,7 @@ func (s *Persistence) fetchActors(ctx context.Context, master *redis.Client, key
768779
}
769780

770781
var actors []*ateapipb.Actor
771-
for _, cmd := range cmds {
782+
for i, cmd := range cmds {
772783
getCmd, ok := cmd.(*redis.StringCmd)
773784
if !ok {
774785
continue
@@ -780,9 +791,13 @@ func (s *Persistence) fetchActors(ctx context.Context, master *redis.Client, key
780791
return nil, fmt.Errorf("while getting actor: %w", getCmd.Err())
781792
}
782793

783-
actor := &ateapipb.Actor{}
784-
if err := protojson.Unmarshal([]byte(getCmd.Val()), actor); err != nil {
785-
return nil, fmt.Errorf("in protojson.Unmarshal: %w", err)
794+
parts := strings.Split(keys[i], ":")
795+
if len(parts) != 3 {
796+
return nil, fmt.Errorf("bad key format %q", keys[i])
797+
}
798+
actor, err := unmarshalStoredActor([]byte(getCmd.Val()), parts[1], parts[2])
799+
if err != nil {
800+
return nil, fmt.Errorf("while unmarshaling actor: %w", err)
786801
}
787802
actors = append(actors, actor)
788803
}

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"errors"
2020
"fmt"
2121
"sort"
22+
"strings"
2223
"sync"
2324
"testing"
2425
"time"
@@ -1070,6 +1071,56 @@ func TestDeleteAtespace_EmptyAfterActorsRemoved(t *testing.T) {
10701071
}
10711072
}
10721073

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

0 commit comments

Comments
 (0)