@@ -11,14 +11,20 @@ import (
11
11
"time"
12
12
13
13
"github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/cdctest"
14
+ "github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/changefeedbase"
14
15
"github.com/cockroachdb/cockroach/pkg/jobs"
15
16
"github.com/cockroachdb/cockroach/pkg/jobs/jobfrontier"
16
17
"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
18
+ "github.com/cockroachdb/cockroach/pkg/roachpb"
19
+ "github.com/cockroachdb/cockroach/pkg/sql/catalog/desctestutils"
17
20
"github.com/cockroachdb/cockroach/pkg/sql/isql"
21
+ "github.com/cockroachdb/cockroach/pkg/sql/rowenc"
18
22
"github.com/cockroachdb/cockroach/pkg/testutils"
19
23
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
24
+ "github.com/cockroachdb/cockroach/pkg/util/encoding"
20
25
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
21
26
"github.com/cockroachdb/cockroach/pkg/util/log"
27
+ "github.com/cockroachdb/cockroach/pkg/util/span"
22
28
"github.com/cockroachdb/errors"
23
29
"github.com/stretchr/testify/require"
24
30
)
@@ -54,9 +60,9 @@ func TestChangefeedFrontierPersistence(t *testing.T) {
54
60
55
61
// Make sure frontier gets persisted to job_info table.
56
62
jobID := foo .(cdctest.EnterpriseTestFeed ).JobID ()
63
+ var allSpans []jobspb.ResolvedSpan
57
64
testutils .SucceedsSoon (t , func () error {
58
65
var found bool
59
- var allSpans []jobspb.ResolvedSpan
60
66
if err := s .Server .InternalDB ().(isql.DB ).Txn (ctx , func (ctx context.Context , txn isql.Txn ) error {
61
67
var err error
62
68
allSpans , found , err = jobfrontier .GetAllResolvedSpans (ctx , txn , jobID )
@@ -74,6 +80,17 @@ func TestChangefeedFrontierPersistence(t *testing.T) {
74
80
return nil
75
81
})
76
82
83
+ // Make sure the persisted spans cover the entire table.
84
+ fooTableSpan := desctestutils .
85
+ TestingGetPublicTableDescriptor (s .Server .DB (), s .Codec , "d" , "foo" ).
86
+ PrimaryIndexSpan (s .Codec )
87
+ var spanGroup roachpb.SpanGroup
88
+ spanGroup .Add (fooTableSpan )
89
+ for _ , rs := range allSpans {
90
+ spanGroup .Sub (rs .Span )
91
+ }
92
+ require .Zero (t , spanGroup .Len ())
93
+
77
94
// Verify metric count and average latency have sensible values.
78
95
testutils .SucceedsSoon (t , func () error {
79
96
metricSnapshot := metric .CumulativeSnapshot ()
@@ -91,3 +108,74 @@ func TestChangefeedFrontierPersistence(t *testing.T) {
91
108
92
109
cdcTest (t , testFn , feedTestEnterpriseSinks )
93
110
}
111
+
112
+ // TestChangefeedFrontierRestore verifies that changefeeds will correctly
113
+ // restore progress from persisted span frontiers.
114
+ func TestChangefeedFrontierRestore (t * testing.T ) {
115
+ defer leaktest .AfterTest (t )()
116
+ defer log .Scope (t ).Close (t )
117
+
118
+ testFn := func (t * testing.T , s TestServer , f cdctest.TestFeedFactory ) {
119
+ sqlDB := sqlutils .MakeSQLRunner (s .DB )
120
+ ctx := context .Background ()
121
+
122
+ // Disable span-level checkpointing.
123
+ changefeedbase .SpanCheckpointInterval .Override (ctx , & s .Server .ClusterSettings ().SV , 0 )
124
+
125
+ // Create a table and a changefeed on it.
126
+ sqlDB .Exec (t , "CREATE TABLE foo (a INT PRIMARY KEY)" )
127
+ foo := feed (t , f , "CREATE CHANGEFEED FOR foo WITH initial_scan='no'" )
128
+ defer closeFeed (t , foo )
129
+ jobFeed := foo .(cdctest.EnterpriseTestFeed )
130
+
131
+ // Pause the changefeed.
132
+ require .NoError (t , jobFeed .Pause ())
133
+
134
+ // Insert a few rows into the table and save the insert time.
135
+ var tsStr string
136
+ sqlDB .QueryRow (t , `INSERT INTO foo VALUES (1), (2), (3), (4), (5), (6)
137
+ RETURNING cluster_logical_timestamp()` ).Scan (& tsStr )
138
+ ts := parseTimeToHLC (t , tsStr )
139
+
140
+ // Make function to create spans for single rows in the table.
141
+ codec := s .Server .Codec ()
142
+ fooDesc := desctestutils .TestingGetPublicTableDescriptor (s .Server .DB (), codec , "d" , "foo" )
143
+ rowSpan := func (key int64 ) roachpb.Span {
144
+ keyPrefix := func () []byte {
145
+ return rowenc .MakeIndexKeyPrefix (codec , fooDesc .GetID (), fooDesc .GetPrimaryIndexID ())
146
+ }
147
+ return roachpb.Span {
148
+ Key : encoding .EncodeVarintAscending (keyPrefix (), key ),
149
+ EndKey : encoding .EncodeVarintAscending (keyPrefix (), key + 1 ),
150
+ }
151
+ }
152
+
153
+ // Manually persist a span frontier that manually marks some of the
154
+ // inserted rows as resolved already.
155
+ hw , err := jobFeed .HighWaterMark ()
156
+ require .NoError (t , err )
157
+ frontier , err := span .MakeFrontierAt (hw , fooDesc .PrimaryIndexSpan (codec ))
158
+ require .NoError (t , err )
159
+ for _ , id := range []int64 {2 , 5 } {
160
+ _ , err := frontier .Forward (rowSpan (id ), ts )
161
+ require .NoError (t , err )
162
+ }
163
+ err = s .Server .InternalDB ().(isql.DB ).Txn (ctx , func (ctx context.Context , txn isql.Txn ) error {
164
+ return jobfrontier .Store (ctx , txn , jobFeed .JobID (), "test frontier" , frontier )
165
+ })
166
+ require .NoError (t , err )
167
+
168
+ // Resume the changefeed.
169
+ require .NoError (t , jobFeed .Resume ())
170
+
171
+ // We should receive rows 1, 3, 4, 6 (rows 2 and 5 were marked as resolved).
172
+ assertPayloads (t , foo , []string {
173
+ `foo: [1]->{"after": {"a": 1}}` ,
174
+ `foo: [3]->{"after": {"a": 3}}` ,
175
+ `foo: [4]->{"after": {"a": 4}}` ,
176
+ `foo: [6]->{"after": {"a": 6}}` ,
177
+ })
178
+ }
179
+
180
+ cdcTest (t , testFn , feedTestEnterpriseSinks )
181
+ }
0 commit comments