@@ -8,15 +8,16 @@ import (
88 "time"
99
1010 "github.com/google/uuid"
11+ "github.com/psviderski/uncloud/internal/corrosion"
1112 "github.com/psviderski/uncloud/pkg/api"
1213)
1314
1415// ErrInvalidStoreVersion indicates an invalid actor UUID in a store version vector.
1516var ErrInvalidStoreVersion = errors .New ("invalid store version" )
1617
1718// WaitForVersion waits until the local store has reached each actor's minimum version in minVersion, with no known
18- // missing or pending transactions through those versions. Corrosion may satisfy a version by applying its surviving
19- // changes or by marking it complete because its changes have been superseded.
19+ // missing or pending transactions through those versions from active members . Corrosion may satisfy a version
20+ // by applying its surviving changes or by marking it complete because its changes have been superseded.
2021//
2122// Waiting normally makes the captured data available locally. However, if another write replaces some of that data
2223// before it arrives, Corrosion can complete the older version without transferring the replaced data. If the
@@ -53,28 +54,64 @@ func (s *Store) WaitForVersion(ctx context.Context, minVersion api.StoreVersion)
5354 return nil
5455 }
5556
56- // Check all bookkeeping in one SQLite snapshot. Raw buffered rows can remain after application,
57- // but sequence bookkeeping is removed in the same transaction that applies or clears a version.
58- query := `WITH min_version(actor_id, version) AS (VALUES ` + strings .Join (placeholders , ", " ) + `)
57+ queryPrefix := `WITH min_version(actor_id, version) AS (VALUES ` + strings .Join (placeholders , ", " ) + `)
5958 SELECT NOT EXISTS (
6059 SELECT 1 FROM min_version
6160 LEFT JOIN crsql_db_versions AS current ON current.site_id = min_version.actor_id
62- WHERE COALESCE(current.db_version, 0) < min_version.version
63- OR EXISTS (
64- SELECT 1 FROM __corro_bookkeeping_gaps
65- WHERE actor_id = min_version.actor_id AND start <= min_version.version
66- )
67- OR EXISTS (
68- SELECT 1 FROM __corro_seq_bookkeeping
69- WHERE site_id = min_version.actor_id AND db_version <= min_version.version
70- )
71- )`
61+ WHERE COALESCE(current.db_version, 0) < min_version.version`
62+
63+ versionsReached := false
7264
7365 ticker := time .NewTicker (100 * time .Millisecond )
7466 defer ticker .Stop ()
7567
7668 for {
77- rows , err := s .corro .QueryContext (ctx , query , args ... )
69+ query := queryPrefix
70+ queryArgs := args
71+ // Check for gaps and pending transactions only after the requested versions are reached.
72+ if versionsReached {
73+ states , err := s .corroAdmin .ClusterMembershipStates (true )
74+ if err != nil {
75+ return fmt .Errorf ("get cluster membership for store replication: %w" , err )
76+ }
77+
78+ activePlaceholders := make ([]string , 0 , len (states ))
79+ queryArgs = make ([]any , len (args ), len (args )+ len (states ))
80+ copy (queryArgs , args )
81+ for _ , state := range states {
82+ if state .State != corrosion .MembershipStateAlive && state .State != corrosion .MembershipStateSuspect {
83+ continue
84+ }
85+ actorID , err := uuid .Parse (state .ID )
86+ if err != nil {
87+ return fmt .Errorf ("parse cluster member actor '%s': %w" , state .ID , err )
88+ }
89+ activePlaceholders = append (activePlaceholders , "?" )
90+ queryArgs = append (queryArgs , [16 ]byte (actorID ))
91+ }
92+ if len (activePlaceholders ) > 0 {
93+ // Check active members' gaps and pending sequences in the same query for simplicity.
94+ // Buffered rows can remain in __corro_buffered_changes after application, but sequence bookkeeping
95+ // is removed when a version completes.
96+ query += `
97+ OR (
98+ min_version.actor_id IN (` + strings .Join (activePlaceholders , ", " ) + `)
99+ AND (
100+ EXISTS (
101+ SELECT 1 FROM __corro_bookkeeping_gaps
102+ WHERE actor_id = min_version.actor_id AND start <= min_version.version
103+ )
104+ OR EXISTS (
105+ SELECT 1 FROM __corro_seq_bookkeeping
106+ WHERE site_id = min_version.actor_id AND db_version <= min_version.version
107+ )
108+ )
109+ )`
110+ }
111+ }
112+ query += ")"
113+
114+ rows , err := s .corro .QueryContext (ctx , query , queryArgs ... )
78115 if err != nil {
79116 return fmt .Errorf ("check store replication: %w" , err )
80117 }
@@ -84,6 +121,7 @@ func (s *Store) WaitForVersion(ctx context.Context, minVersion api.StoreVersion)
84121 }
85122 return errors .New ("check store replication: store version query returned no rows" )
86123 }
124+
87125 var reached int
88126 if err = rows .Scan (& reached ); err != nil {
89127 rows .Close ()
@@ -95,7 +133,11 @@ func (s *Store) WaitForVersion(ctx context.Context, minVersion api.StoreVersion)
95133 return fmt .Errorf ("check store replication: %w" , err )
96134 }
97135 if reached == 1 {
98- return ctx .Err ()
136+ if versionsReached {
137+ return nil
138+ }
139+ versionsReached = true
140+ continue
99141 }
100142
101143 select {
0 commit comments