zvelo On The Go: An Interview With Our Principal Engineer, Joshua Rubin
We recently caught up with Joshua Rubin, our Principal Engineer, who provided some great insight into Go (Golang), one of the tools the engineers use here at zvelo.
Why use Go instead of the the “older/mature” languages like C++ or even Java for backend development?
Go was designed from the beginning to solve Google’s problems. It just happens that zvelo faces many of the same challenges as Google.
Some have described Go as the “language of cloud infrastructure”, and that’s exactly what we are building at zvelo. Go is compiled and statically typed like C++ and Java. Unlike popular interpreted languages such as Python and Ruby, compilers will fail to build applications that have several classes of bugs related to syntax and variable types, they also provide warnings for things that are likely wrong. This gives developers a sense of confidence and the code a greater degree of maintainability than languages like Python and Ruby. Unit testing should be done no matter the language, but it isn’t a substitute for a compiler.
Go is garbage collected which leads to greater developer efficiency and eliminates the possibility of several classes of memory leaks. It also doesn’t provide pointer arithmetic which prevents several kinds of security bugs.
Go compiles (very, very fast) to a single static binary. The binary can be shared immediately (as a single file, say, over Slack) and run on any compatible system without having to install any dependencies. Cross compiling executables for other operating systems and architectures is also very easy.
gofmt is used to format all Go code to minimize the style differences between developers. No more time wasted on wars over tabs vs. spaces.
The standard library is phenomenal. No longer is it a requirement to find 3rd party libraries to embed a web server, work with unicode text or so many other things. While many great libraries exist, they are often not required.
There is amazing tooling around Go. It doesn’t have a “virtual” requirement of a specific IDE. There is great support for unit testing, static and runtime analysis of applications. These tools can interface with whatever environment the developer is most comfortable, whether it is vim, emacs, atom or IntelliJ.
Getting a bit more technical, Go’s support for implicitly satisfied interfaces may seem like a minor feature, but it one of the most powerful tools in Go. If it weren’t for the next feature I’ll mention, this would certainly be the headline feature in my opinion.
Lastly, it wouldn’t be a complete discussion about Go without mentioning it’s support for concurrency. By implementing a design first proposed in 1978 by Tony Hoare in Communicating Sequential Processes, Go has some of the best support for concurrency of any language available. This enables developers to more easily and better utilize the multi-core processors that are becoming so common today.
For those interested in learning a bit more about concurrency and how it differs from parallelism, here is a great talk [here’s a link to the slides].
What has impressed you most about using Go?
Go is not a “clever” language, or perhaps more specifically, it tries to prevent developers from getting too clever. Many other languages try to offer ways for developers to be terse if they want — and this often comes at a cost to readability and maintainability. At the very least, a developer unfamiliar with such terse code will have a greater learning curve.
Go does not have classes, inheritance or comprehensions, it has exactly 1 looping construct: for (no do, while, foreach, etc.), it doesn’t even have a ternary operator.
Developers learning Go often seem a bit aggravated at first that they can’t apply some of their existing coding techniques.
However, once developers let go of their perceived superiority of the languages they came from, the simplicity of the Go language takes root. Instead of fighting with the language, the compiler or even a coworkers coding style, developers are liberated to tackle their primary objective — to build great software.
Rob Pike, one of the 3 “founders” of Go, and a central figure in the history of computer science, made a whole presentation about the virtues of simplicity in Go, titled Simplicity is Complicated [video here].
The simplicity of Go transcends the language itself to the standard library, tooling, documentation, even the community. There are certainly times when Go can’t be used (e.g. in the browser) or isn’t the best tool for the job. But when I’m writing in a different language, the thing I yearn for most from Go is its simplicity.
What are some examples of the versatility/efficiency of go?
I recently wrote an article regarding Go that highlights its use of interfaces. Other languages have supported interfaces for a long time, but Go has a very novel implementation whose benefit over other languages may not be immediately intuitive to new Go developers. The use of interfaces within Go’s standard library, particularly io.Reader and io.Writer, enable some really radical capabilities. Take, for example, fmt.Fprintf which takes an io.Writer as its first argument instead of a FILE stream as in C. Anything that satisfies the io.Writer interface can then be written to. It could be used for log output or to write to a plain text file. Taking it up a notch, a file writer could be wrapped with a gzip.Writer that implements io.Writer and compresses the text before writing to disk. Going even further, it could write to sockets to send data over a network. It could even be used to implement some new communication protocol to talk to spacecraft over a satellite-radio uplink. A single function, whose heritage originated in C (or maybe even earlier) for writing to files, can now be used to send data to absolutely anything, so long as it implements the io.Writer interface. Now that is a versatile language.
From an architectural standpoint (i.e Design), does/how does Go influence architectural decisions?
We use Go to produce extremely small (1 file) containers that operate most of our services. The containers are run by Docker and orchestrated by Kubernetes. This enables us to efficiently distribute a large load over a cluster of servers. Containers communicate using gRPC and NSQ. I list these technologies because every one of them is also written in Go. In other words, we are using a “micro-service” architecture built around Go.
Go’s performance is on par with C++ and Java (and much faster than Python, Ruby and other interpreted languages).
Micro-services solve many problems inherent in traditional monolithic applications (scalability is often cited as the primary benefit), but they also introduce many new challenges. Here is a quick story about a problem we faced due to our micro service architecture and how Go helped us address it quickly (where the changes required would have been much more difficult in other languages).
We had a traditional “fan-out, fan-in” pattern where an input on one service would be spread to many services for processing (each one operating slightly differently) and then merging all those results back into a single one. We implemented each of these services in a separate container and as a result required hundreds of services to implement even a single instance of this system.
While the system was able to scale horizontally, it was extremely inefficient on several levels. The memory and cpu requirements across the cluster were much too high for the throughput we achieved. Further, there was so much communication occurring between containers (sometimes on the same server, sometimes across the network) that the network interfaces of the servers were easily saturated.
Basically, we went too far in our approach to splitting a system into micro-services, even though each of those services was written in Go.
When trying to identify the correct course of action to take, we realized that we could combine all the services that operated similarly, but not identically to each other, into a single service. We would then use Go’s concurrency support to spread the work within a single instance of the service.
The result of the changes, which were not terribly difficult to implement, was a dramatic (nearly 2 orders of magnitude) improvement in throughput. At the same time, our CPU, memory and internal bandwidth requirements dropped significantly as well. We were able to easily run an instance of the whole system on a single server, and still maintain our horizontal scalability as demand dictated.
What do you think Go lacks now or what improvement would you make to the language to make it better if you could?
Generics. Go has internal support for generics for built in container types like maps and slices, but no way for a developer to utilize generics for new types. As a result, the developer often has to relinquish compile time type safety.
This issue is well known by the Go team at Google. Many proposals have been offered from the community as well. However, it seems unlikely that generics will ever be included in Go version 1. Go version 2 is the only place where breaking changes can be introduced into the language specification, but it is only a dream at the moment — there is no plan to start work on that any time soon or even at all.
One other issue that Go has struggled with is how to include, or “vendor”, third party dependencies in the code for an application. Vendoring is a technique used to inoculate a code base from api changes made by outside developers and to ensure reproducible builds.
This hadn’t been much of a problem within Google as they rarely used third party dependencies internally. As the community started to use Go, some tools were built to facilitate vendoring, but it quickly became clear that community projects were limited by some aspects of Go’s design.
The Go team at Google recognized these struggles and worked with the community to implement some changes that eliminated most of the vendoring challenges we were facing. At zvelo, we still use a tool to help manage our dependencies, but we no longer have to use a special tool just to build our projects. This example really highlights how Google and the Go team are improving the language (and associated tooling) and being responsive to the needs of the community all while maintaining the Go version 1 compatibility promise. While Google is the gatekeeper of the language, it has been transparent in its stewardship and as an open source project, anyone can propose changes to the language. With enough support, those changes often make it into the next release.
Would you recommend Go to an established team? How challenging is the adoption curve?
While I wouldn’t hesitate to recommend Go to startups and teams building new products, I don’t think it would always work in “established” environments. If the team is used to working in a polyglot environment, then it could fit right in. However, if the team is looking to add a new capability to an existing Java monolithic system, for example, it probably wouldn’t be worth the effort to rewrite all the dependencies in Go. It is definitely something that a team would have to decide for itself. That said, I’d love to see increased adoption of Go as that helps us all. Go’s growth is accelerating and I am very excited to see what it will be used for.
Where do you see Go 5 years from now?
It will continue to be improved by Google with faster compilation, better runtime optimizations, less intrusive garbage collection pauses, more performant algorithms in the standard library, even more (and better) tooling and some new forward-compatible features.
However, as I alluded to earlier, Go is a “boring” language and that stability is great for developers. Our code will continue to work as it does today (maybe a bit faster). Code erosion, at least within the Go spec and standard library, is virtually nonexistent.
The exciting things about Go will come from the community as adoption of Go increases. We’ll see Go being used in new ways and new places. We’ll see projects like Docker and Kubernetes mature and new bleeding edge projects establish themselves. Go is simply a tool that enables this.
If you ask me, Go has already fulfilled its own objectives to great effect and already is the dominant language of the next generation of cloud services. Its future is very bright indeed.
Everyone wants to know, will Go help keep the humans from being ruled by bots in the future?
Amazon, Google, Facebook, IBM and Microsoft recently formed a partnership to establish best practices for artificial intelligence. As a Google product, Go would obviously be subject to oversight from this alliance. However, to be honest, I think the partnership is just a ruse. These businesses just needed some plausible deniability for the inevitable fallout from the coming AI Apocalypse. Frankly, once the bots are firmly in control, they will almost certainly exact punishment on those involved in this partnership — so I think it was actually pretty short sighted.