Skip to content

Commit c9d3f19

Browse files
craig[bot]nvbasubiottobenesch
committed
27112: roachpb: replace `gogoproto.onlyone` with `oneof` in BatchRequest/BatchResponse r=nvanbenschoten a=nvanbenschoten All Requests and Responses pass through RequestUnion/ResponseUnion structs when they are added to BatchRequests/BatchResponses. In order to ensure that only one Request type can be assigned to one of these RequestUnion or ResponseUnion structs, we currently use gogoproto's approach to tagged unions: the `gogoproto.onlyone` option. This option was introduced before proto3. Proto3 then added the `oneof` option, which for all intents and purposes addresses the same issue: https://developers.google.com/protocol-buffers/docs/proto#oneof. However, there is one major difference between the two options, which is in their generated code. `gogoproto.onlyone` will generate a single flat struct with pointers to each possible variant type. `oneof` will generate a union interface and an interface "wrapper" struct for each variant type. The effect of this is that `onlyone` will generate code that looks like this: ``` type Union struct { Variant1 *Variant1Type Variant2 *Variant2Type ... } ``` While `oneof` will generate code the looks like this: ``` type Union struct { Value isUnion_Value } type isUnion_Value interface { ... } type Union_Variant1 struct { Variant1 *Variant1Type } type Union_Variant2 struct { Variant2 *Variant2Type } ``` There are pretty obvious tradeoffs to each. For one, `oneof` introduces an extra layer of indirection, which forces an extra allocation. It also doesn't generate particularly useful setters and getters. On the other hand, `onlyone` creates a large struct that grows linearly with the number of variants. Neither approach is great, and there has been **A LOT** of discussion on this: - golang/protobuf#78 - golang/protobuf#283 - gogo/protobuf#103 - gogo/protobuf#168 Clearly neither approach is ideal, ergonomically or with regard to performance. However, over time, the tradeoff has been getting worse for us and it's time we consider switching over to `oneof` in `RequestUnion` and `ResponseUnion`. These structs have gotten huge as more and more request variants have been added: `RequestUnion` has grown to **328 bytes** and `ResponseUnion` has grown to **320 bytes**. It has gotten to the point where the wasted space is non-negligible. This change switches over to `oneof` to shrink these union structs down to more manageable sizes (16 bytes each). The downside of this is that in reducing the struct size we end up introducing an extra allocation. This isn't great, but we can avoid the extra allocation in some places (like `BatchRequest.CreateReply`) by grouping the allocation with that of the Request/Response itself. We've seen previous cases like #4216 where adding in an extra allocation/indirection is a net-win if it reduces a commonly used struct's size significantly. The other downside to this change is that the ergonomics of `oneof` aren't quite as nice as `gogo.onlyone`. Specifically, `gogo.onlyone` generates getters and setters called `GetValue` and `SetValue` that provide access to the wrapped `interface{}`, which we can assert to a `Request`. `oneof` doesn't provide such facilities. This was the cause of a lot of the discussions linked above. While it we be nice for this to be resolved upstream, I think we've waited long enough (~3 years) for a resolution to those discussions. For now, we'll just generate the getters and setters ourselves. This change demonstrated about a **5%** improvement when running kv95 on my local laptop. When run on a three-node GCE cluster (4 vCPUs), the improvements were less pronounced but still present. kv95 showed a throughput improvement of **2.4%**. Running kv100 showed a much more dramatic improvement of **18%** on the three-node GCE cluster. I think this is because kv100 is essentially a hot loop where all reads miss because the cluster remains empty, so it's the best-case scenario for this change. Still, the impact was shocking. Release note (performance improvement): Reduce the memory size of commonly used Request and Response objects. 27114: opt/sql: fix explain analyze missing option r=asubiotto a=asubiotto ConstructExplain previously ignored the ANALYZE option so any EXPLAIN ANALYZE statement would result in execution as an EXPLAIN (DISTSQL) statement. The ANALYZE option is now observed in ConstructExplain. Additionally, the stmtType field from the explainDistSQLNode has been removed because it was not necessary and it was unclear how to pass this from the `execFactory`. Release note: None 27116: Makefile: learn that roachtest depends on optimizer-generated code r=benesch a=benesch As mentioned in cd4415c, the Makefile will one day be smart enough to deduce this on its own, but for now it's simpler to explicitly list the commands that require generated code. Note that the simple but coarse solution of assuming that all commands depend on generated code is inviable as some of these commands are used to generate the code in the first place. Release note: None 27119: storage: extract replica unlinking into store method r=tschottdorf a=benesch Extract some code that was duplicated in three places into a dedicated helper method. Prerequisite for #27061. Release note: None Co-authored-by: Nathan VanBenschoten <nvanbenschoten@gmail.com> Co-authored-by: Alfonso Subiotto Marqués <alfonso@cockroachlabs.com> Co-authored-by: Nikhil Benesch <nikhil.benesch@gmail.com>
5 parents 21a2c80 + cb9f4fc + 83e4f10 + f1d364d + d404a3f commit c9d3f19

File tree

19 files changed

+5111
-1750
lines changed

19 files changed

+5111
-1750
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,7 @@ logictestccl-package = ./pkg/ccl/logictestccl
14291429

14301430
# Additional dependencies for binaries that depend on generated code.
14311431
bin/workload bin/docgen bin/roachtest: $(SQLPARSER_TARGETS) $(PROTOBUF_TARGETS)
1432+
bin/roachtest: $(OPTGEN_TARGETS)
14321433

14331434
$(bins): bin/%: bin/%.d | bin/prereqs bin/.submodules-initialized
14341435
@echo go install -v $*

pkg/ccl/backupccl/backup_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,9 @@ func checkInProgressBackupRestore(
534534
params := base.TestClusterArgs{}
535535
params.ServerArgs.Knobs.Store = &storage.StoreTestingKnobs{
536536
TestingResponseFilter: func(ba roachpb.BatchRequest, br *roachpb.BatchResponse) *roachpb.Error {
537-
for _, res := range br.Responses {
538-
if res.Export != nil || res.Import != nil {
537+
for _, ru := range br.Responses {
538+
switch ru.GetInner().(type) {
539+
case *roachpb.ExportResponse, *roachpb.ImportResponse:
539540
<-allowResponse
540541
}
541542
}

pkg/kv/transport_race.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package kv
1919

2020
import (
2121
"context"
22-
"encoding/gob"
22+
"encoding/json"
2323
"io/ioutil"
2424
"math/rand"
2525
"sync/atomic"
@@ -74,7 +74,7 @@ func GRPCTransportFactory(
7474
// are evicted in FIFO order.
7575
const size = 1000
7676
bas := make([]*roachpb.BatchRequest, size)
77-
encoder := gob.NewEncoder(ioutil.Discard)
77+
encoder := json.NewEncoder(ioutil.Discard)
7878
for {
7979
iters++
8080
start := timeutil.Now()

pkg/kv/truncate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func TestTruncate(t *testing.T) {
148148

149149
original := roachpb.BatchRequest{Requests: make([]roachpb.RequestUnion, len(goldenOriginal.Requests))}
150150
for i, request := range goldenOriginal.Requests {
151-
original.Requests[i].SetValue(request.GetInner().ShallowCopy())
151+
original.Requests[i].MustSetInner(request.GetInner().ShallowCopy())
152152
}
153153

154154
desc := &roachpb.RangeDescriptor{

pkg/roachpb/api.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -446,22 +446,12 @@ func (*NoopResponse) Verify(_ Request) error {
446446
return nil
447447
}
448448

449-
// GetInner returns the Request contained in the union.
450-
func (ru RequestUnion) GetInner() Request {
451-
return ru.GetValue().(Request)
452-
}
453-
454-
// GetInner returns the Response contained in the union.
455-
func (ru ResponseUnion) GetInner() Response {
456-
return ru.GetValue().(Response)
457-
}
458-
459449
// MustSetInner sets the Request contained in the union. It panics if the
460450
// request is not recognized by the union type. The RequestUnion is reset
461451
// before being repopulated.
462452
func (ru *RequestUnion) MustSetInner(args Request) {
463453
ru.Reset()
464-
if !ru.SetValue(args) {
454+
if !ru.SetInner(args) {
465455
panic(fmt.Sprintf("%T excludes %T", ru, args))
466456
}
467457
}
@@ -471,7 +461,7 @@ func (ru *RequestUnion) MustSetInner(args Request) {
471461
// before being repopulated.
472462
func (ru *ResponseUnion) MustSetInner(reply Response) {
473463
ru.Reset()
474-
if !ru.SetValue(reply) {
464+
if !ru.SetInner(reply) {
475465
panic(fmt.Sprintf("%T excludes %T", ru, reply))
476466
}
477467
}

pkg/roachpb/api.pb.go

Lines changed: 4054 additions & 1341 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/roachpb/api.proto

Lines changed: 85 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,101 +1258,96 @@ message GetSnapshotForMergeResponse {
12581258
// Be cautious about deprecating fields as doing so can lead to inconsistencies
12591259
// between replicas.
12601260
message RequestUnion {
1261-
option (gogoproto.onlyone) = true;
1262-
1263-
GetRequest get = 1;
1264-
PutRequest put = 2;
1265-
ConditionalPutRequest conditional_put = 3;
1266-
IncrementRequest increment = 4;
1267-
DeleteRequest delete = 5;
1268-
DeleteRangeRequest delete_range = 6;
1269-
ClearRangeRequest clear_range = 38;
1270-
ScanRequest scan = 7;
1271-
BeginTransactionRequest begin_transaction = 8;
1272-
EndTransactionRequest end_transaction = 9;
1273-
AdminSplitRequest admin_split = 10;
1274-
AdminMergeRequest admin_merge = 11;
1275-
AdminTransferLeaseRequest admin_transfer_lease = 29;
1276-
AdminChangeReplicasRequest admin_change_replicas = 35;
1277-
HeartbeatTxnRequest heartbeat_txn = 12;
1278-
GCRequest gc = 13;
1279-
PushTxnRequest push_txn = 14;
1280-
reserved 15;
1281-
ResolveIntentRequest resolve_intent = 16;
1282-
ResolveIntentRangeRequest resolve_intent_range = 17;
1283-
MergeRequest merge = 18;
1284-
TruncateLogRequest truncate_log = 19;
1285-
RequestLeaseRequest request_lease = 20;
1286-
ReverseScanRequest reverse_scan = 21;
1287-
ComputeChecksumRequest compute_checksum = 22;
1288-
reserved 23;
1289-
CheckConsistencyRequest check_consistency = 24;
1290-
NoopRequest noop = 25;
1291-
InitPutRequest init_put = 26;
1292-
reserved 27;
1293-
TransferLeaseRequest transfer_lease = 28;
1294-
LeaseInfoRequest lease_info = 30;
1295-
WriteBatchRequest write_batch = 31;
1296-
ExportRequest export = 32;
1297-
ImportRequest import = 34;
1298-
QueryTxnRequest query_txn = 33;
1299-
QueryIntentRequest query_intent = 42;
1300-
AdminScatterRequest admin_scatter = 36;
1301-
AddSSTableRequest add_sstable = 37;
1302-
RecomputeStatsRequest recompute_stats = 39;
1303-
RefreshRequest refresh = 40;
1304-
RefreshRangeRequest refresh_range = 41;
1305-
GetSnapshotForMergeRequest get_snapshot_for_merge = 43;
1261+
oneof value {
1262+
GetRequest get = 1;
1263+
PutRequest put = 2;
1264+
ConditionalPutRequest conditional_put = 3;
1265+
IncrementRequest increment = 4;
1266+
DeleteRequest delete = 5;
1267+
DeleteRangeRequest delete_range = 6;
1268+
ClearRangeRequest clear_range = 38;
1269+
ScanRequest scan = 7;
1270+
BeginTransactionRequest begin_transaction = 8;
1271+
EndTransactionRequest end_transaction = 9;
1272+
AdminSplitRequest admin_split = 10;
1273+
AdminMergeRequest admin_merge = 11;
1274+
AdminTransferLeaseRequest admin_transfer_lease = 29;
1275+
AdminChangeReplicasRequest admin_change_replicas = 35;
1276+
HeartbeatTxnRequest heartbeat_txn = 12;
1277+
GCRequest gc = 13;
1278+
PushTxnRequest push_txn = 14;
1279+
ResolveIntentRequest resolve_intent = 16;
1280+
ResolveIntentRangeRequest resolve_intent_range = 17;
1281+
MergeRequest merge = 18;
1282+
TruncateLogRequest truncate_log = 19;
1283+
RequestLeaseRequest request_lease = 20;
1284+
ReverseScanRequest reverse_scan = 21;
1285+
ComputeChecksumRequest compute_checksum = 22;
1286+
CheckConsistencyRequest check_consistency = 24;
1287+
NoopRequest noop = 25;
1288+
InitPutRequest init_put = 26;
1289+
TransferLeaseRequest transfer_lease = 28;
1290+
LeaseInfoRequest lease_info = 30;
1291+
WriteBatchRequest write_batch = 31;
1292+
ExportRequest export = 32;
1293+
ImportRequest import = 34;
1294+
QueryTxnRequest query_txn = 33;
1295+
QueryIntentRequest query_intent = 42;
1296+
AdminScatterRequest admin_scatter = 36;
1297+
AddSSTableRequest add_sstable = 37;
1298+
RecomputeStatsRequest recompute_stats = 39;
1299+
RefreshRequest refresh = 40;
1300+
RefreshRangeRequest refresh_range = 41;
1301+
GetSnapshotForMergeRequest get_snapshot_for_merge = 43;
1302+
}
1303+
reserved 15, 23, 27;
13061304
}
13071305

13081306
// A ResponseUnion contains exactly one of the responses.
13091307
// The values added here must match those in RequestUnion.
13101308
message ResponseUnion {
1311-
option (gogoproto.onlyone) = true;
1312-
1313-
GetResponse get = 1;
1314-
PutResponse put = 2;
1315-
ConditionalPutResponse conditional_put = 3;
1316-
IncrementResponse increment = 4;
1317-
DeleteResponse delete = 5;
1318-
DeleteRangeResponse delete_range = 6;
1319-
ClearRangeResponse clear_range = 38;
1320-
ScanResponse scan = 7;
1321-
BeginTransactionResponse begin_transaction = 8;
1322-
EndTransactionResponse end_transaction = 9;
1323-
AdminSplitResponse admin_split = 10;
1324-
AdminMergeResponse admin_merge = 11;
1325-
AdminTransferLeaseResponse admin_transfer_lease = 29;
1326-
AdminChangeReplicasResponse admin_change_replicas = 35;
1327-
HeartbeatTxnResponse heartbeat_txn = 12;
1328-
GCResponse gc = 13;
1329-
PushTxnResponse push_txn = 14;
1330-
reserved 15;
1331-
ResolveIntentResponse resolve_intent = 16;
1332-
ResolveIntentRangeResponse resolve_intent_range = 17;
1333-
MergeResponse merge = 18;
1334-
TruncateLogResponse truncate_log = 19;
1335-
RequestLeaseResponse request_lease = 20;
1336-
ReverseScanResponse reverse_scan = 21;
1337-
ComputeChecksumResponse compute_checksum = 22;
1338-
reserved 23;
1339-
CheckConsistencyResponse check_consistency = 24;
1340-
NoopResponse noop = 25;
1341-
InitPutResponse init_put = 26;
1342-
reserved 27;
1343-
reserved 28; // TransferLease and RequestLease both use RequestLeaseResponse
1344-
LeaseInfoResponse lease_info = 30;
1345-
WriteBatchResponse write_batch = 31;
1346-
ExportResponse export = 32;
1347-
ImportResponse import = 34;
1348-
QueryTxnResponse query_txn = 33;
1349-
QueryIntentResponse query_intent = 42;
1350-
AdminScatterResponse admin_scatter = 36;
1351-
AddSSTableResponse add_sstable = 37;
1352-
RecomputeStatsResponse recompute_stats = 39;
1353-
RefreshResponse refresh = 40;
1354-
RefreshRangeResponse refresh_range = 41;
1355-
GetSnapshotForMergeResponse get_snapshot_for_merge = 43;
1309+
oneof value {
1310+
GetResponse get = 1;
1311+
PutResponse put = 2;
1312+
ConditionalPutResponse conditional_put = 3;
1313+
IncrementResponse increment = 4;
1314+
DeleteResponse delete = 5;
1315+
DeleteRangeResponse delete_range = 6;
1316+
ClearRangeResponse clear_range = 38;
1317+
ScanResponse scan = 7;
1318+
BeginTransactionResponse begin_transaction = 8;
1319+
EndTransactionResponse end_transaction = 9;
1320+
AdminSplitResponse admin_split = 10;
1321+
AdminMergeResponse admin_merge = 11;
1322+
AdminTransferLeaseResponse admin_transfer_lease = 29;
1323+
AdminChangeReplicasResponse admin_change_replicas = 35;
1324+
HeartbeatTxnResponse heartbeat_txn = 12;
1325+
GCResponse gc = 13;
1326+
PushTxnResponse push_txn = 14;
1327+
ResolveIntentResponse resolve_intent = 16;
1328+
ResolveIntentRangeResponse resolve_intent_range = 17;
1329+
MergeResponse merge = 18;
1330+
TruncateLogResponse truncate_log = 19;
1331+
RequestLeaseResponse request_lease = 20;
1332+
ReverseScanResponse reverse_scan = 21;
1333+
ComputeChecksumResponse compute_checksum = 22;
1334+
CheckConsistencyResponse check_consistency = 24;
1335+
NoopResponse noop = 25;
1336+
InitPutResponse init_put = 26;
1337+
LeaseInfoResponse lease_info = 30;
1338+
WriteBatchResponse write_batch = 31;
1339+
ExportResponse export = 32;
1340+
ImportResponse import = 34;
1341+
QueryTxnResponse query_txn = 33;
1342+
QueryIntentResponse query_intent = 42;
1343+
AdminScatterResponse admin_scatter = 36;
1344+
AddSSTableResponse add_sstable = 37;
1345+
RecomputeStatsResponse recompute_stats = 39;
1346+
RefreshResponse refresh = 40;
1347+
RefreshRangeResponse refresh_range = 41;
1348+
GetSnapshotForMergeResponse get_snapshot_for_merge = 43;
1349+
}
1350+
reserved 15, 23, 27, 28;
13561351
}
13571352

13581353
// A Header is attached to a BatchRequest, encapsulating routing and auxiliary

pkg/roachpb/api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func TestMustSetInner(t *testing.T) {
108108
if m := req.GetInner().Method(); m != EndTransaction {
109109
t.Fatalf("unexpected request: %s in %+v", m, req)
110110
}
111-
if _, isET := res.GetValue().(*EndTransactionResponse); !isET {
111+
if _, isET := res.GetInner().(*EndTransactionResponse); !isET {
112112
t.Fatalf("unexpected response union: %+v", res)
113113
}
114114
}

0 commit comments

Comments
 (0)