Protobuf

From Jonathan Gardner's Tech Wiki
Jump to: navigation, search

Intro

When using any new technology, you have to consider that there are "warts". This is another name for the bugs that are features. Really, it's just inconsistencies with what you expect and what is actually there.

Recommendation

Don't use Protobuf. Use JSON.

  • It's not much faster than JSON.
  • It is a lot less popular than JSON.
  • It doesn't solve the versioning problem.
  • You don't want strict-weak typing, you want dynamic-strong typing.
  • You will waste a lot of time with it.
  • You will wish you used JSON.

If JSON doesn't work for you:

  • If speed is truly your issue, try capnproto. (I have 0 experience with capnproto.) Or use something like a C-struct but don't encode and decode it as you send it across the wire. Build it up in place in memory and then just do a memory copy.
  • If versioning is a problem, then solve versioning explicitly.
  • If you want strict-weak typing, you need to seriously ponder the error of your ways. If you can't understand why people love dynamic-string typing, and you think C and Java have all the answers (they just are really hard to find), then you need to actually program in something like Python.
  • If you have lots of time, you should be developing actual features your customers need, not migrating to pointless technologies that offer no benefit.

What is Protobuf?

People get confused on what protobuf really is. Protobuf is a 'serialization protocol'. That is, it converts data structures to a stream of bytes and back again.

Protobuf's claim to fame are:


  • It is written by Google.
  • It is supposed to solve the versioning problem which plagues all protocols.
  • It makes your life a lot easier than, say, JSON. Your projects will get done faster.
  • It gives you strict typing without strong typing.

Unfortunately, in my experience:

  • Something written by a big company is usually lower quality. Have I written an article on why big companies simply can't write good software yet? I need to. Email me to remind me.
  • The versioning problem should be solved explicitly. There is no implicit solution.
  • Your life is not easier, it's a lot harder, and things take longer to write.
  • You really, really don't want strict-weak typing.

My Experience

So, I'm working for a new team in a company, and we're writing new software completely from scratch. I sketch out for my team how the software is going to evolve as our team grows from just a few dozen people to hundreds, and how we are going to integrate with the rest of the company. My experience at Amazon has given me insight into how you can make SOA (Service-oriented Architecture) to work.

Our first service is designed to serve two clients: A python library, which will also publish a command-line interface, and a website, which is going to use some JS framework and then call back into us. So we recommend and agree we're going to do something REST-like.

Now, I have not been quiet about my lack of enthusiasm for REST, but the simple fact is a lot of JS frameworks rely on it, and if you write it a certain way you make the frameworks extremely easy to write. In the future, when we do service-service interactions, particularly as we start to scale up the number of transactions and desire lower latency, I point out that we're going to want something like protobuf (but not protobuf, because I thought others had already recognized its limitations.) Regardless, we have a roadmap in place.

I get the service "working", and I start to document it for the users. I have the Python code automatically generate a Swagger file so that the clients can read the docs online and so that they can just generate a Python client from it. We spend about a week or two going back and forth on the swagger until one of the developers decides that I'm doing it all backwards. I should generate the server from the Swagger file, not the other way around. I try to point out this insanity (the server is already working, I'm just trying to document it for you) but they don't want any of that, so we spend a week debating how the swagger file should look, and I'm making changes to the swagger generation and the service to make it all match up to expectations.

Keep in mind, the clock is ticking, and we're getting closer to our deadline and we're still arguing about the documentation of the service, which I have running, it's just not exactly the way they want it.

Anyway, late Friday night, sometime around 10 PM, my manager emails me that we're going to go with protobuf because it's faster and better documented and supported and we'll get done quicker. And we're going to go with protobuf eventually so might as well be now.

Oh boy. I happen to check my email while I'm on a date with my wife and it ruins my weekend. Literally, I can't think of anything else but how dumb this decision is.

Monday morning, we have a big pow-wow and we revisit this decision. I point out several key things that we seem to be missing:

  • Protobuf isn't really that much faster than REST/JSON/whatever. Our bottleneck is NOT serialization and deserialization, nor is our bottleneck network bandwidth.
  • If we go with Protobuf, someone is going to have to write the REST to protobuf translator for the frontend website.
  • None of us have ever actually developed on protobuf, so factor in "unknown" to our timeline.
  • Protobuf is new (to the internet) and not even used by anyone I can seem to find. Nor is there any support for it or the tools.
  • Oh, by the way, you want me to use grpc and that's only been released a few months ago and it's nowhere to be found on the internet except light introductions with terrible documentation. (And have you read the support forums?)

When the big boss asks what the estimates are, all he gets is a blank face, so I blurt out my estimation "We'll be two weeks later, AT LEAST, plus some factor for the unknowns."

So we decide to move forward, on a project already two weeks late, to make it at least two weeks later.

We're closing in on over two weeks now fighting with protobuf and grpc.

Let that be a lesson on why software projects can and do fail.

End rant!

Warts

Let's cover all the bugs and warts and "features" I could find in my two or three weeks. I'll tryt o keep the list updated.

First: Google will need to find this page. So let me say:

  • Protobuf sucks
  • Here are reasons not to use protobuf
  • Why protobuf doesn't work
  • Should I use protobuf?


Protobuf 2 vs 3

Protobuf 2 and 3 are VERY different protocols. Similar syntax, COMPLETELY different behavior. Short summary: Protobuf 3 is Protobuf 2 without required or optional fields.

"repeated" ?

Protobuf, rather than defining lists as a template or some such, use the "repeated" keyword. I guess this makes sense, but really, to the end user, it's a list.

No repeated maps

Protobuf gives you a map template. You provide the key and value types, which is typical of any strict-weak typed system.

But you can't have a list of maps. :-(

Protobuf's Timestamp Sucks

Newsflash: You CANNOT send dates and times as a number of seconds since Jan. 1, 1970. This doesn't work well in Unix and it doesn't work well anywhere else. A date is a year, month and day. A time is hour minutes and seconds. You may or may not attach timezones (I recommend only supporting UTC.)

Protobuf's Python Libraries REALLY Suck

They were written by someone who heard about descriptors and thought they were a great idea.

Also, they are supported by a crowd who has heard of introspection but doesn't understand why people want it. HINT: If I could automatically generate Protobuf objects from SQLAlchemy objects by matching named fields, I wouldn't have to write so much code anymore.


Can't assign same values you can during construction

IE:

 m = MyMessage(repeated_field=[...]) # OK!
 m.repeated_field = [...]            # NOT OK!

repr() is bad

Typically, `repr()` should either be a string of the form "<class id ...>" or code that can actually be run. I've never heard of repr's having newlines either.

In the case of protobuf's messages, empty messages have a repr of an empty string, and ones without default values have newlines and attributes randomly scattered about.

No Introspection

There is no way that I can find of getting the list of fields for a Message and the Message objects those fields map to. I can get FieldDescriptors, but not the objects themselves.

Generated Code is Messy

Look at the generated code. It's really messy. They could've simplified a whole lot by (a) presenting a parsed object tree of the file, and (b) mapping items in the parsed tree to actual classes. Instead, it's a horrible mess, even full of seemingly random sequences of bytes.

Oh, and if you include the server code (which is necessary to generate a client?) you get code that is marked "Beta".

Python Documentation REALLY bad

You can see how hard they tried to make Python look like Java by reading the generated docs.

Also, note that only part of the code is documented. There is a whole lot hidden behind "private" modules.

I can easily do a better job

I really shouldn't have a problem generating better code than what the Python compiler generates. It's very sophomorish and a little bit of Python code can make it a lot better. When I get some time (and someone pays me) perhaps I will do it.

GRPC SUCKS!!!

GRPC might be a great service, but I wouldn't be able to tell. It's so horribly documented. How are you supposed to do sessions and authentication and such?

And how can I make an HTTP/2 server that speaks like GRPC? No one knows.

Versioning Simply Can't Work

Protobuf encourages you not to use versioning. As long as you follow a straight line for development, and never reuse numbers for fields, you should be fine. But the moment you have a project diverge, you have serious problems.

Also, you can never complete a sequence of changes that makes old versions backwards incompatible.