@@ -556,6 +556,10 @@ type batchResult struct {
556556 skippedConflicts int
557557}
558558
559+ var errPushComparisonPreload = errors .New (
560+ "push comparison preload failed" ,
561+ )
562+
559563// pushBatch pushes a slice of sessions within a single
560564// transaction. On success it appends to pushed and returns
561565// ok=true with session/message counts. On a session-level
@@ -570,6 +574,35 @@ func (s *Sync) pushBatch(
570574 legacyMarkerMachines []string ,
571575 sessionUsageFingerprints map [string ]string ,
572576 pushed * []db.Session ,
577+ ) (batchResult , error ) {
578+ preloadComparisons := len (batch ) > 0 && ! full
579+ result , err := s .pushBatchAttempt (
580+ ctx , batch , full , markerID , legacyMarkerMachines ,
581+ sessionUsageFingerprints , pushed , preloadComparisons ,
582+ )
583+ if err == nil || ! errors .Is (err , errPushComparisonPreload ) {
584+ return result , err
585+ }
586+ log .Printf (
587+ "pgsync: preloading pg comparison fingerprints failed, " +
588+ "retrying batch without preload: %v" ,
589+ err ,
590+ )
591+ return s .pushBatchAttempt (
592+ ctx , batch , full , markerID , legacyMarkerMachines ,
593+ sessionUsageFingerprints , pushed , false ,
594+ )
595+ }
596+
597+ func (s * Sync ) pushBatchAttempt (
598+ ctx context.Context ,
599+ batch []db.Session ,
600+ full bool ,
601+ markerID string ,
602+ legacyMarkerMachines []string ,
603+ sessionUsageFingerprints map [string ]string ,
604+ pushed * []db.Session ,
605+ preloadComparisons bool ,
573606) (batchResult , error ) {
574607 tx , err := s .pg .BeginTx (ctx , nil )
575608 if err != nil {
@@ -586,19 +619,17 @@ func (s *Sync) pushBatch(
586619 sessionIDs = append (sessionIDs , sess .ID )
587620 }
588621 comparisons := (* pushMessageComparison )(nil )
589- if len (sessionIDs ) > 0 && ! full {
622+ if preloadComparisons && len (sessionIDs ) > 0 {
590623 comparisonsBatch , err := readPushSessionMessageComparisons (
591624 ctx , tx , sessionIDs ,
592625 )
593626 if err != nil {
594- log . Printf (
595- "pgsync: preloading pg comparison fingerprints: %v" ,
596- err ,
627+ _ = tx . Rollback ()
628+ return batchResult {}, fmt . Errorf (
629+ "%w: %w" , errPushComparisonPreload , err ,
597630 )
598- comparisons = nil
599- } else {
600- comparisons = comparisonsBatch
601631 }
632+ comparisons = comparisonsBatch
602633 }
603634
604635 for _ , sess := range batch {
@@ -1315,46 +1346,46 @@ func (s *Sync) pushMessages(
13151346 return 0 , nil
13161347 }
13171348
1318- var pgCount int
1319- var pgContentSum , pgContentMax , pgContentMin int64
1320- // Exact string fingerprint for the system-message ordinal set:
1321- // STRING_AGG produces e.g. "0,2,5" — impossible to collide for
1322- // distinct ordinal sets (unlike SUM or SUM+SUM-of-squares).
1323- var pgSystemFP sql. NullString
1324- var pgToolCallCount int
1325- var pgTCContentSum int64
1326- if err := tx . QueryRowContext ( ctx ,
1327- `SELECT COUNT(*),
1328- COALESCE(SUM(content_length), 0),
1329- COALESCE(MAX(content_length), 0 ),
1330- COALESCE(MIN(content_length), 0),
1331- STRING_AGG(ordinal::text, ',' ORDER BY ordinal )
1332- FILTER (WHERE is_system)
1333- FROM messages
1334- WHERE session_id = $1` ,
1335- sessionID ,
1336- ). Scan (
1337- & pgCount , & pgContentSum ,
1338- & pgContentMax , & pgContentMin ,
1339- & pgSystemFP ,
1340- ); err != nil {
1341- return 0 , fmt . Errorf (
1342- "counting pg messages: %w" , err ,
1343- )
1344- }
1345- if err := tx . QueryRowContext ( ctx ,
1346- `SELECT COUNT(*),
1347- COALESCE(SUM(result_content_length), 0)
1348- FROM tool_calls
1349- WHERE session_id = $1` ,
1350- sessionID ,
1351- ). Scan ( & pgToolCallCount , & pgTCContentSum ); err != nil {
1352- return 0 , fmt . Errorf (
1353- "counting pg tool_calls: %w" , err ,
1354- )
1349+ pgAgg , pgToolAgg , hasPreloadedComparisons := comparisonAggregates (
1350+ sessionID , comparisons ,
1351+ )
1352+ if ! hasPreloadedComparisons {
1353+ if err := tx . QueryRowContext ( ctx ,
1354+ `SELECT COUNT(*),
1355+ COALESCE(SUM(content_length), 0),
1356+ COALESCE(MAX(content_length), 0),
1357+ COALESCE(MIN(content_length), 0) ,
1358+ COALESCE(
1359+ STRING_AGG(ordinal::text, ',' ORDER BY ordinal)
1360+ FILTER (WHERE is_system ),
1361+ ''
1362+ )
1363+ FROM messages
1364+ WHERE session_id = $1` ,
1365+ sessionID ,
1366+ ). Scan (
1367+ & pgAgg . Count , & pgAgg . Sum ,
1368+ & pgAgg . Max , & pgAgg . Min ,
1369+ & pgAgg . SysFP ,
1370+ ); err != nil {
1371+ return 0 , fmt . Errorf (
1372+ "counting pg messages: %w" , err ,
1373+ )
1374+ }
1375+ if err := tx . QueryRowContext ( ctx ,
1376+ `SELECT COUNT(*) ,
1377+ COALESCE(SUM(result_content_length), 0)
1378+ FROM tool_calls
1379+ WHERE session_id = $1` ,
1380+ sessionID ,
1381+ ). Scan ( & pgToolAgg . Count , & pgToolAgg . Sum ); err != nil {
1382+ return 0 , fmt . Errorf (
1383+ "counting pg tool_calls: %w" , err ,
1384+ )
1385+ }
13551386 }
13561387
1357- if ! full && pgCount == localCount && pgCount > 0 {
1388+ if ! full && pgAgg . Count == localCount && pgAgg . Count > 0 {
13581389 localFP := pushLocalMessageFingerprint {}
13591390
13601391 localFP .Sum , localFP .Max , localFP .Min , err = s .local .MessageContentFingerprint (
@@ -1490,15 +1521,15 @@ func (s *Sync) pushMessages(
14901521 )
14911522 }
14921523
1493- if localFP .Sum == pgContentSum &&
1494- localFP .Max == pgContentMax &&
1495- localFP .Min == pgContentMin &&
1524+ if localFP .Sum == pgAgg . Sum &&
1525+ localFP .Max == pgAgg . Max &&
1526+ localFP .Min == pgAgg . Min &&
14961527 localFP .ContentHashFP == pgContentHashFP &&
14971528 localFP .RoleTimeFP == pgRoleTimeFP &&
14981529 localFP .FlagsFP == pgFlagsFP &&
1499- localFP .SystemFP == pgSystemFP . String &&
1500- localFP .ToolCallCount == pgToolCallCount &&
1501- localFP .ToolCallSum == pgTCContentSum &&
1530+ localFP .SystemFP == pgAgg . SysFP &&
1531+ localFP .ToolCallCount == pgToolAgg . Count &&
1532+ localFP .ToolCallSum == pgToolAgg . Sum &&
15021533 localFP .ToolCallFP == pgTCFP &&
15031534 localFP .TokenFP == pgTokenFP &&
15041535 localFP .UsageEventFP == pgUsageFP {
0 commit comments