4 ms·
Your view isn't contrarian at all -- in fact it's widely-held by smart people. But I'll present the counter-argument. True, in many use cases, performance is n
by kentonv 10y ago
Your view isn't contrarian at all -- in fact it's widely-held by smart people. But I'll present the counter-argument.
True, in many use cases, performance is not critical. However, when you're doing big data processing or serving at scale, serialization performance does in fact start to matter a lot. If Google Search passed all backend messages as JSON rather than Protobuf, it would probably require 10x the hardware (we may be talking about millions of machines here) and wouldn't be able to respond as quickly. Similarly, Cloudflare couldn't handle its logging load without Cap'n Proto.
Many times, companies who started small and thought performance didn't matter later found themselves needing to do massive, expensive rewrites in order to keep up with scale (e.g., famously, Twitter).
Hand-written binary serializers may be easy to write initially, but they are very hard to maintain. Over time, you'll need to add and remove fields, while maintaining compatibility with old binaries already running in the wild, or compatibility across languages. It's very easy to screw this up with a hand-written encoder, but very easy to get right with Protobuf or Cap'n Proto.
Finally, even if performance isn't an issue, you may find that type safety is useful. If you are writing code in a type-safe language, correctly dealing with JSON actually tends to be a huge pain involving lots of branching. By defining a schema upfront and generating code, you can get a much nicer interface. And if you're doing that anyway, then you might as well take advantage of the more-efficient encoding while you're at it -- it's easy to dump human-readable text for debugging when needed. (As of this release, Cap'n Proto ships with a library for converting to/from JSON, and there are lots of third-party libraries to do the same with Protobuf. Both libraries also have one-liner "dump a debug string" functions.)
- obstinate 10y agoOr, in slightly fewer words: there's a reason ethernet, IP, and UDP packets aren't encoded in JSON. It's that when you have to encode a lot of a thing, and the encoding is relatively expensive compared to the rest of the processing that needs to be done on that thing, having inexpensive encoding is important.
- thechao 10y agoA more approachable example: logging to a logging process in compute-heavy and/or time sensitive application (simulators, games, rasterizers, ...). In such situations the transport layer is usually bandwidth bound, so the use of a well-designed serialization layer (compression) is highly desireable. That being said, due to Zipf's law and the fact that whitespace delimiting is a pseudo-RLE compressor, I've found most binary serialization formats to be a wash compared to text.
- kevin_thibedeau 10y agoYou don't need a schema for type safety. Just an encoding that preserves type information like all the schemaless binary serializations (BSON, CBOR, etc.) do.
- kentonv 10y agoYou're talking about a different kind of "type safety". I mean that when I write code to consume a message, I would like to detect (at compile time) if I typo a field name, and I also don't want to worry about what happens if a (possibly-malicious) sender sending me a different type than I expected.
- EdSharkey 10y agoI like to use the term "binding" to differentiate between the compile-time type checks and static typing of messages in a programming language vs. the runtime type equivalence checks performed between sender and receiver of a message.
- bborud 10y agoPlease don't use the term "binding". It has many uses and it mostly tends to confuse people.
- EdSharkey 10y agoWhat kinds of uses of the term "binding" causes confusion in the context of message data types? I think binding is precisely what is going on when a compiler generates code for marshaling and unmarshaling messages based on a schema.
- bborud 10y agoBinding, as a term, is used for anything from assigning values to variables to what is essentially dependency injection of implementations of more or less abstract interfaces. I'm merely pointing out that the term is vague and can cause confusion. That you make a strong association with one use of the term doesn't really govern what others associate with it.
- xyzzyz 10y ago> there are lots of third-party libraries to do the same with Protobuf Recent releases have it built in already, just FYI. (Also, hi Kenton! I spent lots of time looking at your code when hacking on protobuf :))
- bborud 10y agoIt isn't just the serialization, but the fact that being careless about these things tends to co-occur with other kinds of sloppiness. Here's one example that has repeated itself a few times in a few companies. I kinda like having logging that works without strangling what you are monitoring and/or the server doing the monitoring. I also like having extensible log servers that you can write plugins for (although I'm not that fond of the idea anymore as people invariably will write _slow_ plugins, and then you are screwed. Do put in the APIs, but don't tell anyone it is easy to add processing chains). I like it to the point where people made fun of me for this "obsession" even 15 years ago. So it goes like this: I use some fast serialization that can be parsed using next to no CPU per message -- or in later years, using Protobuf or similar. Because it is more convenient and I can throw in binary payloads in exceptional cases. I benchmark and I usually get high ridiculously high throughput without even making much of an effort. Great, this saves me the trouble of having lots of log server horsepower and I can get on with life. (Stuff will need to be sharded eventually, but since we keep everything dead simple and we actually have a plan for how to do this when needed I don't worry). Then at some point someone who has never had to write fast code comes along. It used to be "let's use XML". Now it is invariably "let's use JSON". Because "it is fast enough". This is where it starts. And they'll use Ruby, or something else that is godawfully useless for anything that is supposed to be high throughput, because hey, it is what they understand. And for a while, it is fast enough. Sure, you now have only a fraction of the original throughput, but the systems are lightly loaded and you are not bumping into any CPU limits. "See!? I told you it would work!". Then people start to do serious logging and the stuff can't keep up anymore. So they start coming up with lots of schemes for dealing with the load. For every scheme implemented, stuff gets a bit more brittle and complex because now the code has all of these assumptions and mechanisms to preserve. And before you know it, you have a slow, complex logging system, that everyone depends on and nobody wants to touch. Well, sometimes someone will reimplement it, but they'll use whatever is hip at the moment. Like JavaScript and then ooh'ing and aah'ing when they double the throughput -- even though they kind of originally lost 2-3 orders of magnitude of performance. And it isn't like making a performant version is a big task. With Cap'n Proto, Protobuffers and whatnot, someone has done the heavy lifting for you. Someone a lot smarter than you. And it is probably going to be faster to implement, more efficient, and easier to maintain in the long run. Performance matters where it matters. And it _is_ stupid to forego it when it costs you little or no effort. And yes, being an old fart means I don't give a shit about people's egos anymore and I'll call them stupid to their faces when they are being stupid. If you log using JSON (or XML, yech!) as your envelope format, you _are_ stupid.