Skip to content

Commit 03c71ef

Browse files
committed
logical: fix bug in tombstone updater
This fixes a bug in the tombstone updater. The tombstone decoder was using the columns decoded by the kv writer when it needs to expect the columns decoded by the sql/crud writers. There is one difference between these two sets of columns that impacts computed columns: 1. The sql/crud writer only decode computed columns that are in the primary key. 2. The kv writer decodes virtual computed columns in the primary key and all stored computed columns in the row. This mismatch can cause panics if the stored column is in an index (which is what the conflict workload noticed) and it can cause the tombstone updater to generate incorrect keys if there is a stored computed column with a lower column id than the primary key column. If that happened, and we hit the tombstone update race condition, we would issue a cput for the wrong row. That would end up creating a tombstone with an origin time stamp at that wrong key value. This would not cause data corruption, since we are using cput, so the write only succeeds if there was nothing there. Additionally we would be creating a tombstone, so it does not have side effects from the SQL layer. But it could LDR to fail to converge, since the tombstone updater is not working. A secondary discovery here is that the tombstone updater is formatting deletes for indexes other than the primary index. Now, the deleter will skip generating values for secondary indexes if the cput helper says its updating a deleted row. Fixes: #159306 Release note: None
1 parent 0f03329 commit 03c71ef

File tree

4 files changed

+96
-3
lines changed

4 files changed

+96
-3
lines changed

pkg/crosscluster/logical/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ go_test(
154154
"//pkg/ccl/changefeedccl/changefeedbase",
155155
"//pkg/ccl/storageccl",
156156
"//pkg/crosscluster",
157+
"//pkg/crosscluster/ldrrandgen",
157158
"//pkg/crosscluster/replicationtestutils",
158159
"//pkg/crosscluster/streamclient",
159160
"//pkg/crosscluster/streamclient/randclient",

pkg/crosscluster/logical/tombstone_updater.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,11 @@ func (tu *tombstoneUpdater) getDeleter(ctx context.Context, txn *kv.Txn) (row.De
164164
return row.Deleter{}, err
165165
}
166166

167-
cols, err := writeableColunms(ctx, tu.leased.descriptor.Underlying().(catalog.TableDescriptor))
168-
if err != nil {
169-
return row.Deleter{}, err
167+
table := tu.leased.descriptor.Underlying().(catalog.TableDescriptor)
168+
schema := getColumnSchema(table)
169+
cols := make([]catalog.Column, len(schema))
170+
for i, cs := range schema {
171+
cols[i] = cs.column
170172
}
171173

172174
tu.leased.deleter = row.MakeDeleter(tu.codec, tu.leased.descriptor.Underlying().(catalog.TableDescriptor), nil /* lockedIndexes */, cols, tu.sd, &tu.settings.SV, nil /* metrics */)

pkg/crosscluster/logical/tombstone_updater_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@ package logical
77

88
import (
99
"context"
10+
"math/rand"
11+
"strings"
1012
"testing"
1113

14+
"github.com/cockroachdb/cockroach/pkg/crosscluster/ldrrandgen"
1215
"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
1316
"github.com/cockroachdb/cockroach/pkg/sql"
17+
"github.com/cockroachdb/cockroach/pkg/sql/catalog"
1418
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descs"
1519
"github.com/cockroachdb/cockroach/pkg/sql/catalog/desctestutils"
1620
"github.com/cockroachdb/cockroach/pkg/sql/catalog/lease"
1721
"github.com/cockroachdb/cockroach/pkg/sql/isql"
22+
"github.com/cockroachdb/cockroach/pkg/sql/randgen"
1823
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
1924
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
2025
"github.com/cockroachdb/cockroach/pkg/util/log"
26+
"github.com/cockroachdb/cockroach/pkg/util/randutil"
2127
"github.com/stretchr/testify/require"
2228
)
2329

@@ -82,3 +88,81 @@ func TestTombstoneUpdaterSetsOriginID(t *testing.T) {
8288
{"1", "42"},
8389
})
8490
}
91+
92+
func TestTombstoneUpdaterRandomTables(t *testing.T) {
93+
defer leaktest.AfterTest(t)()
94+
defer log.Scope(t).Close(t)
95+
96+
ctx := context.Background()
97+
server, s, runners, dbNames := setupServerWithNumDBs(t, ctx, testClusterBaseClusterArgs, 1, 1)
98+
defer server.Stopper().Stop(ctx)
99+
100+
runner := runners[0]
101+
dbName := dbNames[0]
102+
103+
rng, _ := randutil.NewPseudoRand()
104+
tableName := "rand_table"
105+
106+
stmt := tree.SerializeForDisplay(ldrrandgen.GenerateLDRTable(ctx, rng, tableName, true))
107+
t.Logf("Creating table with schema: %s", stmt)
108+
runner.Exec(t, stmt)
109+
110+
desc := desctestutils.TestingGetMutableExistingTableDescriptor(
111+
s.DB(), s.Codec(), dbName, tableName)
112+
113+
sd := sql.NewInternalSessionData(ctx, s.ClusterSettings(), "" /* opName */)
114+
tu := newTombstoneUpdater(s.Codec(), s.DB(), s.LeaseManager().(*lease.Manager), desc.GetID(), sd, s.ClusterSettings())
115+
defer tu.ReleaseLeases(ctx)
116+
117+
columnSchemas := getColumnSchema(desc)
118+
cols := make([]catalog.Column, len(columnSchemas))
119+
for i, cs := range columnSchemas {
120+
cols[i] = cs.column
121+
}
122+
123+
config := s.ExecutorConfig().(sql.ExecutorConfig)
124+
125+
session := newInternalSession(t, s)
126+
defer session.Close(ctx)
127+
128+
writer, err := newSQLRowWriter(ctx, desc, session)
129+
require.NoError(t, err)
130+
131+
for i := 0; i < 10; i++ {
132+
row := generateRandomRow(rng, cols)
133+
before := s.Clock().Now()
134+
after := s.Clock().Now()
135+
136+
err := sql.DescsTxn(ctx, &config, func(
137+
ctx context.Context, txn isql.Txn, descriptors *descs.Collection,
138+
) error {
139+
_, err := tu.updateTombstone(ctx, txn, after, row)
140+
return err
141+
})
142+
require.NoError(t, err)
143+
144+
stats, err := tu.updateTombstone(ctx, nil, before, row)
145+
require.NoError(t, err)
146+
require.Equal(t, int64(1), stats.kvWriteTooOld, "expected 1 kv put for tombstone update")
147+
148+
// Use the table writer to try and insert the previous row using the
149+
// `before` timestamp. This should fail with LWW error because the
150+
// tombstone was written at a later timestamp. It may fail with integer out
151+
// of range error if the table has computed columns and the random datums
152+
// add to produces something out of range.
153+
err = writer.InsertRow(ctx, before, row)
154+
require.Error(t, err)
155+
if !(strings.Contains(err.Error(), "integer out of range") || isLwwLoser(err)) {
156+
t.Fatalf("expected LWW or integer out of range error, got: %v", err)
157+
}
158+
}
159+
}
160+
161+
func generateRandomRow(rng *rand.Rand, cols []catalog.Column) []tree.Datum {
162+
row := make([]tree.Datum, 0, len(cols))
163+
for _, col := range cols {
164+
datum := randgen.RandDatum(rng, col.GetType(), col.IsNullable())
165+
row = append(row, datum)
166+
}
167+
return row
168+
}

pkg/sql/row/deleter.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,12 @@ func (rd *Deleter) DeleteRow(
212212
return err
213213
}
214214

215+
if oth.PreviousWasDeleted {
216+
// If we are updating a tombstone, then there are no secondary index entries
217+
// that need to be deleted.
218+
return nil
219+
}
220+
215221
// Delete the row from any secondary indices.
216222
for i, index := range rd.Helper.Indexes {
217223
// If the index ID exists in the set of indexes to ignore, do not

0 commit comments

Comments
 (0)