Building High Performance gRPC Node.JS Framework

Overview

Anderson
8 min readMay 25, 2021
  • gRPC Node
  • gRPC Node Performance Best Practices
  • Proto Loader
  • gRPC Node Framework
  • Request Validation
  • Logger
  • gRPC Health Checking Protocol

gRPC Node

@grpc/grpc-js

grpc-node is only one node.js gRPC implementation. You can use @grpc/grpc-js or grpc (legacy)as your server. As we know, @grpc/grpc-js is slower than grpc (legacy) because pure javascript could not beat C addon.

gRPC Node Performance Best Practices

Connection keep-alive

Default client/server keep-alive ping is 2 hours. We need to keep HTTP/2 connections alive during periods of inactivity to allow initial RPCs to be made quickly without a delay.

Re-use stubs and channels

Re-use the stubs and channels when possible. Different stubs with identical parameters use the same tcp connection (stackoverflow). If your application with high load or long-lived streaming RPCs, you can use local subchannel will earn performance.

Compression

@grpc/grpc-js support several compression algorithms and levels. Use compression will earn network efficiency.

# re-use stub
const client = new hello_proto.Greeter(
"0.0.0.0:50051",
grpc.credentials.createInsecure(),
{
# send keepalive ping every 10 second, default is 2 hours
"grpc.keepalive_time_ms": 10000,
# keepalive ping timeout after 5 seconds, default is 20 seoncds
"grpc.keepalive_timeout_ms": 5000,
# allow keepalive pings when there's no gRPC calls
"grpc.keepalive_permit_without_calls": 1,
# 0: multi clients use global subchannel pool
# 1: applications with high load or long-lived streaming RPCs use local subchannel will earn performance
"grpc.use_local_subchannel_pool": 0,
# 0: no compression
# 1: deflate
# 2: gzip
# 3: gzip stream
"grpc.default_compression_algorithm": 2
# 0: none
# 1: low level (gzip-3)
# 2: medium level (gzip-6)
# 3: high level (gzip-9)

"grpc.default_compression_level": 2
}
);

Proto Loader

protobuf.js

protobuf.js & google-protobuf are both used to protobuf serialization. protobuf.js are 8x faster than google-protobuf serialization (benchmarks). It’s recommend protobuf.js as gRPC protocol buffers implementation.

However, it’s lack of support Struct/Map/Any data structure. Use nested object with dynamic key as response is not a good idea. Repeated is a good choice as your data structure.

# bad data structure
# it's not easy to define in proto3 because protobuf.js is lack of support struct, map & any
{
"users": {
# dynamic key by name
"mali": {
"id": 1,
"name": "mali",
"age": 18,
"friends": {
# dynamic key by name
"andy": {
"id": 2,
"name": "andy"
},
},
},
},
}
# array of objects is good choice
{
"users": [
{
"id": 1,
"name": "mali",
"age": 18,
"friends": [
{
"id": 2,
"name": "andy"
}
]
}
]
}

gRPC Node Framework

Mali

Mali and Nest.JS are good frameworks which provide features like middleware and error handling. Low overhead is required for building microservice. Mali is an minimalistic implementation and performant framework. It is more confident if Mali can provide benchmarks with bare @grpc/grpc-js (issue#271).

Request Validation

Ajv

Ajv is the fastest JSON schema validator (benchmarks). It recommended to use a single Ajv instance for the whole application and pre-compile schemas.

Memory Efficiency Best Practices

const Ajv = require("ajv");# single ajv instance
const ajv = new Ajv({ coerceTypes: true });
module.exports = function (schema, name) {
# use ajv instance cache to have all compiled validators
ajv.addSchema(schema, name);
return ajv.getSchema(name);
}

Logger

Pino v7+

Pino is the fastest logger (benchmarks) than others. v7 transport use libuv worker thread to deliver best performance (benchmarks). You can use async destination which also perform good performance.

Performance Best Practices

const pino = require("pino");// async
const logger = pino(pino.destination({ sync: false }));
// transport
const logger = pino({
transport: {
target: "pino/file"
},
});

gRPC Health Checking Protocol

gRPC Health Checking Protocol

gRPC Health Checking Protocol is provided to implement health check rpc. If your kubernetes version is not upgrade to 1.24, gRPC Health Prob Bin is provided to ping rpc.

Availability Best Practices

K8S liveness probes using gRPC Health Prob Bin, gRPC Health Checking Protocol is also implemented will earn availability.

livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]

With kubernetes 1.24, you can use

livenessProbe:
grpc:
port: 50051

--

--

Anderson
Anderson

No responses yet