@@ -24,6 +24,7 @@ import (
24
24
"github.com/cockroachdb/cockroach/pkg/jobs"
25
25
"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
26
26
"github.com/cockroachdb/cockroach/pkg/keys"
27
+ "github.com/cockroachdb/cockroach/pkg/kv"
27
28
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts"
28
29
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts/ptpb"
29
30
"github.com/cockroachdb/cockroach/pkg/roachpb"
@@ -44,6 +45,7 @@ import (
44
45
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
45
46
"github.com/cockroachdb/cockroach/pkg/testutils/storageutils"
46
47
"github.com/cockroachdb/cockroach/pkg/util/duration"
48
+ "github.com/cockroachdb/cockroach/pkg/util/encoding"
47
49
"github.com/cockroachdb/cockroach/pkg/util/hlc"
48
50
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
49
51
"github.com/cockroachdb/cockroach/pkg/util/log"
@@ -1674,7 +1676,7 @@ func TestComputeStatsDiff(t *testing.T) {
1674
1676
var tableID int
1675
1677
c .SrcTenantSQL .QueryRow (t , "SELECT id FROM system.namespace WHERE name = 'x'" ).Scan (& tableID )
1676
1678
tenantID := c .Args .DestTenantID
1677
- liveCountOverPKQuery := fmt . Sprintf ( `SELECT stats->'approximate_total_stats'->'live_count' FROM crdb_internal.tenant_span_stats(ARRAY(SELECT(crdb_internal.index_span(%d,%d,1)[1],crdb_internal.index_span(%d,%d,1)[2])))` , tenantID . ToUint64 (), tableID , tenantID . ToUint64 () , tableID )
1679
+ liveCountOverPKQuery := makeLiveCountOverPKQuery ( tenantID , tableID )
1678
1680
1679
1681
var liveCount int64
1680
1682
c .DestSysSQL .QueryRow (t , liveCountOverPKQuery ).Scan (& liveCount )
@@ -1690,11 +1692,7 @@ func TestComputeStatsDiff(t *testing.T) {
1690
1692
// Split out the index span we will gather stats so that
1691
1693
// crdb_internal.tenant_span_stats() uses the stats that hang off of the
1692
1694
// descriptor, instead of computing the stats manually.
1693
- codec := keys .MakeSQLCodec (tenantID )
1694
- pkStartKey := codec .IndexPrefix (uint32 (tableID ), 1 )
1695
- pkEndKey := pkStartKey .PrefixEnd ()
1696
- require .NoError (t , c .DestSysServer .DB ().AdminSplit (ctx , pkStartKey , hlc .MaxTimestamp ))
1697
- require .NoError (t , c .DestSysServer .DB ().AdminSplit (ctx , pkEndKey , hlc .MaxTimestamp ))
1695
+ splitPrimaryKeyIndexSpan (ctx , t , c .DestSysServer .DB (), tenantID , tableID )
1698
1696
1699
1697
c .DestSysSQL .QueryRow (t , liveCountOverPKQuery ).Scan (& liveCount )
1700
1698
require .Equal (t , int64 (2 ), liveCount )
@@ -1710,3 +1708,95 @@ func TestComputeStatsDiff(t *testing.T) {
1710
1708
c .DestSysSQL .QueryRow (t , liveCountOverPKQuery ).Scan (& liveCount )
1711
1709
require .Equal (t , int64 (2 ), liveCount )
1712
1710
}
1711
+
1712
+ // TestDelRangeStatsUpdates is an end to end test that ensures that mvcc stats are
1713
+ // computed correctly after replicating range key deletes.
1714
+ func TestDelRangeStatsUpdates (t * testing.T ) {
1715
+ defer leaktest .AfterTest (t )()
1716
+ defer log .Scope (t ).Close (t )
1717
+
1718
+ ctx := context .Background ()
1719
+ args := replicationtestutils .DefaultTenantStreamingClustersArgs
1720
+ c , cleanup := replicationtestutils .CreateTenantStreamingClusters (ctx , t , args )
1721
+ defer cleanup ()
1722
+
1723
+ c .DestSysSQL .Exec (t , "SET CLUSTER SETTING server.debug.default_vmodule = 'stream_ingestion_processor=2'; " )
1724
+
1725
+ c .SrcTenantSQL .Exec (t , "CREATE DATABASE test" )
1726
+ c .SrcTenantSQL .Exec (t , "CREATE TABLE test.x (id INT PRIMARY KEY, n INT)" )
1727
+
1728
+ var tableID int
1729
+ c .SrcTenantSQL .QueryRow (t , "SELECT id FROM system.namespace WHERE name = 'x'" ).Scan (& tableID )
1730
+ tenantID := c .Args .DestTenantID
1731
+ liveCountOverPKQuery := makeLiveCountOverPKQuery (tenantID , tableID )
1732
+
1733
+ var liveCount int64
1734
+ c .DestSysSQL .QueryRow (t , liveCountOverPKQuery ).Scan (& liveCount )
1735
+ require .Equal (t , int64 (0 ), liveCount )
1736
+
1737
+ producerJobID , ingestionJobID := c .StartStreamReplication (ctx )
1738
+
1739
+ jobutils .WaitForJobToRun (c .T , c .SrcSysSQL , jobspb .JobID (producerJobID ))
1740
+ jobutils .WaitForJobToRun (c .T , c .DestSysSQL , jobspb .JobID (ingestionJobID ))
1741
+
1742
+ c .WaitUntilStartTimeReached (jobspb .JobID (ingestionJobID ))
1743
+
1744
+ // Split out the index span we will gather stats so that
1745
+ // crdb_internal.tenant_span_stats() uses the stats that hang off of the
1746
+ // descriptor, instead of computing the stats manually.
1747
+ splitPrimaryKeyIndexSpan (ctx , t , c .DestSysServer .DB (), tenantID , tableID )
1748
+
1749
+ for i := 1 ; i <= 50 ; i ++ {
1750
+ c .SrcTenantSQL .Exec (t , fmt .Sprintf ("INSERT INTO test.x VALUES (%d, %d)" , i , i ))
1751
+ }
1752
+
1753
+ srcTime := c .SrcCluster .Server (0 ).Clock ().Now ()
1754
+ c .WaitUntilReplicatedTime (srcTime , jobspb .JobID (ingestionJobID ))
1755
+
1756
+ c .DestSysSQL .QueryRow (t , liveCountOverPKQuery ).Scan (& liveCount )
1757
+ require .Equal (t , int64 (50 ), liveCount )
1758
+
1759
+ // Add 5 split points to the replicated table on destination cluster, to test
1760
+ // that the recompute stats logic works on multiple ranges.
1761
+ destCodec := keys .MakeSQLCodec (c .Args .DestTenantID )
1762
+ for i := 1 ; i <= 5 ; i ++ {
1763
+ splitValue := i * 5
1764
+ splitKey := destCodec .IndexPrefix (uint32 (tableID ), 1 )
1765
+ splitKey = encoding .EncodeVarintAscending (splitKey , int64 (splitValue ))
1766
+ require .NoError (t , c .DestSysServer .DB ().AdminSplit (ctx , splitKey , hlc .MaxTimestamp ))
1767
+ }
1768
+
1769
+ // Write a range key over table test.x's key span instead of dropping the
1770
+ // table to guarantee that the srcTime below is after the range key write.
1771
+ srcCodec := keys .MakeSQLCodec (c .Args .SrcTenantID )
1772
+ tableSpan := roachpb.Span {
1773
+ Key : srcCodec .TablePrefix (uint32 (tableID )),
1774
+ EndKey : srcCodec .TablePrefix (uint32 (tableID )).PrefixEnd (),
1775
+ }
1776
+ require .NoError (t , c .SrcSysServer .DB ().DelRangeUsingTombstone (ctx , tableSpan .Key , tableSpan .EndKey ))
1777
+
1778
+ srcTime = c .SrcCluster .Server (0 ).Clock ().Now ()
1779
+ c .WaitUntilReplicatedTime (srcTime , jobspb .JobID (ingestionJobID ))
1780
+ c .RequireFingerprintMatchAtTimestamp (srcTime .AsOfSystemTime ())
1781
+
1782
+ c .DestSysSQL .QueryRow (t , liveCountOverPKQuery ).Scan (& liveCount )
1783
+ require .Equal (t , int64 (0 ), liveCount )
1784
+ }
1785
+
1786
+ // makeLiveCountOverPKQuery constructs a query to get live count stats for a table's primary key.
1787
+ func makeLiveCountOverPKQuery (tenantID roachpb.TenantID , tableID int ) string {
1788
+ return fmt .Sprintf (`SELECT stats->'approximate_total_stats'->'live_count' FROM crdb_internal.tenant_span_stats(ARRAY(SELECT(crdb_internal.index_span(%d,%d,1)[1],crdb_internal.index_span(%d,%d,1)[2])))` ,
1789
+ tenantID .ToUint64 (), tableID , tenantID .ToUint64 (), tableID )
1790
+ }
1791
+
1792
+ // splitPrimaryKeyIndexSpan splits the primary key index span for a table to ensure
1793
+ // crdb_internal.tenant_span_stats() uses the stats that hang off of the descriptor.
1794
+ func splitPrimaryKeyIndexSpan (
1795
+ ctx context.Context , t * testing.T , db * kv.DB , tenantID roachpb.TenantID , tableID int ,
1796
+ ) {
1797
+ codec := keys .MakeSQLCodec (tenantID )
1798
+ pkStartKey := codec .IndexPrefix (uint32 (tableID ), 1 )
1799
+ pkEndKey := pkStartKey .PrefixEnd ()
1800
+ require .NoError (t , db .AdminSplit (ctx , pkStartKey , hlc .MaxTimestamp ))
1801
+ require .NoError (t , db .AdminSplit (ctx , pkEndKey , hlc .MaxTimestamp ))
1802
+ }
0 commit comments