mirror of https://github.com/grpc/grpc.io.git
More blog post cleanup
Signed-off-by: lucperkins <lucperkins@gmail.com>
This commit is contained in:
parent
6ae6fc886f
commit
59fc928363
|
|
@ -1,6 +1,5 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
date: {{ dateFormat "2006-01-02" .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
attribution: Originally written by Dale Hopkins with additional content by Lisa Carey
|
||||
and others at Google.
|
||||
author: Dale Hopkins
|
||||
company: Vendasta
|
||||
company-link: https://vendasta.com
|
||||
date: "2016-08-29T00:00:00Z"
|
||||
published: true
|
||||
thumbnail: ../img/vend-icon.png?raw=true
|
||||
title: Why we have decided to move our APIs to gRPC
|
||||
url: blog/vendastagrpc
|
||||
---
|
||||
|
||||
Our guest post today comes from Dale Hopkins, CTO of [Vendasta](https://vendasta.com/).
|
||||
|
||||
Vendasta started out 8 years ago as a point solution provider of products for small business. From the beginning we partnered with media companies and agencies who have armies of salespeople and existing relationships with those businesses to sell our software. It is estimated that over 30 million small businesses exist in the United States alone, so scalability of our SaaS solution was considered one of our top concerns from the beginning and it was the reason we started with [Google App Engine](https://cloud.google.com/appengine/) and Datastore. This solution worked really well for us as our system scaled from hundreds to hundreds of thousands of end users. We also scaled our offering from a point solution to an entire platform with multiple products and the tools for partners to manage their sales of those products during this time.
|
||||
|
||||
All throughout this journey Python GAE served our needs well. We exposed a number of APIs via HTTP + JSON for our partners to automate tasks and integrate their other systems with our products and platform. However, in 2016 we introduced the Vendasta Marketplace. This marked a major change to our offering, which depended heavily on having 3rd party vendors use our APIs to deliver their own products in our platform. This was a major change because our public APIs provide an upper-bound on 3rd-party applications, and made us realize that we really needed to make APIs that were amazing, not just good.
|
||||
|
||||
The first optimization that we started with was to use the Go programming language to build endpoints that handled higher throughput with lower latency than we could get with Python. On some APIs this made an incredible difference: we saw 50th percentile response times to drop from 1200 ms to 4 ms, and even more spectacularly 99th percentile response times drop from 30,000 ms to 12 ms! On other APIs we saw a much smaller, but still significant difference.
|
||||
|
||||
The second optimization we used was to replicate large portions of our Datastore data into ElasticSearch. ElasticSearch is a fundamentally different storage technology to Datastore, and is not a managed service, so it was a big leap for us. But this change allowed us to migrate almost all of our overnight batch-processing APIs to real-time APIs. We had tried BigQuery, but it's query processing times meant that we couldn't display things in real time. We had tried cloudSQL, but there was too much data for it to easily scale. We had tried the appengine Search API, but it has limitations with result sets over 10,000. We instead scaled up our ElasticSearch cluster using [Google Container Engine](https://cloud.google.com/container-engine/) and with it's powerful aggregations and facet processing our needs were easily met. So with these first two solutions in place, we had made meaningful changes to the performance of our APIs.
|
||||
|
||||
The last optimization we made was to move our APIs to [gRPC](/). This change was much more extensive than the others as it affected our clients. Like ElasticSearch, it represents a fundamentally different model with differing performance characteristics, but unlike ElasticSearch we found it to be a true superset: all of our usage scenarios were impacted positively by it.
|
||||
|
||||
|
||||
The first benefit we saw from gRPC was the ability to move from publishing APIs and asking developers to integrate with them, to releasing SDKs and asking developers to copy-paste example code written in their language. This represents a really big benefit for people looking to integrate with our products, while not requiring us to hand-roll entire SDKs in the 5+ languages our partners and vendors use. It is important to note that we still write light wrappers over the generated gRPC SDKs to make them package-manager friendly, and to provide wrappers over the generated protobuf structures.
|
||||
|
||||
The second benefit we saw from gRPC was the ability to break free from the call-and-response architecture necessitated by HTTP + JSON. gRPC is built on top of HTTP/2, which allows for client-side and/or server-side streaming. In our use cases, this means we can lower the time to first display by streaming results as they become ready on the server (server-side streaming). We have also been investigating the potential to offer very flexible create endpoints that easily support bulk ingestion with bi-directional streaming, this would mean we would allow the client to asynchronously stream results, while the server would stream back statuses allowing for easy checkpoint operations while not slowing upload speeds to wait for confirmations. We feel that we are just starting to see the benefits from this feature as it opens up a totally new model for client-server interactions that just wasn't possible with HTTP.
|
||||
|
||||
The third benefit was the switch from JSON to protocol buffers, which works very well with gRPC. This improves serialization and deserialization times; which is very significant to some of our APIs, but appreciated on all of them. The more important benefit comes from the explicit format specification of proto, meaning that clients receive typed objects rather than free-form JSON. Because of this, our clients can reap the benefits of auto-completion in their IDEs, type-safety if their language supports it, and enforced compatibility between clients and servers with differing versions.
|
||||
|
||||
The final benefit of gRPC was our ability to quickly spec endpoints. The proto format for both data and service definition greatly simplifies defining new endpoints and finally allows the succinct definition of endpoint contracts. This means we are much better able to communicate endpoint specifications between our development teams. gRPC means that for the first time at our company we are able to simultaneously develop the client and the server side of our APIs! This means our latency to produce new APIs with the accompanying SDKs has dropped dramatically. Combined with code generation, it allows us to truly develop clients and servers in parallel.
|
||||
|
||||
Our experience with gRPC has been positive, even though it does not eliminate the difficulty of providing endpoints to partners and vendors, and address all of our performance issues. However, it does make improvements to our endpoint performance, integration with those endpoints, and even in delivery of SDKs.
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
---
|
||||
title: A short introduction to Channelz
|
||||
author: Yuxuan Li
|
||||
author-link: https://github.com/lyuxuan
|
||||
date: "2018-09-05T00:00:00Z"
|
||||
published: true
|
||||
title: A short introduction to Channelz
|
||||
url: blog/a_short_introduction_to_channelz
|
||||
date: 2018-09-05
|
||||
---
|
||||
|
||||
Channelz is a tool that provides comprehensive runtime info about connections at
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
---
|
||||
title: Building gRPC services with bazel and rules_protobuf
|
||||
attribution: Originally written by Paul Johnston.
|
||||
author: Paul Cody Johnston
|
||||
company: PubRef.org
|
||||
company-link: https://pubref.org
|
||||
date: "2016-10-13T00:00:00Z"
|
||||
published: true
|
||||
date: 2016-10-13
|
||||
thumbnail: https://avatars3.githubusercontent.com/u/10408150?v=3&s=200
|
||||
title: Building gRPC services with bazel and rules_protobuf
|
||||
url: blog/bazel_rules_protobuf
|
||||
---
|
||||
|
||||
[gRPC](/) makes it easier to build high-performance
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: gRPC releases Beta, opening door for use in production environments
|
||||
attribution: Mugur Marculescu, gRPC
|
||||
date: "2015-10-26"
|
||||
date: 2015-10-26
|
||||
aliases: ["blog/beta_release"]
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
---
|
||||
title: 2017-08-17 Community Meeting Update
|
||||
author: Jaye Pitzeruse
|
||||
company: Indeed
|
||||
company-link: https://www.indeed.com
|
||||
date: "2017-08-17T00:00:00Z"
|
||||
published: true
|
||||
title: 2017-08-17 Community Meeting Update
|
||||
date: 2017-08-17
|
||||
---
|
||||
|
||||
**Next Community Meeting:** Thursday, August 31, 2017 11am Pacific Time (US and Canada)
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
---
|
||||
title: gRPC with REST and Open APIs
|
||||
attribution: Originally written by Brandon Phillips with additional content by Lisa
|
||||
Carey and others at Google
|
||||
author: Brandon Phillips
|
||||
company: CoreOS
|
||||
company-link: https://coreos.com
|
||||
date: "2016-05-09"
|
||||
date: 2016-05-09
|
||||
thumbnail: https://avatars2.githubusercontent.com/u/3730757?v=3&s=200
|
||||
title: gRPC with REST and Open APIs
|
||||
---
|
||||
|
||||
Our guest post today comes from Brandon Phillips of [CoreOS](https://coreos.com/). CoreOS builds open source projects and products for Linux Containers. Their flagship product for consensus and discovery [etcd](https://coreos.com/etcd/) and their container engine [rkt](https://coreos.com/rkt/) are early adopters of gRPC.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
---
|
||||
title: gRPC and Deadlines
|
||||
author: Gráinne Sheerin, Google SRE
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2018-02-26T00:00:00Z"
|
||||
published: true
|
||||
title: gRPC and Deadlines
|
||||
url: blog/deadlines
|
||||
date: 2018-02-26
|
||||
---
|
||||
|
||||
**TL;DR Always set a deadline**. This post explains why we recommend being deliberate about setting deadlines, with useful code snippets to show you how.
|
||||
**TL;DR: Always set a deadline**. This post explains why we recommend being deliberate about setting deadlines, with useful code snippets to show you how.
|
||||
|
||||
<!--more-->
|
||||
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
---
|
||||
title: gRPC Project is now 1.0 and ready for production deployments
|
||||
attribution: Originally written by Varun Talwar with additional content by Kailash
|
||||
Sethuraman and others at Google.
|
||||
author: Varun Talwar
|
||||
company: Google
|
||||
company-link: https://cloud.google.com
|
||||
date: "2016-08-23T00:00:00Z"
|
||||
published: true
|
||||
date: 2016-08-23
|
||||
thumbnail: ../img/gcp-icon.png?raw=true
|
||||
title: gRPC Project is now 1.0 and ready for production deployments
|
||||
url: blog/gablogpost
|
||||
aliases: ["blog/gablogpost"]
|
||||
---
|
||||
|
||||
Today, the gRPC project has reached a significant milestone with its [1.0 release](https://github.com/grpc/grpc/releases).
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
---
|
||||
title: Gracefully clean up in gRPC JUnit tests
|
||||
author: Dapeng Zhang
|
||||
author-link: https://github.com/dapengzhang0
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2018-06-26T00:00:00Z"
|
||||
published: true
|
||||
title: Gracefully clean up in gRPC JUnit tests
|
||||
url: blog/gracefully_clean_up_in_grpc_junit_tests
|
||||
date: 2018-06-26
|
||||
---
|
||||
|
||||
It is best practice to always clean up gRPC resources such as client channels, servers, and previously attached Contexts whenever they are no longer needed.
|
||||
|
|
@ -112,5 +110,5 @@ public class MyTest {
|
|||
|
||||
Now with [`GrpcCleanupRule`][GrpcCleanupRule] you don't need to worry about graceful shutdown of gRPC servers and channels in JUnit test. So try it out and clean up in your tests!
|
||||
|
||||
[GrpcServerRule]:https://github.com/grpc/grpc-java/blob/v1.1.x/testing/src/main/java/io/grpc/testing/GrpcServerRule.java
|
||||
[GrpcCleanupRule]:https://github.com/grpc/grpc-java/blob/v1.13.x/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java
|
||||
[GrpcServerRule]: https://github.com/grpc/grpc-java/blob/v1.1.x/testing/src/main/java/io/grpc/testing/GrpcServerRule.java
|
||||
[GrpcCleanupRule]: https://github.com/grpc/grpc-java/blob/v1.13.x/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
title: "gRPC Meets .NET SDK And Visual Studio: Automatic Codegen On Build"
|
||||
author: Kirill 'kkm' Katsnelson
|
||||
author-link: https://github.com/kkm000
|
||||
date: "2018-12-18"
|
||||
date: 2018-12-18
|
||||
---
|
||||
|
||||
As part of Microsoft's move towards its cross-platform .NET offering, they have
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: Announcing out-of-the-box support for gRPC in the Flatbuffers serialization library
|
||||
author: Wouter van Oortmerssen
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2017-08-17T00:00:00Z"
|
||||
published: true
|
||||
title: Announcing out of the box support for gRPC in the Flatbuffers serialization
|
||||
library.
|
||||
url: blog/flatbuffers
|
||||
date: 2017-08-17
|
||||
---
|
||||
|
||||
The recent release of Flatbuffers [version 1.7](https://github.com/google/flatbuffers/releases) introduced truly zero-copy support for gRPC out of the box.
|
||||
|
|
@ -22,9 +19,11 @@ This is currently, fully supported in the C++ implementation of FlatBuffers, wit
|
|||
|
||||
|
||||
## Example Usage
|
||||
|
||||
Let's look at an example of how this works.
|
||||
|
||||
### Use Flatbuffers as an IDL
|
||||
|
||||
Start with an `.fbs` schema (similar to .proto, if you are familiar with protocol buffers) that declares an RPC service:
|
||||
|
||||
```proto
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
---
|
||||
title: gRPC-Go Engineering Practices
|
||||
author: Doug Fawley, gRPC-Go TL
|
||||
company: Google
|
||||
company-link: google.com
|
||||
date: "2018-01-22T00:00:00Z"
|
||||
published: true
|
||||
title: 2018-01-19 gRPC-Go Engineering Practices
|
||||
date: 2018-01-22
|
||||
---
|
||||
|
||||
It's the start of the new year, and almost the end of my first full year on the
|
||||
|
|
@ -1,23 +1,20 @@
|
|||
---
|
||||
title: gRPC-Go performance Improvements
|
||||
author: Mahak Mukhi
|
||||
company: Google
|
||||
company-link: google.com
|
||||
date: "2017-08-22T00:00:00Z"
|
||||
published: true
|
||||
title: 2017-08-22 gRPC-Go performance Improvements
|
||||
date: 2017-08-22
|
||||
---
|
||||
<p>
|
||||
<span style="margin-bottom:5%">For past few months we've been working on improving gRPC-Go performance. This includes improving network utilization, optimizing CPU usage and memory allocations. Most of our recent effort has been focused around revamping gRPC-Go flow control. After several optimizations and new features we've been able to improve quite significantly, especially on high-latency networks. We expect users that are working with high-latency networks and large messages to see an order of magnitude performance gain.
|
||||
|
||||
For past few months we've been working on improving gRPC-Go performance. This includes improving network utilization, optimizing CPU usage and memory allocations. Most of our recent effort has been focused around revamping gRPC-Go flow control. After several optimizations and new features we've been able to improve quite significantly, especially on high-latency networks. We expect users that are working with high-latency networks and large messages to see an order of magnitude performance gain.
|
||||
Benchmark results at the end.
|
||||
|
||||
This blog summarizes the work we have done so far (in chronological order) to improve performance and lays out our near-future plans.</style>
|
||||
</p><br>
|
||||
This blog summarizes the work we have done so far (in chronological order) to improve performance and lays out our near-future plans.
|
||||
<!--more-->
|
||||
|
||||
### Recently Implemented Optimizations
|
||||
## Recently Implemented Optimizations
|
||||
|
||||
|
||||
###### Expanding stream window on receiving large messages
|
||||
### Expanding stream window on receiving large messages
|
||||
|
||||
[Code link](https://github.com/grpc/grpc-go/pull/1248)
|
||||
|
||||
|
|
@ -26,7 +23,7 @@ This is an optimization used by gRPC-C to achieve performance benefits for large
|
|||
This optimization alone provided a 10x improvement for large messages on high-latency networks.
|
||||
|
||||
|
||||
###### Decoupling application reads from connection flow control
|
||||
### Decoupling application reads from connection flow control
|
||||
|
||||
[Code link](https://github.com/grpc/grpc-go/pull/1265)
|
||||
|
||||
|
|
@ -45,14 +42,13 @@ The need for connection-level flow control:
|
|||
It is true that stream-level flow control is sufficient to throttle a sender from sending too much data. But not having connection-level flow control (or using an unlimited connection-level window) makes it so that when things get slower on a stream, opening a new one will appear to make things faster. This will only take one so far since the number of streams are limited. However, having a connection-level flow control window set to the Bandwidth Delay Product (BDP) of the network puts an upper-bound on how much performance can realistically be squeezed out of the network.
|
||||
|
||||
|
||||
###### Piggyback window updates
|
||||
### Piggyback window updates
|
||||
|
||||
[Code link](https://github.com/grpc/grpc-go/pull/1273)
|
||||
|
||||
Sending a window update itself has a cost associated to it; a flush operation is necessary, which results in a syscall. Syscalls are blocking and slow. Therefore, when sending out a stream-level window update, it makes sense to also check if a connection-level window update can be sent using the same flush syscall.
|
||||
|
||||
|
||||
###### BDP estimation and dynamic flow control window
|
||||
### BDP estimation and dynamic flow control window
|
||||
|
||||
[Code link](https://github.com/grpc/grpc-go/pull/1310)
|
||||
|
||||
|
|
@ -72,13 +68,11 @@ Given that we're always bound by the flow control of TCP which for most cases is
|
|||
|
||||
BDP estimation and dynamically adjusting window sizes is turned-on by default and can be turned off by setting values manually for connection and/or stream window sizes.
|
||||
|
||||
|
||||
###### Near-future efforts
|
||||
### Near-future efforts
|
||||
|
||||
We are now looking into improving our throughput by better CPU utilization, the following efforts are in-line with that.
|
||||
|
||||
|
||||
###### Reducing flush syscalls
|
||||
### Reducing flush syscalls
|
||||
|
||||
We noticed a bug in our transport layer which causes us to make a flush syscall for every data frame we write, even if the same goroutine has more data to send. We can batch a lot of these writes to use only one flush. This in fact will not be a big change to the code itself.
|
||||
|
||||
|
|
@ -86,15 +80,11 @@ In our efforts to get rid of unnecessary flushes we recently combined the header
|
|||
|
||||
Another related idea proposed by one of our users @petermattic in [this](https://github.com/grpc/grpc-go/pull/1373) PR was to combine a server response to a unary RPC into one flush. We are currently looking into that as well.
|
||||
|
||||
|
||||
###### Reducing memory allocation
|
||||
### Reducing memory allocation
|
||||
|
||||
For every data frame read from the wire a new memory allocation takes place. The same holds true at the gRPC layer for every new message for decompressing and decoding. These allocations result in excessive garbage collection cycles, which are expensive. Reusing memory buffers can reduce this GC pressure, and we are prototyping approaches to do so. As requests need buffers of differing sizes, one approach would be to maintain individual memory pools of fixed sizes (powers of two). So now when reading x bytes from the wire we can find the nearest power of 2 greater than x and reuse a buffer from our cache if available or allocate a new one if need be. We will be using golang sync Pools so we don't have to worry about garbage collection. However, we will need to run sufficient tests before committing to this.
|
||||
|
||||
|
||||
|
||||
###### Results:
|
||||
|
||||
### Results
|
||||
|
||||
* Benchmark on a real network:
|
||||
|
||||
|
|
@ -104,18 +94,14 @@ For every data frame read from the wire a new memory allocation takes place. The
|
|||
* [Code link](https://github.com/grpc/grpc-go/compare/master...MakMukhi:http_greeter)
|
||||
|
||||
|
||||
<table>
|
||||
<tr><th>Message Size </th><th>GRPC </th><th>HTTP 1.1</th></tr>
|
||||
|
||||
<tr><td>1 KB</td><td>~152 ms</td><td>~152 ms</td></tr>
|
||||
<tr><td>10 KB</td><td>~152 ms</td><td>~152 ms</td></tr>
|
||||
<tr><td>10 KB</td><td>~152 ms</td><td>~152 ms</td></tr>
|
||||
<tr><td>1 MB</td><td>~152 ms</td><td>~152 ms</td></tr>
|
||||
<tr><td>10 MB</td><td>~622 ms</td><td>~630 ms</td></tr>
|
||||
<tr><td>100 MB</td><td>~5 sec</td><td>~5 sec</td></tr>
|
||||
|
||||
|
||||
</table>
|
||||
Message size | gRPC | HTTP 1.1
|
||||
:------------|:-----|:--------
|
||||
1 KB | ~152 ms | ~152 ms
|
||||
10 KB | ~152 ms | ~152 ms
|
||||
10 KB | ~152 ms | ~152 ms
|
||||
1 MB | ~152 ms | ~152 ms
|
||||
10 MB | ~622 ms | ~630 ms
|
||||
100 MB | ~5 sec | ~5 sec
|
||||
|
||||
* Benchmark on simulated network:
|
||||
* Server and client were launched on the same machine and different network latencies were simulated.
|
||||
|
|
@ -124,9 +110,9 @@ For every data frame read from the wire a new memory allocation takes place. The
|
|||
* Following tables show time taken by first 10 RPCs.
|
||||
* [Code link](https://github.com/grpc/grpc-go/compare/master...MakMukhi:grpc_vs_http)
|
||||
|
||||
##### No Latency Network
|
||||
##### No-latency Network
|
||||
|
||||
| GRPC | HTTP 2.0 | HTTP 1.1 |
|
||||
| gRPC | HTTP 2.0 | HTTP 1.1 |
|
||||
| --------------|:-------------:|-----------:|
|
||||
|5.097809ms|16.107461ms|18.298959ms |
|
||||
|4.46083ms|4.301808ms|7.715456ms
|
||||
|
|
@ -141,7 +127,7 @@ For every data frame read from the wire a new memory allocation takes place. The
|
|||
|
||||
##### Network with RTT of 16ms
|
||||
|
||||
| GRPC | HTTP 2.0 | HTTP 1.1 |
|
||||
| gRPC | HTTP 2.0 | HTTP 1.1 |
|
||||
| --------------|:-------------:|-----------:|
|
||||
|118.837625ms|84.453913ms|58.858109ms
|
||||
|36.801006ms|22.476308ms|20.877585ms
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
---
|
||||
title: gRPC Load Balancing
|
||||
author: makdharma
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2017-06-15T00:00:00Z"
|
||||
published: true
|
||||
title: gRPC Load Balancing
|
||||
url: blog/loadbalancing
|
||||
date: 2017-06-15
|
||||
---
|
||||
|
||||
This post describes various load balancing scenarios seen when deploying gRPC. If you use [gRPC](/) with multiple backends, this document is for you.
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
title: .NET Core ❤ gRPC
|
||||
author: Sourabh Shirhatti
|
||||
author-link: https://twitter.com/sshirhatti
|
||||
date: "2019-09-23"
|
||||
date: 2019-09-23
|
||||
---
|
||||
|
||||
_This is a guest post by [Sourabh Shirhatti](https://twitter.com/sshirhatti), a Program Manager on the .NET team at Microsoft._
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
---
|
||||
author: Jean de Klerk
|
||||
author-link: https://github.com/jadekler
|
||||
date: "2018-08-20T00:00:00Z"
|
||||
published: true
|
||||
title: gRPC on HTTP/2 Engineering a Robust, High Performance Protocol
|
||||
url: blog/grpc_on_http2
|
||||
author: Jean de Klerk
|
||||
author-link: https://github.com/jadekler
|
||||
date: 2018-08-20
|
||||
---
|
||||
|
||||
In a [previous article](/blog/http2_smarter_at_scale), we explored how HTTP/2 dramatically increases network efficiency and enables real-time communication by providing a framework for long-lived connections. In this article, we’ll look at how gRPC builds on HTTP/2’s long-lived connections to create a performant, robust platform for inter-service communication. We will explore the relationship between gRPC and HTTP/2, how gRPC manages HTTP/2 connections, and how gRPC uses HTTP/2 to keep connections alive, healthy, and utilized.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## gRPC Semantics
|
||||
|
||||
To begin, let’s dive into how gRPC concepts relate to HTTP/2 concepts. gRPC introduces three new concepts: *channels* [1], *remote procedure calls* (RPCs), and *messages*. The relationship between the three is simple: each channel may have many RPCs while each RPC may have many messages.
|
||||
|
||||
<img src="/img/channels_mapping_2.png" title="Channel Mapping" alt="Channel Mapping" style="max-width: 800px">
|
||||

|
||||
|
||||
Let’s take a look at how gRPC semantics relate to HTTP/2:
|
||||
|
||||
<img src="/img/grpc_on_http2_mapping_2.png" title="gRPC on HTTP/2" alt="gRPC on HTTP/2" style="max-width: 800px">
|
||||

|
||||
|
||||
Channels are a key concept in gRPC. Streams in HTTP/2 enable multiple concurrent conversations on a single connection; channels extend this concept by enabling multiple streams over multiple concurrent connections. On the surface, channels provide an easy interface for users to send messages into; underneath the hood, though, an incredible amount of engineering goes into keeping these connections alive, healthy, and utilized.
|
||||
|
||||
|
|
@ -28,9 +27,9 @@ Channels represent virtual connections to an endpoint, which in reality may be b
|
|||
|
||||
In order to keep connections alive, healthy, and utilized, gRPC utilizes a number of components, foremost among them *name resolvers* and *load balancers*. The resolver turns names into addresses and then hands these addresses to the load balancer. The load balancer is in charge of creating connections from these addresses and load balancing RPCs between connections.
|
||||
|
||||
<img src="/img/dns_to_load_balancer_mapping_3.png" title="Resolvers and Load Balancers" alt="Resolvers and Load Balancers" style="max-width: 800px">
|
||||

|
||||
|
||||
<img src="/img/load_balance_round_robins_2.png" alt="Round Robin Load Balancer" style="max-width: 800px">
|
||||

|
||||
|
||||
A DNS resolver, for example, might resolve some host name to 13 IP addresses, and then a RoundRobin balancer might create 13 connections - one to each address - and round robin RPCs across each connection. A simpler balancer might simply create a connection to the first address. Alternatively, a user who wants multiple connections but knows that the host name will only resolve to one address might have their balancer create connections against each address 10 times to ensure that multiple connections are used.
|
||||
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
---
|
||||
title: Visualizing gRPC Language Stacks
|
||||
author: Carl Mastrangelo
|
||||
author-link: https://carlmastrangelo.com/
|
||||
date: "2018-12-11T00:00:00Z"
|
||||
published: true
|
||||
title: Visualizing gRPC Language Stacks
|
||||
url: blog/grpc-stacks
|
||||
date: 2018-12-11
|
||||
---
|
||||
|
||||
Here is a high level overview of the gRPC Stacks. Each of the **10** default languages supported by gRPC has multiple layers, allowing you to customize what pieces you want in your application.
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
---
|
||||
author: Luc Perkins - CNCF, Stanley Cheung - Google, Kailash Sethuraman - Google
|
||||
date: "2018-10-23T00:00:00Z"
|
||||
published: true
|
||||
title: gRPC-Web is Generally Available
|
||||
url: blog/grpc-web-ga
|
||||
author: Luc Perkins - CNCF, Stanley Cheung - Google, Kailash Sethuraman - Google
|
||||
date: 2018-10-23
|
||||
---
|
||||
|
||||
We are excited to announce the GA release of
|
||||
|
|
@ -35,7 +33,6 @@ From a broader architectural perspective, gRPC-Web enables end-to-end gRPC. The
|
|||
|
||||
<img src="/img/grpc-web-arch.png" style="max-width: 947px">
|
||||
|
||||
|
||||
<p style="text-align: center"> Figure 1.
|
||||
gRPC with gRPC-Web (left) and gRPC with REST (right)</p>
|
||||
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
---
|
||||
title: gRPC + JSON
|
||||
author: Carl Mastrangelo
|
||||
author-link: https://carlmastrangelo.com
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2018-08-15T00:00:00Z"
|
||||
published: true
|
||||
title: gRPC + JSON
|
||||
url: blog/grpc-with-json
|
||||
date: 2018-08-15
|
||||
---
|
||||
|
||||
So you've bought into this whole RPC thing and want to try it out, but aren't quite sure about Protocol Buffers. Your existing code encodes your own objects, or perhaps you have code that needs a particular encoding. What to do?
|
||||
|
|
@ -227,4 +225,3 @@ Almost **10x** faster than before! We can still take advantage of gRPC's effici
|
|||
gRPC lets you use encoders other than Protobuf. It has no dependency on Protobuf and was specially made to work with a wide variety of environments. We can see that with a little extra boilerplate, we can use any encoder we want. While this post only covered JSON, gRPC is compatible with Thrift, Avro, Flatbuffers, Cap’n Proto, and even raw bytes! gRPC lets you be in control of how your data is handled. (We still recommend Protobuf though due to strong backwards compatibility, type checking, and performance it gives you.)
|
||||
|
||||
All the code is avaialable on [GitHub](https://github.com/carl-mastrangelo/kvstore/tree/04-gson-marshaller) if you would like to see a fully working implementation.
|
||||
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
---
|
||||
title: Dear gRPC
|
||||
author: April Nassi
|
||||
author-link: https://www.thisisnotapril.com/
|
||||
date: "2019-03-08T00:00:00Z"
|
||||
published: true
|
||||
title: Dear gRPC
|
||||
url: blog/hello-pancakes
|
||||
date: 2019-03-08
|
||||
---
|
||||
|
||||
Dear gRPC,
|
||||
|
|
@ -15,7 +13,7 @@ You're 4 now and that's a big milestone! You're part of so much amazing technolo
|
|||
|
||||
We're proud of you, gRPC, and we're going to make this up to you. For starters - we got you a puppy! He's an adorable **G**olden **R**etriever and his name is **P**an**C**akes. He loves to run back and forth with toys, packets, or messages. He's super active and no matter how much we train him, we just can't get him to REST. PanCakes is going to be your best friend, and ambassador.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/grpc/grpc-community/master/PanCakes/Pancakes_Birthday.png" alt="gRPC Mascot PanCakes" style="max-width: 547px">
|
||||

|
||||
|
||||
Even though it's a bit late, we still want to throw you a party, gRPC. Our friends at CNCF have planned a [big event](https://events.linuxfoundation.org/events/grpconf-2019/) for you on March 21, and there's going to be lots of people there! They'll be sharing stories about the cool things they've built, and meeting new people. It's an entire day all about you, and everyone there is going to learn so much. There will be other puppies who can play with PanCakes! Some of the amazing dogs from [Canine Companions for Independence](http://www.cci.org/) will be there to greet conference attendees and share how they help their humans live a more independent life.
|
||||
|
||||
|
|
@ -23,4 +21,4 @@ We are so excited to see what this year holds for you, gRPC!
|
|||
|
||||
~ gRPC Maintainers
|
||||
|
||||
<img src="https://raw.githubusercontent.com/grpc/grpc-community/master/PanCakes/Pancakes_Birthday_4.png" alt="gRPC Mascot PanCakes" style="max-width: 547px">
|
||||

|
||||
|
|
@ -1,12 +1,10 @@
|
|||
---
|
||||
title: gRPC in Helm
|
||||
author: Brian Hardock
|
||||
company: DEIS
|
||||
company-link: https://deis.com/
|
||||
date: "2017-05-15T00:00:00Z"
|
||||
published: true
|
||||
date: 2017-05-15
|
||||
thumbnail: https://gabrtv.github.io/deis-dockercon-2014/img/DeisLogo.png
|
||||
title: gRPC in Helm
|
||||
url: blog/helmgrpc
|
||||
---
|
||||
|
||||
*Our guest post today comes from Brian Hardock, a software engineer from Deis working on the [Helm](https://helm.sh/) project.*
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
---
|
||||
title: HTTP/2 Smarter At Scale
|
||||
author: Jean de Klerk
|
||||
author-link: https://github.com/jadekler
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2018-07-13T00:00:00Z"
|
||||
published: true
|
||||
title: HTTP/2 Smarter At Scale
|
||||
url: blog/http2_smarter_at_scale
|
||||
date: 2018-07-13
|
||||
---
|
||||
|
||||
Much of the web today runs on HTTP/1.1. The spec for HTTP/1.1 was published in June of 1999, just shy of 20 years ago. A lot has changed since then, which makes it all the more remarkable that HTTP/1.1 has persisted and flourished for so long. But in some areas it’s beginning to show its age; for the most part, in that the designers weren’t building for the scale at which HTTP/1.1 would be used and the astonishing amount of traffic that it would come to handle. A not-so-bad case is that subsequent tests can't pass because of a leaked resource from the previous test. The worst case is that some subsequent tests pass that wouldn't have passed at all if the previously passed test had not leaked a resource.
|
||||
|
||||
<!--more-->
|
||||
|
||||
HTTP/2, whose specification was published in May of 2015, seeks to address some of the scalability concerns of its predecessor while still providing a similar experience to users. HTTP/2 improves upon HTTP/1.1’s design in a number of ways, perhaps most significantly in providing a semantic mapping over connections. In this post we’ll explore the concept of streams and how they can be of substantial benefit to software engineers.
|
||||
|
|
@ -20,7 +19,7 @@ There’s significant overhead to creating HTTP connections. You must establish
|
|||
|
||||
HTTP/2 takes the concept of persistent connections further by providing a semantic layer above connections: streams. Streams can be thought of as a series of semantically connected messages, called frames. A stream may be short-lived, such as a unary stream that requests the status of a user (in HTTP/1.1, this might equate to `GET /users/1234/status`). With increasing frequency it’s long-lived. To use the last example, instead of making individual requests to the /users/1234/status endpoint, a receiver might establish a long-lived stream and thereby continuously receive user status messages in real time.
|
||||
|
||||
<img src="/img/conn_stream_frame_mapping.png" alt="Kotlin Android app example" style="max-width: 800px">
|
||||

|
||||
|
||||
## Streams Provide Concurrency
|
||||
|
||||
|
|
@ -30,13 +29,13 @@ To illustrate this point, consider the case of some service A sending HTTP/1.1 r
|
|||
|
||||
In some snapshot in time, service A has a single idle connection to service B and wants to use it to send some data. Service A wants to send a product order (request 1), a profile update (request 2), and two “new user” requests (requests 3 and 4). Since the product order arrives first, it dominates the single idle connection. The latter three smaller requests must either wait for the large product order to be sent, or some number of new HTTP/1.1 connection must be spun up for the small requests.
|
||||
|
||||
<img src="/img/http2_queue_3.png" alt="Kotlin Android app example" style="max-width: 800px">
|
||||

|
||||
|
||||
Meanwhile, with HTTP/2, streaming allows messages to be sent concurrently on the same connection. Let’s imagine that service A creates a connection to service B with three streams: a “new users” stream, a “profile updates” stream, and a “product order” stream. Now, the latter requests don’t have to wait for the first-to-arrive large product order request; all requests are sent concurrently.
|
||||
|
||||
Concurrency does not mean parallelism, though; we can only send one packet at a time on the connection. So, the sender might round robin sending packets between streams (see below). Alternatively, senders might prioritize certain streams over others; perhaps getting new users signed up is more important to the service!
|
||||
|
||||
<img src="/img/http2_round_robin.png" alt="Kotlin Android app example" style="max-width: 800px">
|
||||

|
||||
|
||||
## Flow Control
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: gRPC - now with easy installation
|
||||
attribution: Originally written by Lisa Carey with help from others at Google.
|
||||
date: "2016-04-04"
|
||||
date: 2016-04-04
|
||||
---
|
||||
|
||||
Today we are happy to provide an update that significantly simplifies the getting started experience for gRPC.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
---
|
||||
title: gRPC ❤ Kotlin
|
||||
author: Spencer Fang
|
||||
author-link: https://github.com/zpencer
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2018-06-19T00:00:00Z"
|
||||
published: true
|
||||
title: gRPC ❤ Kotlin
|
||||
url: blog/kotlin-gradle-projects
|
||||
date: 2018-06-19
|
||||
---
|
||||
|
||||
Did you know that gRPC Java now has out of box support for Kotlin projects built with Gradle? [Kotlin](https://kotlinlang.org/) is a modern, statically typed language developed by JetBrains that targets the JVM and Android. It is generally easy for Kotlin programs to interoperate with existing Java libraries. To improve this experience further, we have added support to the [protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin/releases) so that the generated Java libraries are automatically picked up by Kotlin. You can now add the protobuf-gradle-plugin to your Kotlin project, and use gRPC just like you would with a typical Java project.
|
||||
|
||||
<!--more-->
|
||||
|
||||
The following examples show you how to configure a project for a JVM application and an Android application using Kotlin.
|
||||
|
||||
### Kotlin gRPC client and server
|
||||
|
|
@ -20,6 +20,7 @@ The full example can be found [here](https://github.com/grpc/grpc-java/tree/mast
|
|||
Configuring gRPC for a Kotlin project is the same as configuring it for a Java project.
|
||||
|
||||
Below is a snippet of the example project's `build.gradle` highlighting some Kotlin related sections:
|
||||
|
||||
```groovy
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'com.google.protobuf'
|
||||
|
|
@ -127,6 +128,6 @@ Just like the non-Android project, run `./gradlew generateProto generateProto` t
|
|||
|
||||
Finally, test out the Android app by opening the project in Android Studio and selecting `Run > Run 'app'`.
|
||||
|
||||
<img src="/img/kotlin-project-android-app.png" alt="Kotlin Android app example" style="max-width: 404px">
|
||||

|
||||
|
||||
We are excited about improving the gRPC experience for Kotlin developers. Please add enhancement ideas or bugs to the [protobuf-gradle-plugin issue tracker](https://github.com/google/protobuf-gradle-plugin/issues) or the [grpc-java issue tracker](https://github.com/grpc/grpc-java/issues).
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: The gRPC Meetup Kit
|
||||
date: "2017-09-14"
|
||||
date: 2017-09-14
|
||||
attribution: Mark Mandel, Sandeep Dinesh
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,25 @@
|
|||
---
|
||||
title: Mobile Benchmarks
|
||||
attribution: Originally written by David Cao with additional content by Makarand and
|
||||
others at Google.
|
||||
author: David Cao
|
||||
company: Google
|
||||
company-link: https://cloud.google.com
|
||||
date: "2016-07-26T00:00:00Z"
|
||||
published: false
|
||||
date: 2016-07-26
|
||||
thumbnail: ../img/gcp-icon.png?raw=true
|
||||
title: Mobile Benchmarks
|
||||
url: blog/mobile-benchmarks
|
||||
---
|
||||
|
||||
As gRPC has become a better and faster RPC framework, we've consistently gotten the question, "How _much_ faster is gRPC?" We already have comprehensive server-side benchmarks, but we don't have mobile benchmarks. Benchmarking a client is a bit different than benchmarking a server. We care more about things such as latency and request size and less about things like queries per second (QPS) and number of concurrent threads. Thus we built an Android app in order to quantify these factors and provide solid numbers behind them.
|
||||
|
||||
Specifically what we want to benchmark is client side protobuf vs. JSON serialization/deserialization and gRPC vs. a RESTful HTTP JSON service. For the serialization benchmarks, we want to measure the size of messages and speed at which we serialize and deserialize. For the RPC benchmarks, we want to measure the latency of end-to-end requests and packet size.
|
||||
|
||||
|
||||
Protobuf vs. JSON
|
||||
## Protobuf vs. JSON
|
||||
|
||||
In order to benchmark protobuf and JSON, we ran serializations and deserializations over and over on randomly generated protos, which can be seen [here](https://github.com/david-cao/gRPCBenchmarks/tree/master/protolite_app/app/src/main/proto). These protos varied quite a bit in size and complexity, from just a few bytes to over 100kb. JSON equivalents were created and then also benchmarked. For the protobuf messages, we had three main methods of serializing and deserializing: simply using a byte array, `CodedOutputStream`/`CodedInputStream` which is protobuf's own implementation of input and output streams, and Java's `ByteArrayOutputStream` and `ByteArrayInputStream`. For JSON we used `org.json`'s [`JSONObject`](https://developer.android.com/reference/org/json/JSONObject.html). This only had one method to serialize and deserialize, `toString()` and `new JSONObject()`, respectively.
|
||||
|
||||
In order to keep benchmarks as accurate as possible, we wrapped the code to be benchmarked in an interface and simply looped it for a set number of iterations. This way we discounted any time spent checking the system time.
|
||||
|
||||
``` Java
|
||||
```java
|
||||
interface Action {
|
||||
void execute();
|
||||
}
|
||||
|
|
@ -39,6 +36,7 @@ for (int i = 0; i < 100; ++i) {
|
|||
a.execute();
|
||||
}
|
||||
```
|
||||
|
||||
Before running a benchmark, we ran a warmup in order to clean out any erratic behaviour by the JVM, and then calculated the number of iterations needed to run for a set time (10 seconds in the protobuf vs. JSON case). To do this, we started with 1 iteration, measured the time it took for that run, and compared it to a minimum sample time (2 seconds in our case). If the number of iterations took long enough, we estimated the number of iterations needed to run for 10 seconds by doing some math. Otherwise, we multiplied the number of iterations by 2 and repeated.
|
||||
|
||||
```Java
|
||||
|
|
@ -54,22 +52,19 @@ while (elapsed < MIN_SAMPLE_TIME_MS) {
|
|||
iterations = (int) ((TARGET_TIME_MS / (double) elapsed) * iterations);
|
||||
```
|
||||
|
||||
Results
|
||||
## Results
|
||||
|
||||
Benchmarks were run on protobuf, JSON, and gzipped JSON.
|
||||
|
||||
We found that regardless of the serialization/deserialization method used for protobuf, it was consistently about 3x faster for serializing than JSON. For deserialization, JSON is actually a bit faster for small messages (<1kb), around 1.5x, but for larger messages (>15kb) protobuf is 2x faster. For gzipped JSON, protobuf is well over 5x faster in serialization, regardless of size. For deserialization, both are about the same at small messages, but protobuf is about 3x faster for larger messages. Results can be explored in more depth and replicated [here](/github_readme).
|
||||
|
||||
|
||||
gRPC vs. HTTP JSON
|
||||
We found that regardless of the serialization/deserialization method used for protobuf, it was consistently about 3x faster for serializing than JSON. For deserialization, JSON is actually a bit faster for small messages (<1kb), around 1.5x, but for larger messages (>15kb) protobuf is 2x faster. For gzipped JSON, protobuf is well over 5x faster in serialization, regardless of size. For deserialization, both are about the same at small messages, but protobuf is about 3x faster for larger messages. Results can be explored in more depth and replicated [in the README](https://github.com/david-cao/gRPCBenchmarks).
|
||||
|
||||
## gRPC vs. HTTP JSON
|
||||
|
||||
To benchmark RPC calls, we want to measure end-to-end latency and bandwidth. To do this, we ping pong with a server for 60 seconds, using the same message each time, and measure the latency and message size. The message consists of some fields for the server to read, and a payload of bytes. We compared gRPC's unary call to a simple RESTful HTTP JSON service. The gRPC benchmark creates a channel, and starts a unary call that repeats when it recieves a response until 60 seconds have passed. The response contains a proto with the same payload sent.
|
||||
|
||||
Similarly for the HTTP JSON benchmarks, it sends a POST request to the server with an equivalent JSON object, and the server sends back a JSON object with the same payload.
|
||||
|
||||
```Java
|
||||
|
||||
```java
|
||||
// This can be found in AsyncClient.java doUnaryCalls()
|
||||
// Make stub to send unary call
|
||||
final BenchmarkServiceStub stub = BenchmarkServiceGrpc.newStub(channel);
|
||||
|
|
@ -109,11 +104,11 @@ stub.unaryCall(request, new StreamObserver<SimpleResponse>() {
|
|||
}
|
||||
});
|
||||
```
|
||||
|
||||
Both `HttpUrlConnection` and the [OkHttp library](https://square.github.io/okhttp/) were used.
|
||||
|
||||
Only gRPC's unary calls were benchmarked against HTTP, since streaming calls were over 2x faster than the unary calls. Moreover, HTTP has no equivalent of streaming, which is an HTTP/2 specific feature.
|
||||
|
||||
## Results
|
||||
|
||||
Results
|
||||
|
||||
In terms of latency, gRPC is **5x-10x** faster up to the 95th percentile, with averages of around 2 milliseconds for an end-to-end request. For bandwidth, gRPC is about 3x faster for small requests (100-1000 byte payload), and consistently 2x faster for large requests (10kb-100kb payload). To replicate these results or explore in more depth, check out our [repository](/github_readme).
|
||||
In terms of latency, gRPC is **5x-10x** faster up to the 95th percentile, with averages of around 2 milliseconds for an end-to-end request. For bandwidth, gRPC is about 3x faster for small requests (100-1000 byte payload), and consistently 2x faster for large requests (10kb-100kb payload). To replicate these results or explore in more depth, check out our [repository](https://github.com/david-cao/gRPCBenchmarks).
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
---
|
||||
title: So You Want to Optimize gRPC - Part 1
|
||||
author: Carl Mastrangelo
|
||||
author-link: https://github.com/carl-mastrangelo
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2018-03-06T00:00:00Z"
|
||||
published: true
|
||||
title: So You Want to Optimize gRPC - Part 1
|
||||
url: blog/optimizing-grpc-part-1
|
||||
date: 2018-03-06
|
||||
---
|
||||
|
||||
A common question with gRPC is how to make it fast. The gRPC library offers users access to high
|
||||
|
|
@ -61,26 +59,26 @@ As mentioned above, the client makes random RPCs. For example, here is the code
|
|||
request:
|
||||
|
||||
```java
|
||||
private void doCreate(KeyValueServiceBlockingStub stub) {
|
||||
ByteString key = createRandomKey();
|
||||
try {
|
||||
CreateResponse res = stub.create(
|
||||
CreateRequest.newBuilder()
|
||||
.setKey(key)
|
||||
.setValue(randomBytes(MEAN_VALUE_SIZE))
|
||||
.build());
|
||||
if (!res.equals(CreateResponse.getDefaultInstance())) {
|
||||
throw new RuntimeException("Invalid response");
|
||||
}
|
||||
} catch (StatusRuntimeException e) {
|
||||
if (e.getStatus().getCode() == Code.ALREADY_EXISTS) {
|
||||
knownKeys.remove(key);
|
||||
logger.log(Level.INFO, "Key already existed", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
private void doCreate(KeyValueServiceBlockingStub stub) {
|
||||
ByteString key = createRandomKey();
|
||||
try {
|
||||
CreateResponse res = stub.create(
|
||||
CreateRequest.newBuilder()
|
||||
.setKey(key)
|
||||
.setValue(randomBytes(MEAN_VALUE_SIZE))
|
||||
.build());
|
||||
if (!res.equals(CreateResponse.getDefaultInstance())) {
|
||||
throw new RuntimeException("Invalid response");
|
||||
}
|
||||
} catch (StatusRuntimeException e) {
|
||||
if (e.getStatus().getCode() == Code.ALREADY_EXISTS) {
|
||||
knownKeys.remove(key);
|
||||
logger.log(Level.INFO, "Key already existed", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A random key is created, along with a random value. The request is sent to the server, and the
|
||||
|
|
@ -101,21 +99,21 @@ On the server side, the request is received by the
|
|||
[service handler](https://github.com/carl-mastrangelo/kvstore/blob/f422b1b6e7c69f8c07f96ed4ddba64757242352c/src/main/java/io/grpc/examples/KvService.java#L34):
|
||||
|
||||
```java
|
||||
private final Map<ByteBuffer, ByteBuffer> store = new HashMap<>();
|
||||
private final Map<ByteBuffer, ByteBuffer> store = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public synchronized void create(
|
||||
CreateRequest request, StreamObserver<CreateResponse> responseObserver) {
|
||||
ByteBuffer key = request.getKey().asReadOnlyByteBuffer();
|
||||
ByteBuffer value = request.getValue().asReadOnlyByteBuffer();
|
||||
simulateWork(WRITE_DELAY_MILLIS);
|
||||
if (store.putIfAbsent(key, value) == null) {
|
||||
responseObserver.onNext(CreateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
return;
|
||||
}
|
||||
responseObserver.onError(Status.ALREADY_EXISTS.asRuntimeException());
|
||||
@Override
|
||||
public synchronized void create(
|
||||
CreateRequest request, StreamObserver<CreateResponse> responseObserver) {
|
||||
ByteBuffer key = request.getKey().asReadOnlyByteBuffer();
|
||||
ByteBuffer value = request.getValue().asReadOnlyByteBuffer();
|
||||
simulateWork(WRITE_DELAY_MILLIS);
|
||||
if (store.putIfAbsent(key, value) == null) {
|
||||
responseObserver.onNext(CreateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
return;
|
||||
}
|
||||
responseObserver.onError(Status.ALREADY_EXISTS.asRuntimeException());
|
||||
}
|
||||
```
|
||||
|
||||
The service extracts the key and value as `ByteBuffer`s from the request. It acquires the lock
|
||||
|
|
@ -164,21 +162,21 @@ decides](https://github.com/carl-mastrangelo/kvstore/blob/f422b1b6e7c69f8c07f96e
|
|||
what operation to do:
|
||||
|
||||
```java
|
||||
void doClientWork(AtomicBoolean done) {
|
||||
Random random = new Random();
|
||||
KeyValueServiceBlockingStub stub = KeyValueServiceGrpc.newBlockingStub(channel);
|
||||
void doClientWork(AtomicBoolean done) {
|
||||
Random random = new Random();
|
||||
KeyValueServiceBlockingStub stub = KeyValueServiceGrpc.newBlockingStub(channel);
|
||||
|
||||
while (!done.get()) {
|
||||
// Pick a random CRUD action to take.
|
||||
int command = random.nextInt(4);
|
||||
if (command == 0) {
|
||||
doCreate(stub);
|
||||
continue;
|
||||
}
|
||||
/* ... */
|
||||
rpcCount++;
|
||||
while (!done.get()) {
|
||||
// Pick a random CRUD action to take.
|
||||
int command = random.nextInt(4);
|
||||
if (command == 0) {
|
||||
doCreate(stub);
|
||||
continue;
|
||||
}
|
||||
/* ... */
|
||||
rpcCount++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This means that **at most one RPC can be active at any time**. Each RPC has to wait for the
|
||||
|
|
@ -192,7 +190,7 @@ Our code can do about 16 queries in a second, so that seems about right. We can
|
|||
assumption by looking at the output of the `time` command used to run the code. The server goes
|
||||
to sleep when running queries in the
|
||||
[`simulateWork`](https://github.com/carl-mastrangelo/kvstore/blob/f422b1b6e7c69f8c07f96ed4ddba64757242352c/src/main/java/io/grpc/examples/KvService.java#L88)
|
||||
method. This implies that the program should be mostly idle while waiting for the RPCs to
|
||||
method. This implies that the program should be mostly idle while waiting for the RPCs to
|
||||
complete.
|
||||
|
||||
We can confirm this is the case by looking at the `real` and `user` times of the command above.
|
||||
|
|
@ -228,39 +226,39 @@ is still roughly from top to bottom in the function. Here is the
|
|||
method revised:
|
||||
|
||||
```java
|
||||
private void doCreate(KeyValueServiceFutureStub stub, AtomicReference<Throwable> error) {
|
||||
ByteString key = createRandomKey();
|
||||
ListenableFuture<CreateResponse> res = stub.create(
|
||||
CreateRequest.newBuilder()
|
||||
.setKey(key)
|
||||
.setValue(randomBytes(MEAN_VALUE_SIZE))
|
||||
.build());
|
||||
res.addListener(() -> rpcCount.incrementAndGet(), MoreExecutors.directExecutor());
|
||||
Futures.addCallback(res, new FutureCallback<CreateResponse>() {
|
||||
@Override
|
||||
public void onSuccess(CreateResponse result) {
|
||||
if (!result.equals(CreateResponse.getDefaultInstance())) {
|
||||
error.compareAndSet(null, new RuntimeException("Invalid response"));
|
||||
}
|
||||
synchronized (knownKeys) {
|
||||
knownKeys.add(key);
|
||||
}
|
||||
private void doCreate(KeyValueServiceFutureStub stub, AtomicReference<Throwable> error) {
|
||||
ByteString key = createRandomKey();
|
||||
ListenableFuture<CreateResponse> res = stub.create(
|
||||
CreateRequest.newBuilder()
|
||||
.setKey(key)
|
||||
.setValue(randomBytes(MEAN_VALUE_SIZE))
|
||||
.build());
|
||||
res.addListener(() -> rpcCount.incrementAndGet(), MoreExecutors.directExecutor());
|
||||
Futures.addCallback(res, new FutureCallback<CreateResponse>() {
|
||||
@Override
|
||||
public void onSuccess(CreateResponse result) {
|
||||
if (!result.equals(CreateResponse.getDefaultInstance())) {
|
||||
error.compareAndSet(null, new RuntimeException("Invalid response"));
|
||||
}
|
||||
synchronized (knownKeys) {
|
||||
knownKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
Status status = Status.fromThrowable(t);
|
||||
if (status.getCode() == Code.ALREADY_EXISTS) {
|
||||
synchronized (knownKeys) {
|
||||
knownKeys.remove(key);
|
||||
}
|
||||
logger.log(Level.INFO, "Key already existed", t);
|
||||
} else {
|
||||
error.compareAndSet(null, t);
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
Status status = Status.fromThrowable(t);
|
||||
if (status.getCode() == Code.ALREADY_EXISTS) {
|
||||
synchronized (knownKeys) {
|
||||
knownKeys.remove(key);
|
||||
}
|
||||
logger.log(Level.INFO, "Key already existed", t);
|
||||
} else {
|
||||
error.compareAndSet(null, t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The stub has been modified to be a `KeyValueServiceFutureStub`, which produces a `Future` when
|
||||
|
|
@ -323,23 +321,23 @@ permit. To [accomplish](https://github.com/carl-mastrangelo/kvstore/blob/02-fut
|
|||
this, we will using a `Semaphore`:
|
||||
|
||||
```java
|
||||
private final Semaphore limiter = new Semaphore(100);
|
||||
private final Semaphore limiter = new Semaphore(100);
|
||||
|
||||
private void doCreate(KeyValueServiceFutureStub stub, AtomicReference<Throwable> error)
|
||||
throws InterruptedException {
|
||||
limiter.acquire();
|
||||
ByteString key = createRandomKey();
|
||||
ListenableFuture<CreateResponse> res = stub.create(
|
||||
CreateRequest.newBuilder()
|
||||
.setKey(key)
|
||||
.setValue(randomBytes(MEAN_VALUE_SIZE))
|
||||
.build());
|
||||
res.addListener(() -> {
|
||||
rpcCount.incrementAndGet();
|
||||
limiter.release();
|
||||
}, MoreExecutors.directExecutor());
|
||||
/* ... */
|
||||
}
|
||||
private void doCreate(KeyValueServiceFutureStub stub, AtomicReference<Throwable> error)
|
||||
throws InterruptedException {
|
||||
limiter.acquire();
|
||||
ByteString key = createRandomKey();
|
||||
ListenableFuture<CreateResponse> res = stub.create(
|
||||
CreateRequest.newBuilder()
|
||||
.setKey(key)
|
||||
.setValue(randomBytes(MEAN_VALUE_SIZE))
|
||||
.build());
|
||||
res.addListener(() -> {
|
||||
rpcCount.incrementAndGet();
|
||||
limiter.release();
|
||||
}, MoreExecutors.directExecutor());
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
Now the code runs successfully, and doesn't run out of memory.
|
||||
|
|
@ -396,4 +394,3 @@ the very basics of how to approach and think about optimization. Always make su
|
|||
before and after your changes, and use these measurements to guide your optimizations.
|
||||
|
||||
In [Part 2](/blog/optimizing-grpc-part-2), we will continue optimizing the server part of the code.
|
||||
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
---
|
||||
title: So You Want to Optimize gRPC - Part 2
|
||||
author: Carl Mastrangelo
|
||||
author-link: https://carlmastrangelo.com/
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2018-04-16T00:00:00Z"
|
||||
published: true
|
||||
title: So You Want to Optimize gRPC - Part 2
|
||||
url: blog/optimizing-grpc-part-2
|
||||
date: 2018-04-16
|
||||
---
|
||||
|
||||
How fast is gRPC? Pretty fast if you understand how modern clients and servers are built. In
|
||||
|
|
@ -32,21 +30,21 @@ accidentally corrupt the state of storage. To ensure this, the service uses the
|
|||
keyword to ensure only one RPC is active at a time:
|
||||
|
||||
```java
|
||||
private final Map<ByteBuffer, ByteBuffer> store = new HashMap<>();
|
||||
private final Map<ByteBuffer, ByteBuffer> store = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public synchronized void create(
|
||||
CreateRequest request, StreamObserver<CreateResponse> responseObserver) {
|
||||
ByteBuffer key = request.getKey().asReadOnlyByteBuffer();
|
||||
ByteBuffer value = request.getValue().asReadOnlyByteBuffer();
|
||||
simulateWork(WRITE_DELAY_MILLIS);
|
||||
if (store.putIfAbsent(key, value) == null) {
|
||||
responseObserver.onNext(CreateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
return;
|
||||
}
|
||||
responseObserver.onError(Status.ALREADY_EXISTS.asRuntimeException());
|
||||
@Override
|
||||
public synchronized void create(
|
||||
CreateRequest request, StreamObserver<CreateResponse> responseObserver) {
|
||||
ByteBuffer key = request.getKey().asReadOnlyByteBuffer();
|
||||
ByteBuffer value = request.getValue().asReadOnlyByteBuffer();
|
||||
simulateWork(WRITE_DELAY_MILLIS);
|
||||
if (store.putIfAbsent(key, value) == null) {
|
||||
responseObserver.onNext(CreateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
return;
|
||||
}
|
||||
responseObserver.onError(Status.ALREADY_EXISTS.asRuntimeException());
|
||||
}
|
||||
```
|
||||
|
||||
While this code is thread safe, it comes at a high price: only one RPC can ever be active! We
|
||||
|
|
@ -81,21 +79,21 @@ Concurrent maps provide stronger guarantees about the safety of `putIfAbsent`, s
|
|||
`HashMap` to a `ConcurrentHashMap` and remove `synchronized`:
|
||||
|
||||
```java
|
||||
private final ConcurrentMap<ByteBuffer, ByteBuffer> store = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(
|
||||
CreateRequest request, StreamObserver<CreateResponse> responseObserver) {
|
||||
ByteBuffer key = request.getKey().asReadOnlyByteBuffer();
|
||||
ByteBuffer value = request.getValue().asReadOnlyByteBuffer();
|
||||
simulateWork(WRITE_DELAY_MILLIS);
|
||||
if (store.putIfAbsent(key, value) == null) {
|
||||
responseObserver.onNext(CreateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
return;
|
||||
}
|
||||
responseObserver.onError(Status.ALREADY_EXISTS.asRuntimeException());
|
||||
private final ConcurrentMap<ByteBuffer, ByteBuffer> store = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(
|
||||
CreateRequest request, StreamObserver<CreateResponse> responseObserver) {
|
||||
ByteBuffer key = request.getKey().asReadOnlyByteBuffer();
|
||||
ByteBuffer value = request.getValue().asReadOnlyByteBuffer();
|
||||
simulateWork(WRITE_DELAY_MILLIS);
|
||||
if (store.putIfAbsent(key, value) == null) {
|
||||
responseObserver.onNext(CreateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
return;
|
||||
}
|
||||
responseObserver.onError(Status.ALREADY_EXISTS.asRuntimeException());
|
||||
}
|
||||
```
|
||||
|
||||
### If at First You Don't Succeed
|
||||
|
|
@ -104,21 +102,21 @@ Updating `create` was pretty easy. Doing the same for `retrieve` and `delete` i
|
|||
However, the `update` method is a little trickier. Let's take a look at what it's doing:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public synchronized void update(
|
||||
UpdateRequest request, StreamObserver<UpdateResponse> responseObserver) {
|
||||
ByteBuffer key = request.getKey().asReadOnlyByteBuffer();
|
||||
ByteBuffer newValue = request.getValue().asReadOnlyByteBuffer();
|
||||
simulateWork(WRITE_DELAY_MILLIS);
|
||||
ByteBuffer oldValue = store.get(key);
|
||||
if (oldValue == null) {
|
||||
responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
|
||||
return;
|
||||
}
|
||||
store.replace(key, oldValue, newValue);
|
||||
responseObserver.onNext(UpdateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
@Override
|
||||
public synchronized void update(
|
||||
UpdateRequest request, StreamObserver<UpdateResponse> responseObserver) {
|
||||
ByteBuffer key = request.getKey().asReadOnlyByteBuffer();
|
||||
ByteBuffer newValue = request.getValue().asReadOnlyByteBuffer();
|
||||
simulateWork(WRITE_DELAY_MILLIS);
|
||||
ByteBuffer oldValue = store.get(key);
|
||||
if (oldValue == null) {
|
||||
responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
|
||||
return;
|
||||
}
|
||||
store.replace(key, oldValue, newValue);
|
||||
responseObserver.onNext(UpdateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
```
|
||||
|
||||
Updating a key to a new value needs two interactions with the `store`:
|
||||
|
|
@ -135,21 +133,21 @@ was successful. (`ConcurrentMap` asserts that the operations will not corrupt t
|
|||
structure, but doesn't say that they will succeed!) We will use a do-while loop:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void update(
|
||||
UpdateRequest request, StreamObserver<UpdateResponse> responseObserver) {
|
||||
// ...
|
||||
ByteBuffer oldValue;
|
||||
do {
|
||||
oldValue = store.get(key);
|
||||
if (oldValue == null) {
|
||||
responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
|
||||
return;
|
||||
}
|
||||
} while (!store.replace(key, oldValue, newValue));
|
||||
responseObserver.onNext(UpdateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
@Override
|
||||
public void update(
|
||||
UpdateRequest request, StreamObserver<UpdateResponse> responseObserver) {
|
||||
// ...
|
||||
ByteBuffer oldValue;
|
||||
do {
|
||||
oldValue = store.get(key);
|
||||
if (oldValue == null) {
|
||||
responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
|
||||
return;
|
||||
}
|
||||
} while (!store.replace(key, oldValue, newValue));
|
||||
responseObserver.onNext(UpdateResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
```
|
||||
|
||||
The code wants to fail if it ever sees null, but never if there is a non-null previous value. One
|
||||
|
|
@ -229,4 +227,3 @@ need to understand what your code is doing. This post shows how to convert a lo
|
|||
a low-contention, lock-free service. Always make sure to measure before and after your changes.
|
||||
|
||||
In Part 3, we will optimize the code even further. 2,400 RPC/s is just the beginning!
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: gRPC Motivation and Design Principles
|
||||
date: "2015-09-08"
|
||||
date: 2015-09-08
|
||||
attribution: Originally written by Louis Ryan with help from others at Google
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
---
|
||||
attribution: Originally written by Lisa Carey with help from others at Google.
|
||||
date: "2016-03-24T00:00:00Z"
|
||||
published: true
|
||||
title: Google Cloud PubSub - with the power of gRPC!
|
||||
url: blog/pubsub
|
||||
attribution: Originally written by Lisa Carey with help from others at Google.
|
||||
date: 2016-03-24
|
||||
---
|
||||
|
||||
[Google Cloud PubSub](https://cloud.google.com/pubsub/) is Google's scalable real-time messaging service that lets users send and receive messages between independent applications. It's an important part of Google Cloud Platform's big data offering, and is used by customers worldwide to build their own robust, global services. However, until now, the only way to use the Cloud PubSub API was via JSON over HTTP. That's all changed with the release of [PubSub gRPC alpha](https://cloud.google.com/blog/big-data/2016/03/announcing-grpc-alpha-for-google-cloud-pubsub). Now **users can access PubSub via gRPC** and benefit from all the advantages it brings.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
---
|
||||
title: The state of gRPC in the browser
|
||||
author: Johan Brandhorst
|
||||
author-link: https://jbrandhorst.com/
|
||||
date: "2019-01-08T00:00:00Z"
|
||||
published: true
|
||||
title: The state of gRPC in the browser
|
||||
url: blog/state-of-grpc-web
|
||||
date: 2019-01-08
|
||||
---
|
||||
|
||||
_This is a guest post by_
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
---
|
||||
title: Take the gRPC Survey!
|
||||
author: Kailash Sethuraman
|
||||
author-link: https://github.com/hsaliak
|
||||
company: Google
|
||||
company-link: https://www.google.com
|
||||
date: "2018-08-14T00:00:00Z"
|
||||
published: true
|
||||
title: Take the gRPC Survey!
|
||||
url: blog/take-the-grpc-survey
|
||||
date: 2018-08-14
|
||||
---
|
||||
|
||||
## The gRPC Project wants your feedback!
|
||||
|
||||
The gRPC project is looking for feedback to improve the gRPC experience. To do this, we are running a [gRPC user survey](http://bit.ly/gRPC18survey). We invite you to participate and provide input that will help us better plan and prioritize.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## gRPC User Survey
|
||||
|
||||
**Who** : If you currently use gRPC, have used gRPC in the past, or have any interest in it, we would love to hear from you.
|
||||
|
||||
**Where**: Please take this 15 minute survey by Friday, 24th August.
|
||||
|
|
@ -23,7 +23,7 @@ The gRPC project is looking for feedback to improve the gRPC experience. To do t
|
|||
|
||||
|
||||
## Spread the word!
|
||||
|
||||
Please help us spread the word on this survey by posting it on your social networks and sharing with your friends. Every single feedback is precious, and we would like as much of it as possible!
|
||||
|
||||
Survey Short link: [http://bit.ly/gRPC18survey
|
||||
](http://bit.ly/gRPC18survey)
|
||||
Survey Short link: [http://bit.ly/gRPC18survey](http://bit.ly/gRPC18survey)
|
||||
|
|
@ -5,38 +5,30 @@ attribution: Originally written by Dale Hopkins with additional content by Lisa
|
|||
author: Dale Hopkins
|
||||
company: Vendasta
|
||||
company-link: https://vendasta.com
|
||||
date: "2016-07-25"
|
||||
published: false
|
||||
date: 2016-08-29
|
||||
thumbnail: ../img/vend-icon.png?raw=true
|
||||
aliases: ["blog/vendastagrpc"]
|
||||
---
|
||||
|
||||
Our guest post today comes from Dale Hopkins, CTO of [Vendasta](https://vendasta.com/). Vendasta started out 8 years ago as a point solution provider of products for small business. From the beginning we partnered with media companies and agencies who have armies of salespeople and existing relationships with those businesses to sell our software. It is estimated that over 30 million small businesses exist in the United States alone, so scalability of our SaaS solution was considered one of our top concerns from the beginning and it was the reason we started with Google App Engine (Python GAE) and Datastore. This solution worked really well for us as our system scaled from hundreds to hundreds of thousands of end users. We also scaled our offering from a point solution to an entire platform with multiple products and the tools for partners to manage their sales of those products during this time.
|
||||
Our guest post today comes from Dale Hopkins, CTO of [Vendasta](https://vendasta.com/).
|
||||
|
||||
Vendasta started out 8 years ago as a point solution provider of products for small business. From the beginning we partnered with media companies and agencies who have armies of salespeople and existing relationships with those businesses to sell our software. It is estimated that over 30 million small businesses exist in the United States alone, so scalability of our SaaS solution was considered one of our top concerns from the beginning and it was the reason we started with [Google App Engine](https://cloud.google.com/appengine/) and Datastore. This solution worked really well for us as our system scaled from hundreds to hundreds of thousands of end users. We also scaled our offering from a point solution to an entire platform with multiple products and the tools for partners to manage their sales of those products during this time.
|
||||
|
||||
<!--more-->
|
||||
All throughout this journey Python GAE served our needs well. We exposed a number of APIs via HTTP + JSON for our partners to automate tasks and integrate their other systems with our products and platform. However, in 2016 we introduced the Vendasta Marketplace. This marked a major change to our offering, which depended heavily on having 3rd party vendors use our APIs to deliver their own products in our platform. This was a major change because our public APIs provide an upper-bound on 3rd-party applications, and made us realize that we really needed to make APIs that were amazing, not just good.
|
||||
|
||||
The first optimization that we started with was to use the Go programming language to build endpoints that handled higher throughput with lower latency than we could get with Python. On some APIs this made an incredible difference: we saw 50th percentile response times to drop from 1200 ms to 4 ms, and even more spectacularly 99th percentile response times drop from 30,000 ms to 12 ms! On other APIs we saw a much smaller, but still significant difference.
|
||||
|
||||
# Three Optimizations to our architecture
|
||||
The second optimization we used was to replicate large portions of our Datastore data into ElasticSearch. ElasticSearch is a fundamentally different storage technology to Datastore, and is not a managed service, so it was a big leap for us. But this change allowed us to migrate almost all of our overnight batch-processing APIs to real-time APIs. We had tried BigQuery, but it's query processing times meant that we couldn't display things in real time. We had tried cloudSQL, but there was too much data for it to easily scale. We had tried the appengine Search API, but it has limitations with result sets over 10,000. We instead scaled up our ElasticSearch cluster using [Google Container Engine](https://cloud.google.com/container-engine/) and with it's powerful aggregations and facet processing our needs were easily met. So with these first two solutions in place, we had made meaningful changes to the performance of our APIs.
|
||||
|
||||
* The first optimization that we started with was to use the Go programming language to build endpoints that handled higher throughput with lower latency than we could get with Python. On some APIs this made an incredible difference: we saw 50th percentile response times to drop from 1200 ms to 4 ms, and even more spectacularly 99th percentile response times drop from 30,000 ms to 12 ms! On other APIs we saw a much smaller, but still significant difference.
|
||||
|
||||
The last optimization we made was to move our APIs to [gRPC](/). This change was much more extensive than the others as it affected our clients. Like ElasticSearch, it represents a fundamentally different model with differing performance characteristics, but unlike ElasticSearch we found it to be a true superset: all of our usage scenarios were impacted positively by it.
|
||||
|
||||
* The second optimization we used was to replicate large portions of our Datastore data into ElasticSearch. ElasticSearch is a fundamentally different storage technology to Datastore, and is not a managed service, so it was a big leap for us. But this change allowed us to migrate almost all of our overnight batch-processing APIs to real-time APIs. So with these first two solutions in place, we had made meaningful changes to the performance of our APIs.
|
||||
|
||||
* The last optimization we made was to move our APIs to gRPC. This change was much more extensive than the others as it affected our clients. Like ElasticSearch, it represents a fundamentally different model with differing performance characteristics, but unlike ElasticSearch we found it to be a true superset: all of our usage scenarios were impacted positively by it.
|
||||
|
||||
|
||||
## Four Benefits from gRPC
|
||||
The first benefit we saw from gRPC was the ability to move from publishing APIs and asking developers to integrate with them, to releasing SDKs and asking developers to copy-paste example code written in their language. This represents a really big benefit for people looking to integrate with our products, while not requiring us to hand-roll entire SDKs in the 5+ languages our partners and vendors use. It is important to note that we still write light wrappers over the generated gRPC SDKs to make them package-manager friendly, and to provide wrappers over the generated protobuf structures.
|
||||
|
||||
* The first benefit we saw from gRPC was the ability to move from publishing APIs and asking developers to integrate with them, to releasing SDKs and asking developers to copy-paste example code written in their language. This represents a really big benefit for people looking to integrate with our products, while not requiring us to hand-roll entire SDKs in the 5+ languages our partners and vendors use. It is important to note that we still write light wrappers over the generated gRPC SDKs to make them package-manager friendly, and to provide wrappers over the generated protobuf structures.
|
||||
|
||||
The second benefit we saw from gRPC was the ability to break free from the call-and-response architecture necessitated by HTTP + JSON. gRPC is built on top of HTTP/2, which allows for client-side and/or server-side streaming. In our use cases, this means we can lower the time to first display by streaming results as they become ready on the server (server-side streaming). We have also been investigating the potential to offer very flexible create endpoints that easily support bulk ingestion with bi-directional streaming, this would mean we would allow the client to asynchronously stream results, while the server would stream back statuses allowing for easy checkpoint operations while not slowing upload speeds to wait for confirmations. We feel that we are just starting to see the benefits from this feature as it opens up a totally new model for client-server interactions that just wasn't possible with HTTP.
|
||||
|
||||
* The second benefit we saw from gRPC was the ability to break free from the call-and-response architecture necessitated by HTTP + JSON. gRPC is built on top of HTTP/2, which allows for client-side and/or server-side streaming. In our use cases, this means we can lower the time to first display by streaming results as they become ready on the server (server-side streaming), and by providing very flexible create endpoints that easily support bulk ingestion (bi-directional streaming). We feel that we are just starting to see the benefits from this feature as it opens up a totally new model for client-server interactions that just wasn't possible with HTTP.
|
||||
|
||||
* The third benefit was the switch from JSON to protocol buffers, which works very well with gRPC. This improves serialization and deserialization times; which is very significant to some of our APIs, but appreciated on all of them. The more important benefit comes from the explicit format specification of proto, meaning that clients receive typed objects rather than free-form JSON. Because of this, our clients can reap the benefits of auto-completion in their IDEs, type-safety if their language supports it, and enforced compatibility between clients and servers with differing versions.
|
||||
|
||||
* The final benefit of gRPC was our ability to quickly spec endpoints. The proto format for both data and service definition greatly simplifies defining new endpoints and finally allows the succinct definition of endpoint contracts. Combined with code generation, it allows us to truly develop clients and servers in parallel.
|
||||
|
||||
|
||||
Our experience with gRPC has been positive, even though it does not eliminate the difficulty of providing endpoints to partners and vendors, and address of our performance issues. However, it does make improvements to our endpoint performance, integration with those endpoints, and even in delivery of SDKs.
|
||||
|
||||
The third benefit was the switch from JSON to protocol buffers, which works very well with gRPC. This improves serialization and deserialization times; which is very significant to some of our APIs, but appreciated on all of them. The more important benefit comes from the explicit format specification of proto, meaning that clients receive typed objects rather than free-form JSON. Because of this, our clients can reap the benefits of auto-completion in their IDEs, type-safety if their language supports it, and enforced compatibility between clients and servers with differing versions.
|
||||
|
||||
The final benefit of gRPC was our ability to quickly spec endpoints. The proto format for both data and service definition greatly simplifies defining new endpoints and finally allows the succinct definition of endpoint contracts. This means we are much better able to communicate endpoint specifications between our development teams. gRPC means that for the first time at our company we are able to simultaneously develop the client and the server side of our APIs! This means our latency to produce new APIs with the accompanying SDKs has dropped dramatically. Combined with code generation, it allows us to truly develop clients and servers in parallel.
|
||||
|
||||
Our experience with gRPC has been positive, even though it does not eliminate the difficulty of providing endpoints to partners and vendors, and address all of our performance issues. However, it does make improvements to our endpoint performance, integration with those endpoints, and even in delivery of SDKs.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
---
|
||||
title: gRPC at VSCO
|
||||
attribution: Thanks to the VSCO engineers that worked on this migration.Steven Tang,
|
||||
Sam Bobra, Daniel Song, Lucas Kacher, and many others.
|
||||
author: Robert Sayre and Melinda Lu
|
||||
company: VSCO
|
||||
company-link: https://vsco.co
|
||||
date: "2016-09-06T00:00:00Z"
|
||||
published: true
|
||||
date: 2016-09-06
|
||||
thumbnail: ../img/vsco-logo.png?raw=true
|
||||
title: gRPC at VSCO
|
||||
url: blog/vscogrpc
|
||||
aliases: ["blog/vscogrpc"]
|
||||
---
|
||||
|
||||
Our guest post today comes from Robert Sayre and Melinda Lu of VSCO.
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
---
|
||||
title: Migration to Google Cloud Platform — gRPC & grpc-gateway
|
||||
author: Miguel Mendez
|
||||
company: Yik Yak
|
||||
company-link: https://yikyakapp.com
|
||||
date: "2017-04-12T00:00:00Z"
|
||||
published: true
|
||||
date: 2017-04-12
|
||||
thumbnail: https://cdn-images-1.medium.com/max/1600/0*qYehJ2DvPgFcG_nX.
|
||||
title: Migration to Google Cloud Platform — gRPC & grpc-gateway
|
||||
url: blog/yygrpc
|
||||
---
|
||||
|
||||
Our guest post today comes from [Miguel Mendez](https://www.linkedin.com/in/miguel-mendez-008231/) of Yik Yak.
|
||||
|
|
@ -36,18 +34,17 @@ Third, it is great that I can use curl from the command line to hit an API, but
|
|||
The fourth problem with a REST APIs is that, at least until [Swagger](https://swagger.io/) arrived on the scene, there was no declarative way to define a REST API and include type information. It may sound pedantic, but there are legitimate reasons to want a proper definition that includes type information in general. To reinforce the point, look at the lines of PHP server code below, which were extracted from various files, that set the “hidePin” field on “yak” which was then returned to the client. The actual line of code that executed on the server was a function of multiple parameters, so imagine that the one which was run was basically chosen at random:
|
||||
|
||||
```php
|
||||
// Code omitted…
|
||||
$yak->hidePin=false;
|
||||
|
||||
// Code omitted…
|
||||
$yak->hidePin=true;
|
||||
|
||||
// Code omitted…
|
||||
$yak->hidePin=0;
|
||||
|
||||
// Code omitted…
|
||||
$yak->hidePin=1;
|
||||
// Code omitted…
|
||||
$yak->hidePin=false;
|
||||
|
||||
// Code omitted…
|
||||
$yak->hidePin=true;
|
||||
|
||||
// Code omitted…
|
||||
$yak->hidePin=0;
|
||||
|
||||
// Code omitted…
|
||||
$yak->hidePin=1;
|
||||
```
|
||||
|
||||
What is the type of the field hidePin? You cannot say for certain. It could be a boolean or an integer or whatever happens to have been written there by the server, but in any case now your clients have to be able to deal with these possibilities which makes them more complicated.
|
||||
|
|
@ -55,51 +52,59 @@ What is the type of the field hidePin? You cannot say for certain. It could be a
|
|||
Problems can also arise when the client’s definition of a type varies from that which the server expects. Have a look at the server code below which processed a JSON payload sent up by a client:
|
||||
|
||||
```php
|
||||
// Code omitted…
|
||||
switch ($fieldName) {
|
||||
// Code omitted…
|
||||
case “recipientID”:
|
||||
// This is being added because iOS is passing the recipientID
|
||||
// incorrectly and we still want to capture these events
|
||||
// … expected fall through …
|
||||
|
||||
case “Recipientid”:
|
||||
$this->yakkerEvent->recipientID = $value;
|
||||
break;
|
||||
// Code omitted…
|
||||
}
|
||||
// Code omitted…
|
||||
// Code omitted...
|
||||
switch ($fieldName) {
|
||||
// Code omitted...
|
||||
case “recipientID”:
|
||||
// This is being added because iOS is passing the recipientID
|
||||
// incorrectly and we still want to capture these events
|
||||
// … expected fall through …
|
||||
|
||||
case “Recipientid”:
|
||||
$this->yakkerEvent->recipientID = $value;
|
||||
break;
|
||||
// Code omitted...
|
||||
}
|
||||
// Code omitted...
|
||||
```
|
||||
|
||||
In this case, the server had to deal with an iOS client that sent a JSON object whose field name used unexpected casing. Again, not insurmountable but all of these little disconnects compound and work together to steal time away from the problems that really move the ball down the field.
|
||||
|
||||
## gRPC can address the issues with REST…
|
||||
## gRPC can address the issues with REST
|
||||
|
||||
If you’re not familiar with gRPC, it’s a “high performance, open-source universal remote procedure call (RPC) framework” that uses Google Protocol Buffers as the Interface Description Language (IDL) for describing a service interface as well as the structure of the messages exchanged. This IDL can then be compiled to produce language-specific client and server stubs. In case that seemed a little obtuse, I’ll zoom into the aspects that are important.
|
||||
|
||||
### gRPC is Declarative, Strongly-Typed, and Language Independent
|
||||
|
||||
gRPC descriptions are written using an Interface Description Language that is independent of any specific programming language, yet its concepts map onto the supported languages. This means that you can describe your ideal service API, the messages that it supports, and then use “protoc”, the protocol compiler, to generate client and server stubs for your API. Out of the box, you can produce client and server stubs in C/C++, C#, Node.js, PHP, Ruby, Python, Go and Java. You can also get additional protoc plugins which can create stubs for Objective-C and Swift.
|
||||
|
||||
Those issues that we had with “hidePin” and “recipientID” vs.”Recipientid” fields above go away because we have a single, canonical declaration that establishes the types used, and the language-specific code generation ensures that we don’t have typos in the client or server code regardless of their implementation language.
|
||||
|
||||
### gRPC Means No hand-rolling of RPC Code is Required
|
||||
|
||||
This is a very powerful aspect of the gRPC ecosystem. Often times developers will hand roll their RPC code because it just seems more straightforward. However, as the number of types of clients that you need to support increases, the carrying costs of this approach also increase non-linearly.
|
||||
Imagine that you start off with a service that is called from a web browser. At some point down the road, the requirements are updated and now you have to support Android and iOS clients. Your server is likely fine, but the clients now need to be able to speak the same RPC dialect and often times there are differences that creep in. Things can get even worse if the server has to compensate for the differences amongst the clients.
|
||||
On the other hand, using gRPC you just add the protocol compiler plugins and they generate the Android and iOS client stubs. This cuts out a whole class of problems. As a bonus, if you don’t modify the generated code — and you should not have to — then any performance improvements in the generated code will be picked up.
|
||||
|
||||
### gRPC has Compact Serialization
|
||||
|
||||
gRPC uses Google protocol buffers to serialize messages. This serialization format is very compact because, among other things, field names are not included in the serialized form. Compare this to a JSON object where each instance of an object carries a full copy of its field names, includes extra curly braces, etc. For a low-volume application this may not be an issue, but it can add up quickly.
|
||||
|
||||
### gRPC Tooling is Extensible
|
||||
|
||||
Another very useful feature of the gRPC framework is that it is extensible. If you need support for a language that is not currently supported, there is a way to create plugins for the protocol compiler that allows you to add what you need.
|
||||
|
||||
### gRPC Supports Contract Updates
|
||||
|
||||
An often overlooked aspect of service APIs is how they may evolve over time. At best, this is often a secondary consideration. If you are using gRPC, and you adhered to a few basic rules, your messages can be forward and backward compatible.
|
||||
|
||||
## Grpc-gateway — because REST will be with us for a while…
|
||||
|
||||
You’re probably thinking: gRPC is great but I have a ton of REST clients to deal with. Well, there is another tool in this ecosystem and it is called grpc-gateway. Grpc-gateway “generates a reverse-proxy server which translates a RESTful JSON API into gRPC”. So if you want to support REST clients you can, and it doesn’t cost you any real extra effort.
|
||||
If your existing REST clients are pretty far from the normal REST APIs, you can use custom marshallers with grpc-gateway to compensate.
|
||||
|
||||
## Migration and gRPC + grpc-gateway
|
||||
|
||||
As mentioned previously, we had a lot of PHP code and REST endpoints which we wanted to rework as part of the migration. By using the combination of gRPC and grpc-gateway, we were able to define gRPC versions of the legacy REST APIs and then use grpc-gateway to expose the exact REST endpoints that clients were used to. With these alternative implementations in place we were able to move traffic between the old and new systems using combinations of DNS updates as well as our [Experimentation and Configuration System](https://medium.com/yik-yak-eng/yik-yak-configuration-and-experiment-system-16a5c15ee77c#.7s11d3kqh) without causing any disruption to the existing clients. We were even able to leverage the existing test suites to verify functionality and establish parity between the old and new systems.
|
||||
Lets walk through the pieces and how they fit together.
|
||||
|
||||
Loading…
Reference in New Issue