@@ -52,17 +52,49 @@ func (s *Service) SyncNamespace(ctx context.Context) error {
5252 return trace .New ("cross-ref store is not configured" )
5353 }
5454 ns := requestctx .FromContext (ctx )
55- if err := s .rebuildOutbound (ctx , ns ); err != nil {
55+ memo := map [targetKey ]resolution {}
56+ if err := s .rebuildOutbound (ctx , ns , memo ); err != nil {
5657 return trace .Wrap (err , "rebuild outbound cross refs" )
5758 }
58- if err := s .reresolveInbound (ctx , ns ); err != nil {
59+ if err := s .reresolveInbound (ctx , ns , memo ); err != nil {
5960 return trace .Wrap (err , "re-resolve inbound cross refs" )
6061 }
6162 return nil
6263}
6364
65+ // targetKey identifies one distinct symbolic resolution target within a sync pass.
66+ // @intent resolve every distinct (namespace, path, symbol) target once per sync instead of once per referencing row.
67+ type targetKey struct {
68+ namespace string
69+ path string
70+ symbol string
71+ }
72+
73+ // resolution caches one matcher outcome for reuse across rows that share a target.
74+ type resolution struct {
75+ id * uint
76+ status graph.CrossRefStatus
77+ }
78+
79+ // resolveOnce memoizes resolve results per distinct target for the duration of one sync pass.
80+ // @intent collapse the per-row resolve round-trips (N+1) down to one query per distinct target.
81+ // @domainRule the memo stays valid across the outbound and inbound phases because a sync pass
82+ // only rewrites cross_refs rows, never the node tables the matcher reads.
83+ func (s * Service ) resolveOnce (ctx context.Context , ref reference.Ref , memo map [targetKey ]resolution ) (* uint , graph.CrossRefStatus , error ) {
84+ key := targetKey {namespace : ref .Namespace , path : ref .Path , symbol : ref .Symbol }
85+ if cached , ok := memo [key ]; ok {
86+ return cached .id , cached .status , nil
87+ }
88+ id , status , err := s .resolve (ctx , ref )
89+ if err != nil {
90+ return nil , status , err
91+ }
92+ memo [key ] = resolution {id : id , status : status }
93+ return id , status , nil
94+ }
95+
6496// @intent replace the namespace's outbound rows with rows derived from its current annotations.
65- func (s * Service ) rebuildOutbound (ctx context.Context , ns string ) error {
97+ func (s * Service ) rebuildOutbound (ctx context.Context , ns string , memo map [ targetKey ] resolution ) error {
6698 tags , err := s .Store .ListAnnotationCCGRefs (ctx , ns )
6799 if err != nil {
68100 return err
@@ -79,7 +111,7 @@ func (s *Service) rebuildOutbound(ctx context.Context, ns string) error {
79111 s .logger ().Debug ("skipping malformed ccg ref" , "node_id" , tag .NodeID , "value" , tag .Value , "error" , err )
80112 continue
81113 }
82- resolvedID , status , err := s .resolve (ctx , * ref )
114+ resolvedID , status , err := s .resolveOnce (ctx , * ref , memo )
83115 if err != nil {
84116 return err
85117 }
@@ -99,14 +131,14 @@ func (s *Service) rebuildOutbound(ctx context.Context, ns string) error {
99131}
100132
101133// @intent update inbound rows whose resolution changed after this namespace's nodes were rebuilt.
102- func (s * Service ) reresolveInbound (ctx context.Context , ns string ) error {
134+ func (s * Service ) reresolveInbound (ctx context.Context , ns string , memo map [ targetKey ] resolution ) error {
103135 inbound , err := s .Store .ListInboundCrossRefs (ctx , ns )
104136 if err != nil {
105137 return err
106138 }
107139 for _ , row := range inbound {
108140 ref := reference.Ref {Raw : row .Raw , Namespace : row .ToNamespace , Path : row .ToPath , Symbol : row .ToSymbol }
109- resolvedID , status , err := s .resolve (ctx , ref )
141+ resolvedID , status , err := s .resolveOnce (ctx , ref , memo )
110142 if err != nil {
111143 return err
112144 }
0 commit comments