Performance Tests

Edit this page on GitHub

This directory contains files required for performance tests with the Grafana K6 REST API load and performance testing tool.

Used API's are from:

Files

  • appsettings.json - configuration for NpgsqlRest used in testing.
  • k6-api-tests.js - K6 testing script.
  • perf_tests_script.sql - database script that creates functions that are tested.
  • perf_tests.http - HTTP file for smoke tests for both systems.
  • postgrest.conf - PostgREST configuration file used in testing.
  • readme.md - this file.
  • test-script.sh - shell script that orchestrates and runs all tests.
  • results - directory with the raw dump of text files from the testing session.

Results

The number of successful requests executed by the K6 (see the k6-api-tests.js file in this dir):

  • Duration of 60 seconds.
  • 100 simultaneous virtual users.
  • Retrieval of 10 and 100 records.

Numbers represent an overall number of successful requests (higher is better).

RecordsFunctionAOT 1JIT 2PostgRESTRatio (AOT / PostgREST)Ratio (JIT / PostgREST)
10perf_test781,803562,30472,30510.817.78
100perf_test307,427303,69240,4567.607.51
10perf_test_arrays495,190384,09055,3318.956.94
100perf_test_arrays124,912127,70732,4183.853.94
10perf_test_record642,772561,85561,82510.409.09
100perf_test_record216,775227,29736,6425.926.20
10perf_test_record_arrays448,273403,90050,5798.867.99
100perf_test_record_arrays100,485112,98932,6193.083.46

Other Platforms

RecordsFunctionAOT 1JIT 2EF 3ADO 4Django 5Express 6GO 7FastAPI 8
10perf_test781,803562,304337,612440,89621,193160,24178,53013,650
100perf_test307,427303,692235,331314,19818,34558,13055,1199,666
10perf_test_arrays495,190384,090254,787309,05919,01191,987N/A11,881
100perf_test_arrays124,912127,707113,663130,47111,45217,896N/A6,192

Consolidated Results

Platform10 Records100 Records
AOT is an ahead-of-time native compilation of NpgsqlRest. NpgsqlRest compiled to the native binary.781,803307,427
JIT is a just-in-time compilation of NpgsqlRest to NET8 CLR (Common Language Runtime) on NET8 runtime.562,304303,692
ADO is NET8 Raw ADO Data Reader approach. Source440,896314,198
EF is Entity Framework Core 8 on NET8 runtime. Source337,612235,331
PostgREST version 12.0.272,30540,456
Django REST Framework 4.2.10 on Python 3.8 Source Link21,19318,345
Express on NodeJS v20.11.1, express v4.18.3, pg 8.11.3 Source Link160,24155,119
GO version go1.13.8 Source Link78,53055,119
FastAPI version 0.110.0 on Python 3.8 Source Link13,6509,666

1) AOT

NpgsqlRest .NET8 AOT build is ahead-of-time (AOT) compiled to native code. AOT has an average startup time of between 180 to 200 milliseconds.

2) JIT

NpgsqlRest JIT build is a Just-In-Time (JIT) compilation of Common Intermediate Language (CIL). JIT has an average startup time of between 360 to 400 milliseconds.

3) EF

.NET8 Npgsql.EntityFrameworkCore.PostgreSQL 8.0.2

var builder = WebApplication.CreateBuilder(args);
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

builder.Services.AddDbContext<DbContext>(options => 
    options.UseNpgsql(connectionString));

var app = builder.Build();


app.MapPost("/api/perf_test", (DbContext dbContext, [FromBody]Params p) => dbContext.Database.SqlQuery<Table>(
        $"select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test(_records => {p._records}, _text_param => {p._text_param}, _int_param => {p._int_param}, _ts_param => {p._ts_param}, _bool_param => {p._bool_param})"));

app.MapPost("/api/perf_test_arrays", (DbContext dbContext, [FromBody]Params p) => dbContext.Database.SqlQuery<TableWithArrays>(
        $"select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays(_records => {p._records}, _text_param => {p._text_param}, _int_param => {p._int_param}, _ts_param => {p._ts_param}, _bool_param => {p._bool_param})"));

app.Run();
var builder = WebApplication.CreateBuilder(args);
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

builder.Services.AddDbContext<DbContext>(options => 
    options.UseNpgsql(connectionString));

var app = builder.Build();


app.MapPost("/api/perf_test", (DbContext dbContext, [FromBody]Params p) => dbContext.Database.SqlQuery<Table>(
        $"select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test(_records => {p._records}, _text_param => {p._text_param}, _int_param => {p._int_param}, _ts_param => {p._ts_param}, _bool_param => {p._bool_param})"));

app.MapPost("/api/perf_test_arrays", (DbContext dbContext, [FromBody]Params p) => dbContext.Database.SqlQuery<TableWithArrays>(
        $"select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays(_records => {p._records}, _text_param => {p._text_param}, _int_param => {p._int_param}, _ts_param => {p._ts_param}, _bool_param => {p._bool_param})"));

app.Run();

4) ADO

.NET8 Raw Data ADO Reader:

var builder = WebApplication.CreateBuilder(args);
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

var app = builder.Build();


app.MapPost("/api/perf_test", (DbContext dbContext, [FromBody]Params p) => Data.GetTableData(p));
app.MapPost("/api/perf_test_arrays", (DbContext dbContext, [FromBody]Params p) => Data.GetTableArrayData(p));

app.Run();

static class Data
{
    public static async IAsyncEnumerable<Table> GetTableData(Params p)
    {
        using var connection = new NpgsqlConnection(connectionString);
        await connection.OpenAsync();

        using var command = connection.CreateCommand();
        command.CommandText = $"select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test(@_records, @_text_param, @_int_param, @_ts_param, @_bool_param)";

        command.Parameters.AddWithValue("_records", p._records);
        command.Parameters.AddWithValue("_text_param", p._text_param);
        command.Parameters.AddWithValue("_int_param", p._int_param);
        command.Parameters.AddWithValue("_ts_param", p._ts_param);
        command.Parameters.AddWithValue("_bool_param", p._bool_param);

        using var reader = await command.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            yield return new Table
            {
                id1 = reader.GetInt32(0),
                foo1 = reader.IsDBNull(1) ? null : reader.GetString(1),
                bar1 = reader.IsDBNull(2) ? null : reader.GetString(2),
                datetime1 = reader.GetDateTime(3),
                id2 = reader.GetInt32(4),
                foo2 = reader.IsDBNull(5) ? null : reader.GetString(5),
                bar2 = reader.IsDBNull(6) ? null : reader.GetString(6),
                datetime2 = reader.GetDateTime(7),
                long_foo_bar = reader.IsDBNull(8) ? null : reader.GetString(8),
                is_foobar = reader.GetBoolean(9)
            };
        }
    }

    public static async IAsyncEnumerable<TableWithArrays> GetTableArrayData(Params p)
    {
        using var connection = new NpgsqlConnection(connectionString);
        await connection.OpenAsync();

        using var command = connection.CreateCommand();
        command.CommandText = $"select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays(@_records, @_text_param, @_int_param, @_ts_param, @_bool_param)";

        command.Parameters.AddWithValue("_records", p._records);
        command.Parameters.AddWithValue("_text_param", p._text_param);
        command.Parameters.AddWithValue("_int_param", p._int_param);
        command.Parameters.AddWithValue("_ts_param", p._ts_param);
        command.Parameters.AddWithValue("_bool_param", p._bool_param);

        using var reader = await command.ExecuteReaderAsync();
        var result = new List<TableWithArrays>();

        while (await reader.ReadAsync())
        {
            yield return new TableWithArrays
            {
                id1 = reader.IsDBNull(0) ? null : (int[])reader.GetValue(0),
                foo1 = reader.IsDBNull(1) ? null : (string[])reader.GetValue(1),
                bar1 = reader.IsDBNull(2) ? null : (string[])reader.GetValue(2),
                datetime1 = reader.IsDBNull(3) ? null : (DateTime[])reader.GetValue(3),
                id2 = reader.IsDBNull(4) ? null : (int[])reader.GetValue(4),
                foo2 = reader.IsDBNull(5) ? null : (string[])reader.GetValue(5),
                bar2 = reader.IsDBNull(6) ? null : (string[])reader.GetValue(6),
                datetime2 = reader.IsDBNull(7) ? null : (DateTime[])reader.GetValue(7),
                long_foo_bar = reader.IsDBNull(8) ? null : (string[])reader.GetValue(8),
                is_foobar = reader.IsDBNull(9) ? null : (bool[])reader.GetValue(9)
            };
        }
    }
}
var builder = WebApplication.CreateBuilder(args);
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

var app = builder.Build();


app.MapPost("/api/perf_test", (DbContext dbContext, [FromBody]Params p) => Data.GetTableData(p));
app.MapPost("/api/perf_test_arrays", (DbContext dbContext, [FromBody]Params p) => Data.GetTableArrayData(p));

app.Run();

static class Data
{
    public static async IAsyncEnumerable<Table> GetTableData(Params p)
    {
        using var connection = new NpgsqlConnection(connectionString);
        await connection.OpenAsync();

        using var command = connection.CreateCommand();
        command.CommandText = $"select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test(@_records, @_text_param, @_int_param, @_ts_param, @_bool_param)";

        command.Parameters.AddWithValue("_records", p._records);
        command.Parameters.AddWithValue("_text_param", p._text_param);
        command.Parameters.AddWithValue("_int_param", p._int_param);
        command.Parameters.AddWithValue("_ts_param", p._ts_param);
        command.Parameters.AddWithValue("_bool_param", p._bool_param);

        using var reader = await command.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            yield return new Table
            {
                id1 = reader.GetInt32(0),
                foo1 = reader.IsDBNull(1) ? null : reader.GetString(1),
                bar1 = reader.IsDBNull(2) ? null : reader.GetString(2),
                datetime1 = reader.GetDateTime(3),
                id2 = reader.GetInt32(4),
                foo2 = reader.IsDBNull(5) ? null : reader.GetString(5),
                bar2 = reader.IsDBNull(6) ? null : reader.GetString(6),
                datetime2 = reader.GetDateTime(7),
                long_foo_bar = reader.IsDBNull(8) ? null : reader.GetString(8),
                is_foobar = reader.GetBoolean(9)
            };
        }
    }

    public static async IAsyncEnumerable<TableWithArrays> GetTableArrayData(Params p)
    {
        using var connection = new NpgsqlConnection(connectionString);
        await connection.OpenAsync();

        using var command = connection.CreateCommand();
        command.CommandText = $"select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays(@_records, @_text_param, @_int_param, @_ts_param, @_bool_param)";

        command.Parameters.AddWithValue("_records", p._records);
        command.Parameters.AddWithValue("_text_param", p._text_param);
        command.Parameters.AddWithValue("_int_param", p._int_param);
        command.Parameters.AddWithValue("_ts_param", p._ts_param);
        command.Parameters.AddWithValue("_bool_param", p._bool_param);

        using var reader = await command.ExecuteReaderAsync();
        var result = new List<TableWithArrays>();

        while (await reader.ReadAsync())
        {
            yield return new TableWithArrays
            {
                id1 = reader.IsDBNull(0) ? null : (int[])reader.GetValue(0),
                foo1 = reader.IsDBNull(1) ? null : (string[])reader.GetValue(1),
                bar1 = reader.IsDBNull(2) ? null : (string[])reader.GetValue(2),
                datetime1 = reader.IsDBNull(3) ? null : (DateTime[])reader.GetValue(3),
                id2 = reader.IsDBNull(4) ? null : (int[])reader.GetValue(4),
                foo2 = reader.IsDBNull(5) ? null : (string[])reader.GetValue(5),
                bar2 = reader.IsDBNull(6) ? null : (string[])reader.GetValue(6),
                datetime2 = reader.IsDBNull(7) ? null : (DateTime[])reader.GetValue(7),
                long_foo_bar = reader.IsDBNull(8) ? null : (string[])reader.GetValue(8),
                is_foobar = reader.IsDBNull(9) ? null : (bool[])reader.GetValue(9)
            };
        }
    }
}

5) Django

Django REST Framework 4.2.10 on Python 3.8

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import connection

class PerfTestView(APIView):
    def post(self, request):
        records = request.data.get('_records', 10)
        text_param = request.data.get('_text_param', 'abcxyz')
        int_param = request.data.get('_int_param', 999)
        ts_param = request.data.get('_ts_param', '2024-01-01')
        bool_param = request.data.get('_bool_param', True)

        with connection.cursor() as cursor:
            cursor.execute(
                "select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test(_records => %s, _text_param => %s, _int_param => %s, _ts_param => %s, _bool_param => %s)",
                [records, text_param, int_param, ts_param, bool_param])
            data = cursor.fetchall()

        columns = [col[0] for col in cursor.description]
        return Response([dict(zip(columns, row)) for row in data])

class PerfTestArrays(APIView):
    def post(self, request):
        records = request.data.get('_records', 10)
        text_param = request.data.get('_text_param', 'abcxyz')
        int_param = request.data.get('_int_param', 999)
        ts_param = request.data.get('_ts_param', '2024-01-01')
        bool_param = request.data.get('_bool_param', True)

        with connection.cursor() as cursor:
            cursor.execute(
                "select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays(_records => %s, _text_param => %s, _int_param => %s, _ts_param => %s, _bool_param => %s)",
                [records, text_param, int_param, ts_param, bool_param])
            data = cursor.fetchall()

        columns = [col[0] for col in cursor.description]
        return Response([dict(zip(columns, row)) for row in data])
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import connection

class PerfTestView(APIView):
    def post(self, request):
        records = request.data.get('_records', 10)
        text_param = request.data.get('_text_param', 'abcxyz')
        int_param = request.data.get('_int_param', 999)
        ts_param = request.data.get('_ts_param', '2024-01-01')
        bool_param = request.data.get('_bool_param', True)

        with connection.cursor() as cursor:
            cursor.execute(
                "select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test(_records => %s, _text_param => %s, _int_param => %s, _ts_param => %s, _bool_param => %s)",
                [records, text_param, int_param, ts_param, bool_param])
            data = cursor.fetchall()

        columns = [col[0] for col in cursor.description]
        return Response([dict(zip(columns, row)) for row in data])

class PerfTestArrays(APIView):
    def post(self, request):
        records = request.data.get('_records', 10)
        text_param = request.data.get('_text_param', 'abcxyz')
        int_param = request.data.get('_int_param', 999)
        ts_param = request.data.get('_ts_param', '2024-01-01')
        bool_param = request.data.get('_bool_param', True)

        with connection.cursor() as cursor:
            cursor.execute(
                "select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays(_records => %s, _text_param => %s, _int_param => %s, _ts_param => %s, _bool_param => %s)",
                [records, text_param, int_param, ts_param, bool_param])
            data = cursor.fetchall()

        columns = [col[0] for col in cursor.description]
        return Response([dict(zip(columns, row)) for row in data])

6) Express

NodeJS v20.11.1, express v4.18.3, pg 8.11.3

app.post('/api/perf_test', async (req, res) => {
  try {
    const { _records, _text_param, _int_param, _ts_param, _bool_param } = req.body;
    const queryResult = await pool.query(
      'select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test($1, $2, $3, $4, $5)', 
      [_records, _text_param, _int_param, _ts_param, _bool_param]);
    res.json(queryResult.rows);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.post('/api/perf_test_arrays', async (req, res) => {
  try {
    const { _records, _text_param, _int_param, _ts_param, _bool_param } = req.body;
    const queryResult = await pool.query(
      'select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays($1, $2, $3, $4, $5)', 
      [_records, _text_param, _int_param, _ts_param, _bool_param]);
    res.json(queryResult.rows);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
app.post('/api/perf_test', async (req, res) => {
  try {
    const { _records, _text_param, _int_param, _ts_param, _bool_param } = req.body;
    const queryResult = await pool.query(
      'select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test($1, $2, $3, $4, $5)', 
      [_records, _text_param, _int_param, _ts_param, _bool_param]);
    res.json(queryResult.rows);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.post('/api/perf_test_arrays', async (req, res) => {
  try {
    const { _records, _text_param, _int_param, _ts_param, _bool_param } = req.body;
    const queryResult = await pool.query(
      'select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays($1, $2, $3, $4, $5)', 
      [_records, _text_param, _int_param, _ts_param, _bool_param]);
    res.json(queryResult.rows);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

7) GO

go version go1.13.8

Note: array function endpoint tests are skipped.

package main

import (
    "database/sql"
    "encoding/json"
    "log"
    "net/http"

    "github.com/gorilla/mux"
    _ "github.com/lib/pq"
)

const (
    host     = "127.0.0.1"
    port     = "5432"
    user     = "postgres"
    password = "postgres"
    dbname   = "perf_tests"
)

type PerfTestResult struct {
    ID1           int     `json:"id1"`
    Foo1          string  `json:"foo1"`
    Bar1          string  `json:"bar1"`
    Datetime1     string  `json:"datetime1"`
    ID2           int     `json:"id2"`
    Foo2          string  `json:"foo2"`
    Bar2          string  `json:"bar2"`
    Datetime2     string  `json:"datetime2"`
    LongFooBar    string  `json:"long_foo_bar"`
    IsFooBar      bool    `json:"is_foobar"`
}

func main() {
    // Initialize a new router
    router := mux.NewRouter()

    // Define your endpoint
    router.HandleFunc("/api/perf_test", PerfTestFunction).Methods("POST")

    // Start the server
    log.Fatal(http.ListenAndServe(":8080", router))
}

func PerfTestFunction(w http.ResponseWriter, r *http.Request) {
    // Parse JSON parameters from request body
    var params map[string]interface{}
    if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // Connect to PostgreSQL database
    connStr := "host=" + host + " port=" + port + " user=" + user + " password=" + password + " dbname=" + dbname + " sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer db.Close()

    // Call PostgreSQL function
    rows, err := db.Query("SELECT id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test($1, $2, $3, $4, $5)", 
        params["_records"], params["_text_param"], params["_int_param"], params["_ts_param"], params["_bool_param"])
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    // Prepare the result slice
    var results []PerfTestResult

    // Iterate over the rows returned by the query
    for rows.Next() {
        var result PerfTestResult
        if err := rows.Scan(
            &result.ID1, &result.Foo1, &result.Bar1, &result.Datetime1,
            &result.ID2, &result.Foo2, &result.Bar2, &result.Datetime2,
            &result.LongFooBar, &result.IsFooBar,
        ); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        results = append(results, result)
    }

    // Check for errors during row iteration
    if err := rows.Err(); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Convert the result slice to JSON
    jsonResponse, err := json.Marshal(results)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Set Content-Type header and write response
    w.Header().Set("Content-Type", "application/json")
    w.Write(jsonResponse)
}
package main

import (
    "database/sql"
    "encoding/json"
    "log"
    "net/http"

    "github.com/gorilla/mux"
    _ "github.com/lib/pq"
)

const (
    host     = "127.0.0.1"
    port     = "5432"
    user     = "postgres"
    password = "postgres"
    dbname   = "perf_tests"
)

type PerfTestResult struct {
    ID1           int     `json:"id1"`
    Foo1          string  `json:"foo1"`
    Bar1          string  `json:"bar1"`
    Datetime1     string  `json:"datetime1"`
    ID2           int     `json:"id2"`
    Foo2          string  `json:"foo2"`
    Bar2          string  `json:"bar2"`
    Datetime2     string  `json:"datetime2"`
    LongFooBar    string  `json:"long_foo_bar"`
    IsFooBar      bool    `json:"is_foobar"`
}

func main() {
    // Initialize a new router
    router := mux.NewRouter()

    // Define your endpoint
    router.HandleFunc("/api/perf_test", PerfTestFunction).Methods("POST")

    // Start the server
    log.Fatal(http.ListenAndServe(":8080", router))
}

func PerfTestFunction(w http.ResponseWriter, r *http.Request) {
    // Parse JSON parameters from request body
    var params map[string]interface{}
    if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // Connect to PostgreSQL database
    connStr := "host=" + host + " port=" + port + " user=" + user + " password=" + password + " dbname=" + dbname + " sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer db.Close()

    // Call PostgreSQL function
    rows, err := db.Query("SELECT id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test($1, $2, $3, $4, $5)", 
        params["_records"], params["_text_param"], params["_int_param"], params["_ts_param"], params["_bool_param"])
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    // Prepare the result slice
    var results []PerfTestResult

    // Iterate over the rows returned by the query
    for rows.Next() {
        var result PerfTestResult
        if err := rows.Scan(
            &result.ID1, &result.Foo1, &result.Bar1, &result.Datetime1,
            &result.ID2, &result.Foo2, &result.Bar2, &result.Datetime2,
            &result.LongFooBar, &result.IsFooBar,
        ); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        results = append(results, result)
    }

    // Check for errors during row iteration
    if err := rows.Err(); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Convert the result slice to JSON
    jsonResponse, err := json.Marshal(results)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Set Content-Type header and write response
    w.Header().Set("Content-Type", "application/json")
    w.Write(jsonResponse)
}

8) FastAPI

FastAPI 0.110.0 on Python 3.8

@app.post("/api/perf_test")
async def perf_test(request: Request):
    conn = get_db_connection()
    cursor = conn.cursor()
    json = await request.json()
    with conn.cursor() as cursor:
        cursor.execute(
            "select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test(_records => %s, _text_param => %s, _int_param => %s, _ts_param => %s, _bool_param => %s)",
            [json["_records"], json["_text_param"], json["_int_param"], json["_ts_param"], json["_bool_param"]])
        return cursor.fetchall()

@app.post("/api/perf_test_arrays")
async def perf_test(request: Request):
    conn = get_db_connection()
    cursor = conn.cursor()
    json = await request.json()
    with conn.cursor() as cursor:
        cursor.execute(
            "select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays(_records => %s, _text_param => %s, _int_param => %s, _ts_param => %s, _bool_param => %s)",
            [json["_records"], json["_text_param"], json["_int_param"], json["_ts_param"], json["_bool_param"]])
        return cursor.fetchall()
@app.post("/api/perf_test")
async def perf_test(request: Request):
    conn = get_db_connection()
    cursor = conn.cursor()
    json = await request.json()
    with conn.cursor() as cursor:
        cursor.execute(
            "select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test(_records => %s, _text_param => %s, _int_param => %s, _ts_param => %s, _bool_param => %s)",
            [json["_records"], json["_text_param"], json["_int_param"], json["_ts_param"], json["_bool_param"]])
        return cursor.fetchall()

@app.post("/api/perf_test_arrays")
async def perf_test(request: Request):
    conn = get_db_connection()
    cursor = conn.cursor()
    json = await request.json()
    with conn.cursor() as cursor:
        cursor.execute(
            "select id1, foo1, bar1, datetime1, id2, foo2, bar2, datetime2, long_foo_bar, is_foobar from perf_test_arrays(_records => %s, _text_param => %s, _int_param => %s, _ts_param => %s, _bool_param => %s)",
            [json["_records"], json["_text_param"], json["_int_param"], json["_ts_param"], json["_bool_param"]])
        return cursor.fetchall()

Tests Functions

create or replace function perf_test(
    _records int,
    _text_param text,
    _int_param int,
    _ts_param timestamp,
    _bool_param bool
) 
returns table(
    id1 int, 
    foo1 text, 
    bar1 text, 
    datetime1 timestamp, 
    id2 int, 
    foo2 text, 
    bar2 text, 
    datetime2 timestamp,
    long_foo_bar text, 
    is_foobar bool
)
language sql
as
$$
select
    i + _int_param as id1,
    'foo' || '_' || _text_param || '_' || i::text as foo1,
        'bar' || i::text as bar1,
        (_ts_param::date) + (i::text || ' days')::interval as datetime1,
        i+1 + _int_param as id2,
    'foo' || '_' || _text_param || '_' || (i+1)::text as foo2,
        'bar' || '_' || _text_param || '_' || (i+1)::text as bar2,
        (_ts_param::date) + ((i+1)::text || ' days')::interval as datetime2,
    'long_foo_bar_' || '_' || _text_param || '_' || (i+2)::text as long_foo_bar, 
    (i % 2)::boolean and _bool_param as is_foobar
from
    generate_series(1, _records) as i
$$;
create or replace function perf_test(
    _records int,
    _text_param text,
    _int_param int,
    _ts_param timestamp,
    _bool_param bool
) 
returns table(
    id1 int, 
    foo1 text, 
    bar1 text, 
    datetime1 timestamp, 
    id2 int, 
    foo2 text, 
    bar2 text, 
    datetime2 timestamp,
    long_foo_bar text, 
    is_foobar bool
)
language sql
as
$$
select
    i + _int_param as id1,
    'foo' || '_' || _text_param || '_' || i::text as foo1,
        'bar' || i::text as bar1,
        (_ts_param::date) + (i::text || ' days')::interval as datetime1,
        i+1 + _int_param as id2,
    'foo' || '_' || _text_param || '_' || (i+1)::text as foo2,
        'bar' || '_' || _text_param || '_' || (i+1)::text as bar2,
        (_ts_param::date) + ((i+1)::text || ' days')::interval as datetime2,
    'long_foo_bar_' || '_' || _text_param || '_' || (i+2)::text as long_foo_bar, 
    (i % 2)::boolean and _bool_param as is_foobar
from
    generate_series(1, _records) as i
$$;
create or replace function perf_test_arrays(
    _records int,
    _text_param text,
    _int_param int,
    _ts_param timestamp,
    _bool_param bool
) 
returns table(
    id1 int[], 
    foo1 text[], 
    bar1 text[], 
    datetime1 timestamp[], 
    id2 int[], 
    foo2 text[], 
    bar2 text[], 
    datetime2 timestamp[],
    long_foo_bar text[], 
    is_foobar bool[]
)
language sql
as
$$
select
    array[i + _int_param, i + _int_param + 1, i + _int_param + 2] as id1, 
    array['foo' || '_' || _text_param || '_' || i::text, 'foo2', 'foo3'] as foo1, 
    array['bar' || i::text, 'bar2', 'bar3'] as bar1, 
    array[(_ts_param::date) + (i::text || ' days')::interval, _ts_param::date + '1 days'::interval, _ts_param::date + '2 days'::interval] as datetime1, 
    array[i+1 + _int_param, i+2 + _int_param, i+3 + _int_param] as id2, 
    array['foo' || '_' || _text_param || '_' || (i+1)::text, 'foo' || '_' || _text_param || '_' || (i+2)::text, 'foo' || '_' || _text_param || '_' || (i+3)::text] as foo2, 
    array['bar' || '_' || _text_param || '_' || (i+1)::text, 'bar' || '_' || _text_param || '_' || (i+2)::text, 'bar' || '_' || _text_param || '_' || (i+3)::text] as bar2, 
    array[(_ts_param::date) + ((i+1)::text || ' days')::interval, _ts_param::date + '1 days'::interval, _ts_param::date + '2 days'::interval] as datetime2,
    array['long_foo_bar_' || '_' || _text_param || '_' || (i+2)::text, 'long_foo_bar_' || '_' || _text_param || '_' || (i+3)::text, 'long_foo_bar_' || '_' || _text_param || '_' || (i+4)::text] as long_foo_bar, 
    array[(i % 2)::boolean and _bool_param, ((i+1) % 2)::boolean and _bool_param, ((i+2) % 2)::boolean and _bool_param] as is_foobar
from
    generate_series(1, _records) as i
$$;
create or replace function perf_test_arrays(
    _records int,
    _text_param text,
    _int_param int,
    _ts_param timestamp,
    _bool_param bool
) 
returns table(
    id1 int[], 
    foo1 text[], 
    bar1 text[], 
    datetime1 timestamp[], 
    id2 int[], 
    foo2 text[], 
    bar2 text[], 
    datetime2 timestamp[],
    long_foo_bar text[], 
    is_foobar bool[]
)
language sql
as
$$
select
    array[i + _int_param, i + _int_param + 1, i + _int_param + 2] as id1, 
    array['foo' || '_' || _text_param || '_' || i::text, 'foo2', 'foo3'] as foo1, 
    array['bar' || i::text, 'bar2', 'bar3'] as bar1, 
    array[(_ts_param::date) + (i::text || ' days')::interval, _ts_param::date + '1 days'::interval, _ts_param::date + '2 days'::interval] as datetime1, 
    array[i+1 + _int_param, i+2 + _int_param, i+3 + _int_param] as id2, 
    array['foo' || '_' || _text_param || '_' || (i+1)::text, 'foo' || '_' || _text_param || '_' || (i+2)::text, 'foo' || '_' || _text_param || '_' || (i+3)::text] as foo2, 
    array['bar' || '_' || _text_param || '_' || (i+1)::text, 'bar' || '_' || _text_param || '_' || (i+2)::text, 'bar' || '_' || _text_param || '_' || (i+3)::text] as bar2, 
    array[(_ts_param::date) + ((i+1)::text || ' days')::interval, _ts_param::date + '1 days'::interval, _ts_param::date + '2 days'::interval] as datetime2,
    array['long_foo_bar_' || '_' || _text_param || '_' || (i+2)::text, 'long_foo_bar_' || '_' || _text_param || '_' || (i+3)::text, 'long_foo_bar_' || '_' || _text_param || '_' || (i+4)::text] as long_foo_bar, 
    array[(i % 2)::boolean and _bool_param, ((i+1) % 2)::boolean and _bool_param, ((i+2) % 2)::boolean and _bool_param] as is_foobar
from
    generate_series(1, _records) as i
$$;
create or replace function perf_test_record(
    _records int,
    _text_param text,
    _int_param int,
    _ts_param timestamp,
    _bool_param bool
) 
returns setof record
language sql
as
$$
select
    i + _int_param as id1,
    'foo' || '_' || _text_param || '_' || i::text as foo1,
        'bar' || i::text as bar1,
        (_ts_param::date) + (i::text || ' days')::interval as datetime1,
        i+1 + _int_param as id2,
    'foo' || '_' || _text_param || '_' || (i+1)::text as foo2,
        'bar' || '_' || _text_param || '_' || (i+1)::text as bar2,
        (_ts_param::date) + ((i+1)::text || ' days')::interval as datetime2,
    'long_foo_bar_' || '_' || _text_param || '_' || (i+2)::text as long_foo_bar, 
    (i % 2)::boolean and _bool_param as is_foobar
from
    generate_series(1, _records) as i
$$;
create or replace function perf_test_record(
    _records int,
    _text_param text,
    _int_param int,
    _ts_param timestamp,
    _bool_param bool
) 
returns setof record
language sql
as
$$
select
    i + _int_param as id1,
    'foo' || '_' || _text_param || '_' || i::text as foo1,
        'bar' || i::text as bar1,
        (_ts_param::date) + (i::text || ' days')::interval as datetime1,
        i+1 + _int_param as id2,
    'foo' || '_' || _text_param || '_' || (i+1)::text as foo2,
        'bar' || '_' || _text_param || '_' || (i+1)::text as bar2,
        (_ts_param::date) + ((i+1)::text || ' days')::interval as datetime2,
    'long_foo_bar_' || '_' || _text_param || '_' || (i+2)::text as long_foo_bar, 
    (i % 2)::boolean and _bool_param as is_foobar
from
    generate_series(1, _records) as i
$$;
create or replace function perf_test_record_arrays(
    _records int,
    _text_param text,
    _int_param int,
    _ts_param timestamp,
    _bool_param bool
) 
returns setof record
language sql
as
$$
select
    array[i + _int_param, i + _int_param + 1, i + _int_param + 2] as id1, 
    array['foo' || '_' || _text_param || '_' || i::text, 'foo2', 'foo3'] as foo1, 
    array['bar' || i::text, 'bar2', 'bar3'] as bar1, 
    array[(_ts_param::date) + (i::text || ' days')::interval, _ts_param::date + '1 days'::interval, _ts_param::date + '2 days'::interval] as datetime1, 
    array[i+1 + _int_param, i+2 + _int_param, i+3 + _int_param] as id2, 
    array['foo' || '_' || _text_param || '_' || (i+1)::text, 'foo' || '_' || _text_param || '_' || (i+2)::text, 'foo' || '_' || _text_param || '_' || (i+3)::text] as foo2, 
    array['bar' || '_' || _text_param || '_' || (i+1)::text, 'bar' || '_' || _text_param || '_' || (i+2)::text, 'bar' || '_' || _text_param || '_' || (i+3)::text] as bar2, 
    array[(_ts_param::date) + ((i+1)::text || ' days')::interval, _ts_param::date + '1 days'::interval, _ts_param::date + '2 days'::interval] as datetime2,
    array['long_foo_bar_' || '_' || _text_param || '_' || (i+2)::text, 'long_foo_bar_' || '_' || _text_param || '_' || (i+3)::text, 'long_foo_bar_' || '_' || _text_param || '_' || (i+4)::text] as long_foo_bar, 
    array[(i % 2)::boolean and _bool_param, ((i+1) % 2)::boolean and _bool_param, ((i+2) % 2)::boolean and _bool_param] as is_foobar
from
    generate_series(1, _records) as i
$$;
create or replace function perf_test_record_arrays(
    _records int,
    _text_param text,
    _int_param int,
    _ts_param timestamp,
    _bool_param bool
) 
returns setof record
language sql
as
$$
select
    array[i + _int_param, i + _int_param + 1, i + _int_param + 2] as id1, 
    array['foo' || '_' || _text_param || '_' || i::text, 'foo2', 'foo3'] as foo1, 
    array['bar' || i::text, 'bar2', 'bar3'] as bar1, 
    array[(_ts_param::date) + (i::text || ' days')::interval, _ts_param::date + '1 days'::interval, _ts_param::date + '2 days'::interval] as datetime1, 
    array[i+1 + _int_param, i+2 + _int_param, i+3 + _int_param] as id2, 
    array['foo' || '_' || _text_param || '_' || (i+1)::text, 'foo' || '_' || _text_param || '_' || (i+2)::text, 'foo' || '_' || _text_param || '_' || (i+3)::text] as foo2, 
    array['bar' || '_' || _text_param || '_' || (i+1)::text, 'bar' || '_' || _text_param || '_' || (i+2)::text, 'bar' || '_' || _text_param || '_' || (i+3)::text] as bar2, 
    array[(_ts_param::date) + ((i+1)::text || ' days')::interval, _ts_param::date + '1 days'::interval, _ts_param::date + '2 days'::interval] as datetime2,
    array['long_foo_bar_' || '_' || _text_param || '_' || (i+2)::text, 'long_foo_bar_' || '_' || _text_param || '_' || (i+3)::text, 'long_foo_bar_' || '_' || _text_param || '_' || (i+4)::text] as long_foo_bar, 
    array[(i % 2)::boolean and _bool_param, ((i+1) % 2)::boolean and _bool_param, ((i+2) % 2)::boolean and _bool_param] as is_foobar
from
    generate_series(1, _records) as i
$$;
Comments