gRPC Go for Professionals by Clément Jean

gRPC Go for Professionals by Clément Jean

Author:Clément Jean
Language: eng
Format: epub
Publisher: Packt Publishing Pvt Ltd
Published: 2023-07-07T00:00:00+00:00


Splitting messages

In general, we prefer to split messages to keep smaller objects and have fewer fields, and thus smaller tags. This lets us arrange information into entities and understand what the given information is representing. Our Task message is an example of that. It groups information and we can reuse that entity in, for example, UpdateTasksRequest to accept a fully featured Task as a request.

However, while it is interesting to be able to separate information into entities, this does not come for free. Your payload gets affected by the use of a user-defined type. Let us see an example of splitting a message and how it can affect the size of serialized data. This example shows that there is a size overhead when splitting messages. To show that, we are going to create a message that contains a name and a wrapper around a name. This first time we check the size, we will only set the string, and the second time we will only set the wrapper. Here is what I mean by such a message:

message ComplexName { string name = 1; } message Split { string name = 1; ComplexName complex_name = 2; }

Right now, let us not worry about the usefulness of this example. We are just trying to prove that splitting a message has an overhead.

Then, we will write a main function that simply sets the value to name first, then calculates the size and prints it. And then, we will clear the name, set the ComplexName.name field, calculate the size, and print it. If there is an overhead, the sizes should be different. In helpers/split.go, we have the following:

package main import ( "fmt" "log" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" pb "github.com/PacktPublishing/gRPC-Go-for-Professionals /helpers/proto" ) func serializedSize[M protoreflect.ProtoMessage](msg M) int { out, err := proto.Marshal(msg) if err != nil { log.Fatal(err) } return len(out) } func main() { s := &pb.Split{Name: "Packt"} sz := serializedSize(s) fmt.Printf("With Name: %d
", sz) s.Name = "" s.ComplexName = &pb.ComplexName{Name: "Packt"} sz = serializedSize(s) fmt.Printf("With ComplexName: %d
", sz) }

If we run that, we should get:

$ go run split.go With Name: 7 With ComplexName: 9

Effectively, these two sizes are different. But what is the difference? The difference is that user-defined types are serialized as length-delimited types. In our case, the simple name would be serialized as 0a 05 50 61 63 6b 74. 0a is the wire type for Length-Delimited + tag 1 and the rest are the characters. But for the complex type, we have 12 07 0a 05 50 61 63 6b 74. We recognize the last 7 bytes but there are two more in front. 12 is the Length-Delimited wire type + tag 2 and 07 is the length of the following bytes.

In conclusion, we once again have a trade-off. The more tags we have in messages, the more possibility there is for us to incur costs in terms of payload size. However, the more we try to split messages to keep the tags small, the more we will also incur costs because the data will be serialized as length-delimited data.



Download



Copyright Disclaimer:
This site does not store any files on its server. We only index and link to content provided by other sites. Please contact the content providers to delete copyright contents if any and email us, we'll remove relevant links or contents immediately.