Skip to content

Commit

Permalink
Merge #24880 #24896
Browse files Browse the repository at this point in the history
24880: storageccl: pull in fix for reading GCS files r=mjibson a=mjibson

The linked issue lists a commit attempting to fix occasional problems
we see during large GCS reads. They haven't been able to repro it
but think this will fix it. Pull in that update so we can run it for
a few months for testing.

See: googleapis/google-cloud-go#784

Release note: None

24896: engine: find split keys in the first range of a partition r=tschottdorf,a-robinson,bdarnell,danhhz a=benesch

This deserves a roachtest—roachmart should make sure it has the correct number of ranges after inserting data—but I wanted to get this out for review ASAP.

---

MVCCFindSplitKey would previously fail to find any split keys in the
first range of a partition. As a result, partitioned tables have been
observed with multi-gigabyte ranges. This commit fixes the bug.

Specifically, MVCCFindSplitKey was assuming that the start key of a
range within a table was also the row prefix for the first row of data
in the range. This does not hold true for the first range of a table or
a partition of a table--that range begins at, for example, /Table/51,
while the row begins at /Table/51/1/aardvark. The old code had a special
case for the first range in a table, but not for the first range in a
partition. (It predates partitioning.)

Remove the need for special casing by actually looking in RocksDB to
determine the row prefix for the first row of data rather than
attempting to derive it from the range start key. This properly handles
partitioning and is robust against future changes to range split
boundaries.

See the commit within for more details on the approach.

Release note (bug fix): Ranges in partitioned tables now properly split
to respect their configured maximum size.

Co-authored-by: Matt Jibson <[email protected]>
Co-authored-by: Nikhil Benesch <[email protected]>
  • Loading branch information
3 people committed Apr 18, 2018
3 parents 8f8da37 + c975931 + cb1b6d7 commit 9ce2b62
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 56 deletions.
30 changes: 25 additions & 5 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ ignored = [
name = "github.com/rubyist/circuitbreaker"
branch = "master"

# https://github.com/GoogleCloudPlatform/google-cloud-go/issues/784
# Remove this when that commit is in a tagged release.
[[constraint]]
name = "cloud.google.com/go"
revision = "094187a3a68a589c8217a46b90af0efae38542b9"

# github.com/docker/docker depends on a few functions not included in the
# latest release: reference.{FamiliarName,ParseNormalizedNamed,TagNameOnly}.
#
Expand Down
63 changes: 54 additions & 9 deletions pkg/storage/engine/mvcc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2531,17 +2531,61 @@ func MVCCFindSplitKey(
it := engine.NewIterator(IterOptions{})
defer it.Close()

// We must never return a split key that falls within a table row. (Rows in
// tables with multiple column families are comprised of multiple keys, one
// key per column family.)
//
// Managing this is complicated: the logic for picking a split key that
// creates ranges of the right size lives in C++, while the logic for
// determining whether a key falls within a table row lives in Go.
//
// Most of the time, we can let C++ pick whatever key it wants. If it picks a
// key in the middle of a row, we simply rewind the key to the start of the
// row. This is handled by keys.EnsureSafeSplitKey.
//
// If, however, that first row in the range is so large that it exceeds the
// range size threshold on its own, and that row is comprised of multiple
// column families, we have a problem. C++ will hand us a key in the middle of
// that row, keys.EnsureSafeSplitKey will rewind the key to the beginning of
// the row, and... we'll end up with what's likely to be the start key of the
// range. The higher layers of the stack will take this to mean that no splits
// are required, when in fact the range is desperately in need of a split.
//
// Note that the first range of a table or a partition of a table does not
// start on a row boundary and so we have a slightly different problem.
// Instead of not splitting the range at all, we'll create a split at the
// start of the first row, resulting in an unnecessary empty range from the
// beginning of the table to the first row in the table (e.g., from /Table/51
// to /Table/51/1/aardvark...). The right-hand side of the split will then be
// susceptible to never being split as outlined above.
//
// To solve both of these problems, we find the end of the first row in Go,
// then plumb that to C++ as a "minimum split key." We're then guaranteed that
// the key C++ returns will rewind to the start key of the range.
//
// On a related note, we find the first row by actually looking at the first
// key in the the range. A previous version of this code attempted to derive
// the first row only by looking at `key`, the start key of the range; this
// was dangerous because partitioning can split off ranges that do not start
// at valid row keys. The keys that are present in the range, by contrast, are
// necessarily valid row keys.
it.Seek(MakeMVCCMetadataKey(key.AsRawKey()))
if ok, err := it.Valid(); err != nil {
return nil, err
} else if !ok {
return nil, nil
}
minSplitKey := key
// If this is a table, we can only split at row boundaries.
if remainder, _, err := keys.DecodeTablePrefix(roachpb.Key(key)); err == nil {
// If this is the first range containing a table, its start key won't
// actually contain any row information, just the table ID. We don't want
// to restrict splits on such tables, since key.PrefixEnd will just be the
// end of the table span.
if len(remainder) > 0 {
minSplitKey = roachpb.RKey(roachpb.Key(key).PrefixEnd())
if _, _, err := keys.DecodeTablePrefix(it.UnsafeKey().Key); err == nil {
// The first key in this range represents a row in a SQL table. Advance the
// minSplitKey past this row to avoid the problems described above.
firstRowKey, err := keys.EnsureSafeSplitKey(it.Key().Key)
if err != nil {
return nil, err
}
minSplitKey = roachpb.RKey(firstRowKey.PrefixEnd())
}

splitKey, err := it.FindSplitKey(
MakeMVCCMetadataKey(key.AsRawKey()),
MakeMVCCMetadataKey(endKey.AsRawKey()),
Expand All @@ -2551,7 +2595,8 @@ func MVCCFindSplitKey(
if err != nil {
return nil, err
}
// The family ID has been removed from this key, making it a valid split point.
// Ensure the key is a valid split point that does not fall in the middle of a
// SQL row by removing the column family ID, if any, from the end of the key.
return keys.EnsureSafeSplitKey(splitKey.Key)
}

Expand Down
109 changes: 68 additions & 41 deletions pkg/storage/engine/mvcc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3438,21 +3438,19 @@ func TestFindSplitKey(t *testing.T) {
func TestFindValidSplitKeys(t *testing.T) {
defer leaktest.AfterTest(t)()

const userID = keys.MaxReservedDescID + 1
// Manually creates rows corresponding to the schema:
// CREATE TABLE t (id STRING PRIMARY KEY, col INT)
encodeTableKey := func(rowVal string, colFam uint32) roachpb.Key {
tableKey := keys.MakeTablePrefix(keys.MaxReservedDescID + 1)
// CREATE TABLE t (id1 STRING, id2 STRING, ... PRIMARY KEY (id1, id2, ...))
tablePrefix := func(id uint32, rowVals ...string) roachpb.Key {
tableKey := keys.MakeTablePrefix(id)
rowKey := roachpb.Key(encoding.EncodeVarintAscending(append([]byte(nil), tableKey...), 1))
rowKey = encoding.EncodeStringAscending(encoding.EncodeVarintAscending(rowKey, 1), rowVal)
colKey := keys.MakeFamilyKey(append([]byte(nil), rowKey...), colFam)
return colKey
}
splitKeyFromTableKey := func(tableKey roachpb.Key) roachpb.Key {
splitKey, err := keys.EnsureSafeSplitKey(tableKey)
if err != nil {
t.Fatal(err)
for _, rowVal := range rowVals {
rowKey = encoding.EncodeStringAscending(rowKey, rowVal)
}
return splitKey
return rowKey
}
addColFam := func(rowKey roachpb.Key, colFam uint32) roachpb.Key {
return keys.MakeFamilyKey(append([]byte(nil), rowKey...), colFam)
}

testCases := []struct {
Expand All @@ -3474,11 +3472,12 @@ func TestFindValidSplitKeys(t *testing.T) {
// All system span cannot be split.
{
keys: []roachpb.Key{
roachpb.Key(keys.MakeTablePrefix(1)),
roachpb.Key(keys.MakeTablePrefix(keys.MaxSystemConfigDescID)),
addColFam(tablePrefix(1, "some", "data"), 1),
addColFam(tablePrefix(keys.MaxSystemConfigDescID, "blah"), 1),
},
expSplit: nil,
expError: false,
rangeStart: keys.MakeTablePrefix(1),
expSplit: nil,
expError: false,
},
// Between meta1 and meta2, splits at meta2.
{
Expand Down Expand Up @@ -3565,47 +3564,75 @@ func TestFindValidSplitKeys(t *testing.T) {
// or return the start key of the range.
{
keys: []roachpb.Key{
encodeTableKey("a", 1),
encodeTableKey("a", 2),
encodeTableKey("a", 3),
encodeTableKey("a", 4),
encodeTableKey("a", 5),
encodeTableKey("b", 1),
encodeTableKey("c", 1),
addColFam(tablePrefix(userID, "a"), 1),
addColFam(tablePrefix(userID, "a"), 2),
addColFam(tablePrefix(userID, "a"), 3),
addColFam(tablePrefix(userID, "a"), 4),
addColFam(tablePrefix(userID, "a"), 5),
addColFam(tablePrefix(userID, "b"), 1),
addColFam(tablePrefix(userID, "c"), 1),
},
rangeStart: splitKeyFromTableKey(encodeTableKey("a", 1)),
expSplit: splitKeyFromTableKey(encodeTableKey("b", 1)),
rangeStart: tablePrefix(userID, "a"),
expSplit: tablePrefix(userID, "b"),
expError: false,
},
// More example table data. Make sure ranges at the start of a table can
// be split properly - this checks that the minSplitKey logic doesn't
// break for such ranges.
{
keys: []roachpb.Key{
encodeTableKey("a", 1),
encodeTableKey("b", 1),
encodeTableKey("c", 1),
encodeTableKey("d", 1),
addColFam(tablePrefix(userID, "a"), 1),
addColFam(tablePrefix(userID, "b"), 1),
addColFam(tablePrefix(userID, "c"), 1),
addColFam(tablePrefix(userID, "d"), 1),
},
rangeStart: keys.MakeTablePrefix(keys.MaxReservedDescID + 1),
expSplit: splitKeyFromTableKey(encodeTableKey("c", 1)),
rangeStart: keys.MakeTablePrefix(userID),
expSplit: tablePrefix(userID, "c"),
expError: false,
},
// More example table data. Make sure ranges at the start of a table can
// be split properly (even if "properly" means creating an empty LHS,
// splitting here will at least allow the resulting RHS to split again).
// be split properly even in the presence of a large first row.
{
keys: []roachpb.Key{
encodeTableKey("a", 1),
encodeTableKey("a", 2),
encodeTableKey("a", 3),
encodeTableKey("a", 4),
encodeTableKey("a", 5),
encodeTableKey("b", 1),
encodeTableKey("c", 1),
addColFam(tablePrefix(userID, "a"), 1),
addColFam(tablePrefix(userID, "a"), 2),
addColFam(tablePrefix(userID, "a"), 3),
addColFam(tablePrefix(userID, "a"), 4),
addColFam(tablePrefix(userID, "a"), 5),
addColFam(tablePrefix(userID, "b"), 1),
addColFam(tablePrefix(userID, "c"), 1),
},
rangeStart: keys.MakeTablePrefix(keys.MaxReservedDescID + 1),
expSplit: splitKeyFromTableKey(encodeTableKey("a", 1)),
expSplit: tablePrefix(userID, "b"),
expError: false,
},
// One partition where partition key is the first column. Checks that
// split logic is not confused by the special partition start key.
{
keys: []roachpb.Key{
addColFam(tablePrefix(userID, "a", "a"), 1),
addColFam(tablePrefix(userID, "a", "b"), 1),
addColFam(tablePrefix(userID, "a", "c"), 1),
addColFam(tablePrefix(userID, "a", "d"), 1),
},
rangeStart: tablePrefix(userID, "a"),
expSplit: tablePrefix(userID, "a", "c"),
expError: false,
},
// One partition with a large first row. Checks that our logic to avoid
// splitting in the middle of a row still applies.
{
keys: []roachpb.Key{
addColFam(tablePrefix(userID, "a", "a"), 1),
addColFam(tablePrefix(userID, "a", "a"), 2),
addColFam(tablePrefix(userID, "a", "a"), 3),
addColFam(tablePrefix(userID, "a", "a"), 4),
addColFam(tablePrefix(userID, "a", "a"), 5),
addColFam(tablePrefix(userID, "a", "b"), 1),
addColFam(tablePrefix(userID, "a", "c"), 1),
},
rangeStart: tablePrefix(userID, "a"),
expSplit: tablePrefix(userID, "a", "b"),
expError: false,
},
}
Expand Down
2 changes: 1 addition & 1 deletion vendor
Submodule vendor updated 97 files
+3 −0 cloud.google.com/go/CONTRIBUTORS
+1 −1 cloud.google.com/go/LICENSE
+38 −10 cloud.google.com/go/iam/iam.go
+54 −0 cloud.google.com/go/internal/annotate.go
+1 −2 cloud.google.com/go/internal/retry.go
+83 −0 cloud.google.com/go/internal/trace/go18.go
+30 −0 cloud.google.com/go/internal/trace/not_go18.go
+1 −1 cloud.google.com/go/internal/version/version.go
+21 −28 cloud.google.com/go/storage/acl.go
+198 −15 cloud.google.com/go/storage/bucket.go
+9 −3 cloud.google.com/go/storage/copy.go
+15 −11 cloud.google.com/go/storage/doc.go
+37 −16 cloud.google.com/go/storage/iam.go
+188 −0 cloud.google.com/go/storage/notifications.go
+221 −11 cloud.google.com/go/storage/reader.go
+85 −151 cloud.google.com/go/storage/storage.go
+24 −7 cloud.google.com/go/storage/writer.go
+1 −0 go.opencensus.io/AUTHORS
+202 −0 go.opencensus.io/LICENSE
+94 −0 go.opencensus.io/exporter/stackdriver/propagation/http.go
+32 −0 go.opencensus.io/internal/internal.go
+50 −0 go.opencensus.io/internal/sanitize.go
+72 −0 go.opencensus.io/internal/tagencoding/tagencoding.go
+52 −0 go.opencensus.io/internal/traceinternals.go
+84 −0 go.opencensus.io/plugin/ochttp/client.go
+125 −0 go.opencensus.io/plugin/ochttp/client_stats.go
+19 −0 go.opencensus.io/plugin/ochttp/doc.go
+119 −0 go.opencensus.io/plugin/ochttp/propagation/b3/b3.go
+193 −0 go.opencensus.io/plugin/ochttp/server.go
+173 −0 go.opencensus.io/plugin/ochttp/stats.go
+215 −0 go.opencensus.io/plugin/ochttp/trace.go
+55 −0 go.opencensus.io/stats/doc.go
+25 −0 go.opencensus.io/stats/internal/record.go
+28 −0 go.opencensus.io/stats/internal/validation.go
+96 −0 go.opencensus.io/stats/measure.go
+52 −0 go.opencensus.io/stats/measure_float64.go
+52 −0 go.opencensus.io/stats/measure_int64.go
+52 −0 go.opencensus.io/stats/record.go
+24 −0 go.opencensus.io/stats/units.go
+120 −0 go.opencensus.io/stats/view/aggregation.go
+207 −0 go.opencensus.io/stats/view/aggregation_data.go
+84 −0 go.opencensus.io/stats/view/collector.go
+46 −0 go.opencensus.io/stats/view/doc.go
+53 −0 go.opencensus.io/stats/view/export.go
+183 −0 go.opencensus.io/stats/view/view.go
+233 −0 go.opencensus.io/stats/view/worker.go
+170 −0 go.opencensus.io/stats/view/worker_commands.go
+41 −0 go.opencensus.io/tag/context.go
+26 −0 go.opencensus.io/tag/doc.go
+35 −0 go.opencensus.io/tag/key.go
+197 −0 go.opencensus.io/tag/map.go
+221 −0 go.opencensus.io/tag/map_codec.go
+31 −0 go.opencensus.io/tag/profile_19.go
+23 −0 go.opencensus.io/tag/profile_not19.go
+56 −0 go.opencensus.io/tag/validate.go
+114 −0 go.opencensus.io/trace/basetypes.go
+40 −0 go.opencensus.io/trace/config.go
+55 −0 go.opencensus.io/trace/doc.go
+74 −0 go.opencensus.io/trace/export.go
+21 −0 go.opencensus.io/trace/internal/internal.go
+108 −0 go.opencensus.io/trace/propagation/propagation.go
+76 −0 go.opencensus.io/trace/sampling.go
+130 −0 go.opencensus.io/trace/spanbucket.go
+306 −0 go.opencensus.io/trace/spanstore.go
+463 −0 go.opencensus.io/trace/trace.go
+5 −10 golang.org/x/oauth2/CONTRIBUTING.md
+0 −25 golang.org/x/oauth2/client_appengine.go
+23 −45 golang.org/x/oauth2/google/default.go
+42 −0 golang.org/x/oauth2/google/doc_go19.go
+43 −0 golang.org/x/oauth2/google/doc_not_go19.go
+57 −0 golang.org/x/oauth2/google/go19.go
+1 −11 golang.org/x/oauth2/google/google.go
+54 −0 golang.org/x/oauth2/google/not_go19.go
+31 −2 golang.org/x/oauth2/google/sdk.go
+13 −0 golang.org/x/oauth2/internal/client_appengine.go
+0 −38 golang.org/x/oauth2/internal/oauth2.go
+26 −8 golang.org/x/oauth2/internal/token.go
+6 −40 golang.org/x/oauth2/internal/transport.go
+4 −1 golang.org/x/oauth2/jwt/jwt.go
+27 −18 golang.org/x/oauth2/oauth2.go
+19 −2 golang.org/x/oauth2/token.go
+1 −0 google.golang.org/api/CONTRIBUTORS
+17 −0 google.golang.org/api/gensupport/go18.go
+36 −4 google.golang.org/api/gensupport/media.go
+14 −0 google.golang.org/api/gensupport/not_go18.go
+10 −0 google.golang.org/api/gensupport/send.go
+8 −70 google.golang.org/api/internal/creds.go
+9 −1 google.golang.org/api/internal/settings.go
+1 −1 google.golang.org/api/iterator/iterator.go
+32 −0 google.golang.org/api/option/credentials_go19.go
+32 −0 google.golang.org/api/option/credentials_notgo19.go
+3,722 −3,649 google.golang.org/api/storage/v1/storage-api.json
+392 −197 google.golang.org/api/storage/v1/storage-gen.go
+12 −15 google.golang.org/api/transport/http/dial.go
+31 −0 google.golang.org/api/transport/http/go18.go
+21 −0 google.golang.org/api/transport/http/not_go18.go
+252 −0 google.golang.org/genproto/googleapis/rpc/code/code.pb.go

0 comments on commit 9ce2b62

Please sign in to comment.