Minimal example of an HTTP server executing asynchronous database queries?

I play with different asynchronous HTTP servers to see how they can handle multiple concurrent connections. To force a time-consuming I / O operation, I use the pg_sleepPostgreSQL function to emulate a time-consuming database query. Here, for example, what I did with Node.js:

var http = require('http');
var pg = require('pg');
var conString = "postgres://al:al@localhost/al";
/* SQL query that takes a long time to complete */
var slowQuery = 'SELECT 42 as number, pg_sleep(0.300);';

var server = http.createServer(function(req, res) {
  pg.connect(conString, function(err, client, done) {
    client.query(slowQuery, [], function(err, result) {
      done();
      res.writeHead(200, {'content-type': 'text/plain'});
      res.end("Result: " + result.rows[0].number);
    });
  });
})

console.log("Serve http://127.0.0.1:3001/")
server.listen(3001)

So, this is a very simple query handler that executes an SQL query that takes 300 ms and returns a response. When I try to benchmark, I get the following results:

$ ab -n 20 -c 10 http://127.0.0.1:3001/
Time taken for tests:   0.678 seconds
Complete requests:      20
Requests per second:    29.49 [#/sec] (mean)
Time per request:       339.116 [ms] (mean)

This clearly shows that requests are executed in parallel. Each request takes 300 ms, and because we have two batches of 10 requests executed in parallel, it takes 600 ms in total.

Elixir, , - . :

defmodule Toto do
  import Plug.Conn

  def init(options) do
    {:ok, pid} = Postgrex.Connection.start_link(
      username: "al", password: "al", database: "al")
    options ++ [pid: pid]
  end

  def call(conn, opts) do
    sql = "SELECT 42, pg_sleep(0.300);"
    result = Postgrex.Connection.query!(opts[:pid], sql, [])
    [{value, _}] = result.rows
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "Result: #{value}")
  end
end

, , :

defmodule Toto.Supervisor do
  use Application

  def start(type, args) do
    import Supervisor.Spec, warn: false

    children = [
      worker(Plug.Adapters.Cowboy, [Toto, []], function: :http),
    ]
    opts = [strategy: :one_for_one, name: Toto.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

, :

$ ab -n 20 -c 10 http://127.0.0.1:4000/
Time taken for tests:   6.056 seconds
Requests per second:    3.30 [#/sec] (mean)
Time per request:       3028.038 [ms] (mean)

, parallelism, . ?

+4
3

. , node.js . Elixir init ( !), , Postgres , .

- Postgres init call. , Ecto, . .

+8

UPDATE , - , @AlexMarandon Ecto pool.

, :

defmodule Toto do
  import Plug.Conn

  def init(options) do
    options
  end

  def call(conn, opts) do
    { :ok, pid } = Postgrex.Connection.start_link(username: "chris", password: "", database: "ecto_test")
    sql = "SELECT 42, pg_sleep(0.300);"
    result = Postgrex.Connection.query!(pid, sql, [])
    [{value, _}] = result.rows
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "Result: #{value}")
  end
end

:

% ab -n 20 -c 10 http://127.0.0.1:4000/
Time taken for tests:   0.832 seconds
Requests per second:    24.05 [#/sec] (mean)
Time per request:       415.818 [ms] (mean)
+5

, José answer:

defmodule Toto do
  import Plug.Conn

  def init(options) do
    options
  end

  def call(conn, _opts) do
    sql = "SELECT 42, pg_sleep(0.300);"
    result = Ecto.Adapters.SQL.query(Repo, sql, [])
    [{value, _}] = result.rows
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "Result: #{value}")
  end
end

:

defmodule Repo do
  use Ecto.Repo, otp_app: :toto
end

:

defmodule Toto.Supervisor do
  use Application

  def start(type, args) do
    import Supervisor.Spec, warn: false

    children = [
      worker(Plug.Adapters.Cowboy, [Toto, []], function: :http),
      worker(Repo, [])
    ]
    opts = [strategy: :one_for_one, name: Toto.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

, , :

config :toto, Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "al",
  username: "al",
  password: "al",
  size: 10,
  lazy: false

( , "" ) :

$ ab -n 20 -c 10 http://127.0.0.1:4000/
Time taken for tests:   0.874 seconds
Requests per second:    22.89 [#/sec] (mean)
Time per request:       436.890 [ms] (mean)

size: 10 lazy: false:

$ ab -n 20 -c 10 http://127.0.0.1:4000/
Time taken for tests:   0.619 seconds
Requests per second:    32.30 [#/sec] (mean)
Time per request:       309.564 [ms] (mean)
+3
source

All Articles