Hello,

When using JDBC as the backend metastore for Polaris, the JDBC connection 
capacity of the active primary can become a limiting factor for the throughput 
of a given Polaris deployment. Taking RDS with PostgreSQL as an example, if we 
are using an AWS db.m9g.4xlarge (16 cores and 64 GB) as the backend store, we 
can get up to 5K connections by default. AWS uses 
"LEAST({DBInstanceClassMemory/9531392}, 5000)" for the default PostgreSQL 
"max_connections", and this can be increased manually.

Now, assuming we put 50 connections per pod, we can have up to 100 pods max (in 
reality, it will be less as a couple of connections are reserved for the 
superuser, but let's stick with 100 pods to make the math simpler).

With 50 connections per pod, this matches what Anand reported in 
https://medium.com/@obelix74/a-30-million-request-load-test-for-apache-polaris-fb5690040154.
 If we assume linear scaling from the benchmark (which may not hold due to 
database CPU, I/O, locking, and other bottlenecks), the theoretical throughput 
would be ~5,333 requests per second. This is great, but if we ever want to 
handle more throughput, we would have no other option other than scaling up the 
RDS instance and overriding the default max allowed connections on the DB 
server when using the native AWS solution. There are other solutions out there, 
such as using a multi-master deployment for the backend DB or switching to a 
NoSQL backend, which we can scale up a lot easier (e.g. MongoDB).

While there are solutions to scale up the infrastructure to support more 
connections, one particular thing that caught my eye is that we are not using 
read-only replicas at all.

Assuming we have two primary workloads:

1. Ingestion layer: this has both read and write.
2. Query/Serving layer: this is read-only queries to power various dashboards.

We could also have two DB connection endpoints:

1. RW connection endpoint (active primary)
2. RO connection endpoint (read replicas)

By default, everything should go to the RW endpoint. This matches the current 
workflow we support. However, if we know a query/serving layer is read-only, we 
should route those requests to the RO connection endpoint; the auth token 
request/renewal would still be fulfilled through the RW connection endpoint for 
the query/serving layer.

Now, to decide where a client should be sending requests to RW/RO, we can check 
the following:

1. Is this an auth request? If yes, always use the RW endpoint.
2. Does this request have a special header (e.g. "Polaris-Readonly", which 
defaults to false or unset)? If yes, offload the request to the RO endpoint.

In this case, if a non-read-only request is ever sent by the client by 
accident, it will be failed by the backend DB because writes are not supported 
on RO replicas, and this is expected behavior. We should not silently fall back 
to the RW endpoint in this case.

With this approach, we can squeeze higher requests per second out of a given 
setup by offloading the read-heavy query/serving workload from the primary. If 
the load from the query/serving layer is significant, this could give us 
another dimension to scale the infrastructure without having to keep scaling up 
the primary DB.

Thanks,
Yong Zheng

Reply via email to