Testing the limits of Supabase + Postgres in a serverless environment
In this article, we will be comparing how postgres performs on a serverless environment compared to a traditional server.
We will be testing the postgres db hosted on Supabase , so we will also test how an orm performs vs how using supabase’s js library performs.
We will be comparing the following setups in our testing.
- Cloudflare Workers + Supabase JS
- Cloudflare Workers + Drizzle ORM
- Express + Supabase JS
- Express + Drizzle ORM
So what are our assumptions going into this test?
- Since the sererless environment is ephemeral, it has to establish a new connection to the database on every request. This can be a bottleneck in some cases.
- But using supabase’s js library, the connection is done to their REST endpoint, which has a connection pool already established, a pool which can handle a million connections at once.
- On the other hand, in a traditional node server, the connection is established once and reused for every request.
- So which would be faster? Querying the db from an already established connection or querying the db from supabase js’s REST methods?
Let’s start by setting up the project.
- Create a new supabase project and run it
If all goes well, you should be able to access the supabase dashboard at http://127.0.0.1:54323
- Now lets create a new table from the SQL Editor in the dashboard
- Let’s populate the table by adding a 100000 mock users
- Let’s create the express server with the following code
- We will now create the Cloudflare Worker
After starting both the servers, you should have four routes available for testing.
- http://localhost:3000/supabasejs - Express server using supabase js
- http://localhost:3000/drizzle - Express server using drizzle orm
- http://localhost:8787/supabasejs - Cloudflare worker using supabase js
- http://localhost:8787/drizzle - Cloudflare worker using drizzle orm
Now lets create a simple script that will test the performance of these routes
- Let’s create a simple script that will test these routes. We will use autocannon for this.
Results when running the test for 60 seconds with 100 connections and 100 pipelining
Total Requests
Total number of requests for different configurations (higher is better)
Requests per Second
Number of requests per second for different configurations (higher is better)
Bytes per Second
Number of bytes transferred per second for different configurations (higher is better)
Latency
Latency for different configurations (lower is better)
Conclusion
From the results, we can see that the biggest performance bottleneck is the connection establishment in the serverless environment. This is evident from the fact that the latency for the serverless environment is much higher than the traditional server.
You can find the full source code here
Log dumps
Results for CF Workers + Supabase :
Stat | 2.5% | 50% | 97.5% | 99% | Avg | Stdev | Max |
---|---|---|---|---|---|---|---|
Latency | 1795 ms | 10858 ms | 11629 ms | 11682 ms | 10067.33 ms | 2468.93 ms | 11839 ms |
Stat | 1% | 2.5% | 50% | 97.5% | Avg | Stdev | Min |
---|---|---|---|---|---|---|---|
Req/Sec | 570 | 752 | 901 | 1,031 | 900.52 | 79.41 | 570 |
Bytes/Sec | 3.34 MB | 4.41 MB | 5.28 MB | 6.04 MB | 5.28 MB | 465 kB | 3.34 MB |
Req/Bytes counts sampled once per second.
Number of samples: 60
64k requests in 60.07s, 317 MB read
Response Codes Summary for http://localhost:8787/supabasejs :
2xx: 54031, 3xx: 0, 4xx: 0, 5xx: 0, other: 0
Results for CF Workers + Drizzle :
Stat | 2.5% | 50% | 97.5% | 99% | Avg | Stdev | Max |
---|---|---|---|---|---|---|---|
Latency | 1089 ms | 17000 ms | 34263 ms | 36654 ms | 17420.1 ms | 10019.33 ms | 42703 ms |
Stat | 1% | 2.5% | 50% | 97.5% | Avg | Stdev | Min |
---|---|---|---|---|---|---|---|
Req/Sec | 0 | 0 | 210 | 433 | 218.29 | 178.32 | 3 |
Bytes/Sec | 0 B | 0 B | 1.08 MB | 2.31 MB | 1.13 MB | 929 kB | 17.6 kB |
Req/Bytes counts sampled once per second.
Number of samples: 60
11302 2xx responses, 1795 non 2xx responses
30k requests in 60.06s, 68 MB read
7k errors (7k timeouts)
Response Codes Summary for http://localhost:8787/drizzle :
2xx: 11302, 3xx: 0, 4xx: 0, 5xx: 1795, other: 0
Results for Express.js + Supabase :
Stat | 2.5% | 50% | 97.5% | 99% | Avg | Stdev | Max |
---|---|---|---|---|---|---|---|
Latency | 105 ms | 10024 ms | 12764 ms | 13425 ms | 7111.44 ms | 4314.4 ms | 20199 ms |
Stat | 1% | 2.5% | 50% | 97.5% | Avg | Stdev | Min |
---|---|---|---|---|---|---|---|
Req/Sec | 0 | 0 | 551 | 3,431 | 976.75 | 1,094.45 | 5 |
Bytes/Sec | 0 B | 0 B | 1.57 MB | 6.23 MB | 2.12 MB | 2.07 MB | 9.69 kB |
Req/Bytes counts sampled once per second.
Number of samples: 60
19477 2xx responses, 39123 non 2xx responses
84k requests in 60.6s, 128 MB read
15k errors (15k timeouts)
Response Codes Summary for http://localhost:3000/supabasejs :
2xx: 19477, 3xx: 0, 4xx: 39123, 5xx: 0, other: 0
Results for Express.js + Drizzle :
Stat | 2.5% | 50% | 97.5% | 99% | Avg | Stdev | Max |
---|---|---|---|---|---|---|---|
Latency | 2299 ms | 2715 ms | 4045 ms | 4304 ms | 2891.94 ms | 467.38 ms | 4636 ms |
Stat | 1% | 2.5% | 50% | 97.5% | Avg | Stdev | Min |
---|---|---|---|---|---|---|---|
Req/Sec | 763 | 919 | 3,601 | 3,837 | 3,382.24 | 617.51 | 763 |
Bytes/Sec | 4.6 MB | 5.54 MB | 21.7 MB | 23.1 MB | 20.4 MB | 3.72 MB | 4.6 MB |
Req/Bytes counts sampled once per second.
Number of samples: 60
213k requests in 60.03s, 1.22 GB read
Response Codes Summary for http://localhost:3000/drizzle :
2xx: 202910, 3xx: 0, 4xx: 0, 5xx: 0, other: 0
Combined Results Comparison
Metric | CF Workers + Supabase | CF Workers + Drizzle | Express.js + Supabase | Express.js + Drizzle |
---|---|---|---|---|
Latency (ms) | ||||
2.5% | 1795 | 1089 | 105 | 2299 |
50% | 10858 | 17000 | 10024 | 2715 |
97.5% | 11629 | 34263 | 12764 | 4045 |
99% | 11682 | 36654 | 13425 | 4304 |
Avg | 10067.33 | 17420.1 | 7111.44 | 2891.94 |
Stdev | 2468.93 | 10019.33 | 4314.4 | 467.38 |
Max | 11839 | 42703 | 20199 | 4636 |
Req/Sec | ||||
1% | 570 | 0 | 0 | 763 |
2.5% | 752 | 0 | 0 | 919 |
50% | 901 | 210 | 551 | 3601 |
97.5% | 1031 | 433 | 3431 | 3837 |
Avg | 900.52 | 218.29 | 976.75 | 3382.24 |
Stdev | 79.41 | 178.32 | 1094.45 | 617.51 |
Min | 570 | 3 | 5 | 763 |
Bytes/Sec | ||||
1% | 3.34 MB | 0 B | 0 B | 4.6 MB |
2.5% | 4.41 MB | 0 B | 0 B | 5.54 MB |
50% | 5.28 MB | 1.08 MB | 1.57 MB | 21.7 MB |
97.5% | 6.04 MB | 2.31 MB | 6.23 MB | 23.1 MB |
Avg | 5.28 MB | 1.13 MB | 2.12 MB | 20.4 MB |
Stdev | 465 kB | 929 kB | 2.07 MB | 3.72 MB |
Min | 3.34 MB | 17.6 kB | 9.69 kB | 4.6 MB |
Total Requests | 64k in 60.07s | 30k in 60.06s | 84k in 60.6s | 213k in 60.03s |
Total Data | 317 MB read | 68 MB read | 128 MB read | 1.22 GB read |
Response Codes | ||||
2xx | 54031 | 11302 | 19477 | 202910 |
3xx | 0 | 0 | 0 | 0 |
4xx | 0 | 0 | 39123 | 0 |
5xx | 0 | 1795 | 0 | 0 |
Other | 0 | 0 | 0 | 0 |
Errors | 0 | 7k timeouts | 15k timeouts | 0 |