Google claims gRPC with protobuf yields a 10-11x performance improvement over HTTP. I am skeptical of those numbers because really it comes down to the frequency of data parsing into and out of the protobuf format.
At any rate just use JSON with WebSockets. Its stupid simple and still 7-8x faster than HTTP with far less administrative overhead than either HTTP or gRPC.
> JSON with WebSockets. Its stupid simple and still 7-8x faster than HTTP with far less administrative overhead than either HTTP or gRPC.
Everyone doing what you are saying ends up reinventing parts of gRPC, on top of reinventing parts of RabbitMQ. It isn't ever "stupid simple." There are ways to build the things you need in a tightly coupled and elegant way, but what people want is Next.js, that's the coupling they care about, and it doesn't have a message broker (neither does gRPC), and it isn't a proxy (which introduce a bajillion little problems into WebSockets), and WebSockets lifetimes don't correspond to session lifetimes, so you have to reinvent that too, and...
What people? Developers? This is why I will not do that work anymore. Don't assume to know what I want based upon some tool set or tech stack that you find favorable. I hate (HATE HATE) talk of tech stacks, the fantasy of the developer who cannot write original software, who does not measure things, and cannot provide their own test automation. They scream their stupidity for all the world to hear when they start crying about reinventing wheels, or some other empty cliche, instead of just delivering a solution.
What I want is two things:
1. Speed. This is not an assumption of speed. Its the result of various measurements in different execution contexts.
2. Less effort. I want to send a message across a network... and done. In this case you have some form of instruction or data package and then you literally just write that to the socket. That is literally 2 primitive instructions without abstractions like ex: socket.write(JSON.parse(thing));. It is without round trips, without headers, without anything else. You are just done.
The counterpoint to the fact that gRPC and RabbitMQ handle whatever you're writing better than you do is that gRPC and RabbitMQ have immense amounts of complexity that you have to deal with despite the fact that you don't care about it
> At any rate just use JSON with WebSockets. Its stupid simple and still 7-8x faster than HTTP with far less administrative overhead than either HTTP or gRPC.
gRPC is not supposed to be a standard web communication layer.
There are times where you need a binary format and extremely fast serialization/deserialization. Video games are one example where binary formats are greatly preferred over JSON.
But I do agree that people keep trying to shove gRPC (or similar) into things where they aren't needed.
> gRPC is not supposed to be a standard web communication layer.
It kind of is. What do you think WebTransport in HTTP/3 is? It's basically gRPC Next. The only reason gRPC didn't make it as the standard web communication layer is because of one disastrous decision by one Chrome engineer in https://issues.chromium.org/issues/40388906, maybe because he woke up on the wrong side of the bed.
gRPC is meant for backend microservices among other things, and it's still painful for that for the reasons the article describes, all of which could've been fixed or avoided. Internally Google doesn't even use gRPC, they use something similar that has better internal support according to https://news.ycombinator.com/item?id=17690676
I also don't see what'd stop it from being used generally for websites calling the backend API. Even if you don't care about the efficiency (which is likely), it'd be nice to get API definitions built in instead of having to set up OpenAPI.
In 2018, there was some kind of push towards gRPC internally, but it was since abandoned and reversed among the few who actually switched. They still don't use it internally, only externally in some places.
Depending on the use case, it's often better to just copy structs directly, with maybe some care for endianness (little-endian). But at this point, the two most popular platforms, ARM and x86, agree on endianness and most alignment.
There's almost no reason why RPC should not just be
Do all of your platforms have the same word width? The same -fshort-enums settings? Do you know that none of your data structures include pointers? Do all of your systems use the same compiler? Compiler version?
I agree it will usually work, but this becomes an ABI concern, and it's surprisingly common to have ABI mismatches on one platform with the items I've noted above.
I've seen wire protocols that had junk for the alignment buffering in such structs. And I've seen people have to do a whole lot of work to make the wire protocol work on a newer compiler/platform. Also, the whole point of a network protocol being documented is that it decouples the interface (msgs over a network) from the implementation (parsing and acting on msgs). Your v1 server might be able to just copy the read buffer into a struct, but your v2 server won't. And it is possible and desirable to live in a world where you can change your implementation but leave your interface alone (although some parts of the software ecosystem seem to not know this nice fact and implicitly fight against realizing it).
My issue with gRPC is simple, the Go gRPC server code does a lot of allocations. I have a gRPC service where each container does 50-80K/second of incoming calls and I spend a ton of time in GC and in allocating headers for all the msgs. I have a similar REST service where I use fasthttp with 0 allocs (but all the stupidly high number of connections due to the lack of multiplexing thru the connection).
Go's GC wasn't really made with throughput maximization in mind. It's a language that doesn't scale that well to take advantage of beefy nodes and has weak compiler. I suppose the Google's vision for it is to "crank the replica count up". gRPC servers based on top of ASP.NET Core, Java Vert.X and Rust Thruster will provide you with much higher throughput on multi-core nodes.
Ignoring the incompatibilities in word size, endianness, etc, how does a Go or JavaScript or etc program on the receiving end know what `mystruct` is? What if you want to send string, list, map, etc data?
> string, list, map, etc? You have to use an encoding scheme.
Yes, you have to use an encoding scheme like JSON or Protobufs. Dumping memory directly down the pipe as you're suggesting doesn't work.
> As for go / javascript? I think most languages have the ability to inspect a raw buffer.
No language has the ability to read a raw buffer and know what the contents are supposed to mean. There needs to be a protocol for decoding the data, for example JSON or Protobufs.
It's not the pointers themselves so much as what they're typically used for. How would you do dynamic sizing? Imagine sending just a struct of integer arrays this way, you'd have to either know their sizes ahead of time or just be ok with sending a lot of empty bits up to some max size. And recursive structures would be impossible.
You could get around this with a ton of effort around serdes, but it'd amount to reinventing ASN1 or Protobuf.
I can see that in niche situations, particularly if you have a flat structure and uniform hardware. Cap'n Proto is also a way to do zero-parsing, but it has other costs.
Protobuf can typically be about 70-80% smaller than the equivalent JSON payloads. If you care about Network I/O costs (at a large scale), you'd probably want to realize a benefit in cost savings like that.
Additionally, I think people put a lot of trust into JSON parsers across ecosystems "just working", and I think that's something more people should look into (it's worse than you think): https://seriot.ch/projects/parsing_json.html
Let's say I wanted to transfer a movie in MKV container format. Its binary and large at about 4gb. Would I use JSON for that? No. Would I use gRPC/protobuf for that? No.
I would open a dedicated TCP socket and a file system stream. I would then pipe the file system stream to the network socket. No matter what you still have to deal with packet assembly because if you are using TLS you have small packets (max size varies by TLS revision). If you are using WebSockets you have control frames and continuity frames and frame head assembly. Even with that administrative overhead its still a fast and simple approach.
When it comes to application instructions, data from some data store, any kind of primitive data types, and so forth I would continue to use JSON over WebSockets.
Thanks. I've got a little project that needs to use protobufs, and if my DIY approach of sending either application/octet-stream or application/json turns out to be too sketchy, I'll give Connect a try. Only reason I'm not jumping for it is it involves more dependencies.
To get feature parity, you still need an IDL to generate types/classes for multiple languages. You could use JSON Schema for that.
Websockets do not follow a request/reply semantics, so you'd have to write that yourself. I'd prefer not to write my own RPC protocol on top of websockets. That said, I'm sure there are some off the shelf frameworks out there, but do they have the same cross-language compatibility as protobuf + gRPC? I don't think "just use JSON with websockets" is such a simple suggestion.
Of course, gRPC does have some of its own problems. The in-browser support is not great (non-existent without a compatibility layer?) last time I checked.
gRPC was based on a HTTP draft that predated the standardization of HTTP/2, so presumably that statement was said about HTTP/1. HTTP/2 may not have existed at the time it was asserted.
gRPC gives you multiplexing slow request over 1 TCP connection, which reduces all the work and memory related to 1 socket per pending request; gRPC means you don't have to put the string name of a field into the wire, which makes your messages smaller which puts less stress into the memory system and the network, assuming your field values are roughly as large as your field names.
Multiplexing is a HTTP/2 feature. But as gRPC was based on an early HTTP/2 draft, it beat HTTP/2 to the punch. Thus it is likely that HTTP/2 didn't exist at the time the statement was made and therefore HTTP/1 would have been implied.
I dont even care about the performance. I just want some way to version my messages that is backward and forwards compatible and can be delivered in all the languages we use in production. I have tried to consume json over websockets before and its always a hassle with the evolution of the data format. Just version it in protobuf and push the bytes over websocket if you have a choice. Also, load balancing web socket services can be a bitch. Just rolling out our web socket service would disconnect 500k clients in 60 seconds if we didnt make huge amounts of work.
> because really it comes down to the frequency of data parsing into and out of the protobuf format.
Protobuf is intentionally designed to NOT require any parsing at all. Data is serialized over the wire (or stored on disk) in the same format/byte order that it is stored in memory
(Yes, that also means that it's not validated at runtime)
Or are you referencing the code we all invariably write before/after protobuf to translate into a more useful format?
You’re likely thinking of Cap’n’Proto or flatbuffers. Protobuf definitely requires parsing. Zero values can be omitted on the wire so there’s not a fixed layout, meaning you can’t seek to a field. In order to find a fields value, you must traverse the entire message, and decode each tag number since the last tag wins.
Cap'n Proto (developed by Kenton Varda, the former Google engineer who, while at Google, re-wrote/refactored Google's protobuf to later open source it as the library we all know today) is another example of zero-copy (de)serialization.
> Protobuf is intentionally designed to NOT require any parsing at all
This is not true at all. If you have a language-specific class codegen'd by protoc then the in-memory representation of that object is absolutely not the same as the serialized representation. For example:
1. Integer values are varint encoded in the wire format but obviously not in the in-memory format
2. This depends on the language of course but variable length fields are stored inline in the wire format (and length-prefixed) while the in-memory representation will typically use some heap-allocated type (so the in-memory representation has a pointer in that field instead of the data stored inline)
Google claims gRPC with protobuf yields a 10-11x performance improvement over HTTP. I am skeptical of those numbers because really it comes down to the frequency of data parsing into and out of the protobuf format.
At any rate just use JSON with WebSockets. Its stupid simple and still 7-8x faster than HTTP with far less administrative overhead than either HTTP or gRPC.