ButterDog
ButterDog

Reputation: 5245

Combining gRPC standard and async methods in the same Python server

I have a gRPC server, written in Python 3.6. This server follows the pattern described in the gRPC examples as follows:

from concurrent import futures
import logging

import grpc

import helloworld_pb2
import helloworld_pb2_grpc


class Greeter(helloworld_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)

    # ...

    def ReadRemoteData(self, request, context):
        return fetch_some_data_io_bound()


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()


if __name__ == '__main__':
    logging.basicConfig()
    serve()

In my server application, there are a lot more methods, but in general is more of the same.

There are a number of these methods that are currently a bottleneck for my server and I would like to optimize those by running them using async (these methods spend good time waiting on IO so they are a good candidate).

The newest versions of grpc now support async as seen in this example, by creating an async server with server = grpc.aio.server().

My problem is the following:

The server has a lot of gRPC methods, many of which are very complex. I would like to avoid rewriting any method that is not currently a bottleneck and leave them as they are. I would like to rewrite only those methods that would benefit from an async implementation, which is just a small fraction of the total. I can't change the .proto definition to split the service into async/non-async, for backwards-compatibility reasons.

The question is, is it possible to combine, somehow, async and non async methods in the same gRPC service?

Upvotes: 2

Views: 2432

Answers (1)

Lidi Zheng
Lidi Zheng

Reputation: 2091

Yes, it's possible. The application needs to create the asyncio server with an extra argument which is migration_thread_pool. This is indeed an essential feature for migration.

See documentation at https://grpc.github.io/grpc/python/grpc_asyncio.html#grpc.aio.server

import asyncio
from concurrent import futures
import logging

import grpc

import helloworld_pb2
import helloworld_pb2_grpc


class Greeter(helloworld_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)

    # ...

    def ReadRemoteData(self, request, context):
        return fetch_some_data_io_bound()


async def serve():
    server = grpc.aio.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    await server.start()
    await server.wait_for_termination()


if __name__ == '__main__':
    logging.basicConfig()
    asyncio.run(serve())

Upvotes: 4

Related Questions