Current section
Files
Jump to
Current section
Files
bench/postgres.exs
unless url = System.get_env("CHRONICLE_BENCH_DATABASE_URL") do
raise """
CHRONICLE_BENCH_DATABASE_URL is required.
Use a disposable PostgreSQL database. The benchmark creates and drops a
uniquely named schema, including all data below it.
"""
end
defmodule Chronicle.Bench.Repo do
use Ecto.Repo,
otp_app: :chronicle,
adapter: Ecto.Adapters.Postgres
end
defmodule Chronicle.Bench.Item do
use Ecto.Schema
@primary_key {:id, :binary_id, autogenerate: true}
schema "chronicle_bench_items" do
field :value, :integer
end
end
parse_positive = fn name, default ->
case System.get_env(name) do
nil ->
default
value ->
case Integer.parse(value) do
{integer, ""} when integer > 0 -> integer
_ -> raise "#{name} must be a positive integer"
end
end
end
writes = parse_positive.("CHRONICLE_BENCH_WRITES", 1_000)
concurrency = parse_positive.("CHRONICLE_BENCH_CONCURRENCY", 16)
pool_size = max(concurrency + 2, 10)
prefix = "chronicle_bench_#{System.unique_integer([:positive, :monotonic])}"
{:ok, _repo} =
Chronicle.Bench.Repo.start_link(
url: url,
pool_size: pool_size,
queue_target: 5_000,
queue_interval: 5_000,
log: false
)
quoted_prefix = ~s("#{prefix}")
Ecto.Adapters.SQL.query!(Chronicle.Bench.Repo, "CREATE SCHEMA #{quoted_prefix}", [], log: false)
try do
:ok =
Ecto.Migrator.up(
Chronicle.Bench.Repo,
System.unique_integer([:positive, :monotonic]),
Chronicle.Ecto.Migration,
prefix: prefix,
log: false
)
Ecto.Adapters.SQL.query!(
Chronicle.Bench.Repo,
"CREATE TABLE #{quoted_prefix}.chronicle_bench_items (id uuid PRIMARY KEY, value bigint)",
[],
log: false
)
Application.delete_env(:chronicle, :stores)
Application.put_env(:chronicle, :repo, Chronicle.Bench.Repo)
Application.put_env(:chronicle, :prefix, prefix)
Application.put_env(:chronicle, :signing_key, :crypto.strong_rand_bytes(32))
Application.put_env(:chronicle, :key_id, "benchmark-key")
write = fn value ->
started = System.monotonic_time()
result =
Chronicle.Bench.Item
|> struct()
|> Ecto.Changeset.change(value: value)
|> Chronicle.insert(ecto_options: [prefix: prefix])
elapsed =
System.monotonic_time()
|> Kernel.-(started)
|> System.convert_time_unit(:native, :microsecond)
case result do
{:ok, _item} -> elapsed
{:error, error} -> raise "benchmark write failed: #{inspect(error)}"
end
end
Enum.each(1..min(25, writes), write)
started = System.monotonic_time()
latencies =
1..writes
|> Task.async_stream(write,
max_concurrency: concurrency,
ordered: false,
timeout: :infinity
)
|> Enum.map(fn {:ok, latency} -> latency end)
|> Enum.sort()
elapsed_us =
System.monotonic_time()
|> Kernel.-(started)
|> System.convert_time_unit(:native, :microsecond)
percentile = fn fraction ->
index = min(length(latencies) - 1, floor((length(latencies) - 1) * fraction))
Enum.at(latencies, index)
end
verification_started = System.monotonic_time()
{:ok, _checkpoints} = Chronicle.verify_all()
verification_us =
System.monotonic_time()
|> Kernel.-(verification_started)
|> System.convert_time_unit(:native, :microsecond)
IO.puts("""
writes: #{writes}
concurrency: #{concurrency}
throughput_writes_per_second: #{Float.round(writes * 1_000_000 / elapsed_us, 1)}
write_latency_ms_p50: #{Float.round(percentile.(0.50) / 1_000, 2)}
write_latency_ms_p95: #{Float.round(percentile.(0.95) / 1_000, 2)}
write_latency_ms_p99: #{Float.round(percentile.(0.99) / 1_000, 2)}
verification_ms: #{Float.round(verification_us / 1_000, 2)}
""")
after
Ecto.Adapters.SQL.query!(
Chronicle.Bench.Repo,
"DROP SCHEMA #{quoted_prefix} CASCADE",
[],
log: false
)
end