Add memory-backed benchmark profile
This commit is contained in:
16
README.md
16
README.md
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
<img alt="Parrhesia Logo" src="./docs/logo.svg" width="150" align="right">
|
<img alt="Parrhesia Logo" src="./docs/logo.svg" width="150" align="right">
|
||||||
|
|
||||||
Parrhesia is a Nostr relay server written in Elixir/OTP with PostgreSQL storage.
|
Parrhesia is a Nostr relay server written in Elixir/OTP.
|
||||||
|
|
||||||
|
Supported storage backends:
|
||||||
|
|
||||||
|
- PostgreSQL, which is the primary and production-oriented backend
|
||||||
|
- in-memory storage, which is useful for tests, local experiments, and benchmarks
|
||||||
|
|
||||||
**ALPHA CONDITION – BREAKING CHANGES MIGHT HAPPEN!**
|
**ALPHA CONDITION – BREAKING CHANGES MIGHT HAPPEN!**
|
||||||
|
|
||||||
@@ -118,6 +123,9 @@ Before a Nostr client can publish its first event successfully, make sure these
|
|||||||
1. PostgreSQL is reachable from Parrhesia.
|
1. PostgreSQL is reachable from Parrhesia.
|
||||||
Set `DATABASE_URL` and create/migrate the database with `Parrhesia.Release.migrate()` or `mix ecto.migrate`.
|
Set `DATABASE_URL` and create/migrate the database with `Parrhesia.Release.migrate()` or `mix ecto.migrate`.
|
||||||
|
|
||||||
|
PostgreSQL is the supported production datastore. The in-memory backend is intended for
|
||||||
|
non-persistent runs such as tests and benchmarks.
|
||||||
|
|
||||||
2. Parrhesia listeners are configured for your deployment.
|
2. Parrhesia listeners are configured for your deployment.
|
||||||
The default config exposes a `public` listener on plain HTTP port `4413`, and a reverse proxy can terminate TLS and forward WebSocket traffic to `/relay`. Additional listeners can be defined in `config/*.exs`.
|
The default config exposes a `public` listener on plain HTTP port `4413`, and a reverse proxy can terminate TLS and forward WebSocket traffic to `/relay`. Additional listeners can be defined in `config/*.exs`.
|
||||||
|
|
||||||
@@ -153,6 +161,8 @@ For runtime overrides, use the `PARRHESIA_...` prefix:
|
|||||||
- `PARRHESIA_PUBLIC_MAX_CONNECTIONS`
|
- `PARRHESIA_PUBLIC_MAX_CONNECTIONS`
|
||||||
- `PARRHESIA_MODERATION_CACHE_ENABLED`
|
- `PARRHESIA_MODERATION_CACHE_ENABLED`
|
||||||
- `PARRHESIA_ENABLE_EXPIRATION_WORKER`
|
- `PARRHESIA_ENABLE_EXPIRATION_WORKER`
|
||||||
|
- `PARRHESIA_ENABLE_PARTITION_RETENTION_WORKER`
|
||||||
|
- `PARRHESIA_STORAGE_BACKEND`
|
||||||
- `PARRHESIA_LIMITS_*`
|
- `PARRHESIA_LIMITS_*`
|
||||||
- `PARRHESIA_POLICIES_*`
|
- `PARRHESIA_POLICIES_*`
|
||||||
- `PARRHESIA_METRICS_*`
|
- `PARRHESIA_METRICS_*`
|
||||||
@@ -496,7 +506,9 @@ Notes:
|
|||||||
|
|
||||||
## Benchmark
|
## Benchmark
|
||||||
|
|
||||||
The benchmark compares Parrhesia against [`strfry`](https://github.com/hoytech/strfry) and [`nostr-rs-relay`](https://sr.ht/~gheartsfield/nostr-rs-relay/) using [`nostr-bench`](https://github.com/rnostr/nostr-bench).
|
The benchmark compares two Parrhesia profiles, one backed by PostgreSQL and one backed by the in-memory adapter, against [`strfry`](https://github.com/hoytech/strfry) and [`nostr-rs-relay`](https://sr.ht/~gheartsfield/nostr-rs-relay/) using [`nostr-bench`](https://github.com/rnostr/nostr-bench). Benchmark runs also lift Parrhesia's relay-side limits by default so the benchmark client, not server guardrails, is the main bottleneck.
|
||||||
|
|
||||||
|
`mix bench` is a sequential mixed-workload benchmark, not an isolated per-endpoint microbenchmark. Each relay instance runs `connect`, then `echo`, then `event`, then `req` against the same live process, so later phases measure against state and load created by earlier phases.
|
||||||
|
|
||||||
Run it with:
|
Run it with:
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ config :parrhesia,
|
|||||||
separate_read_pool?: config_env() != :test
|
separate_read_pool?: config_env() != :test
|
||||||
],
|
],
|
||||||
moderation_cache_enabled: true,
|
moderation_cache_enabled: true,
|
||||||
|
enable_partition_retention_worker: true,
|
||||||
relay_url: "ws://localhost:4413/relay",
|
relay_url: "ws://localhost:4413/relay",
|
||||||
nip43: [
|
nip43: [
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -127,7 +128,9 @@ config :parrhesia,
|
|||||||
marmot_push_notifications: false
|
marmot_push_notifications: false
|
||||||
],
|
],
|
||||||
storage: [
|
storage: [
|
||||||
|
backend: :postgres,
|
||||||
events: Parrhesia.Storage.Adapters.Postgres.Events,
|
events: Parrhesia.Storage.Adapters.Postgres.Events,
|
||||||
|
acl: Parrhesia.Storage.Adapters.Postgres.ACL,
|
||||||
moderation: Parrhesia.Storage.Adapters.Postgres.Moderation,
|
moderation: Parrhesia.Storage.Adapters.Postgres.Moderation,
|
||||||
groups: Parrhesia.Storage.Adapters.Postgres.Groups,
|
groups: Parrhesia.Storage.Adapters.Postgres.Groups,
|
||||||
admin: Parrhesia.Storage.Adapters.Postgres.Admin
|
admin: Parrhesia.Storage.Adapters.Postgres.Admin
|
||||||
|
|||||||
@@ -35,6 +35,20 @@ bool_env = fn name, default ->
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
storage_backend_env = fn name, default ->
|
||||||
|
case System.get_env(name) do
|
||||||
|
nil ->
|
||||||
|
default
|
||||||
|
|
||||||
|
value ->
|
||||||
|
case String.downcase(String.trim(value)) do
|
||||||
|
"postgres" -> :postgres
|
||||||
|
"memory" -> :memory
|
||||||
|
_other -> raise "environment variable #{name} must be one of: postgres, memory"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
csv_env = fn name, default ->
|
csv_env = fn name, default ->
|
||||||
case System.get_env(name) do
|
case System.get_env(name) do
|
||||||
nil ->
|
nil ->
|
||||||
@@ -125,14 +139,12 @@ ipv4_env = fn name, default ->
|
|||||||
end
|
end
|
||||||
|
|
||||||
if config_env() == :prod do
|
if config_env() == :prod do
|
||||||
database_url =
|
|
||||||
System.get_env("DATABASE_URL") ||
|
|
||||||
raise "environment variable DATABASE_URL is missing. Example: ecto://USER:PASS@HOST/DATABASE"
|
|
||||||
|
|
||||||
repo_defaults = Application.get_env(:parrhesia, Parrhesia.Repo, [])
|
repo_defaults = Application.get_env(:parrhesia, Parrhesia.Repo, [])
|
||||||
read_repo_defaults = Application.get_env(:parrhesia, Parrhesia.ReadRepo, [])
|
read_repo_defaults = Application.get_env(:parrhesia, Parrhesia.ReadRepo, [])
|
||||||
relay_url_default = Application.get_env(:parrhesia, :relay_url)
|
relay_url_default = Application.get_env(:parrhesia, :relay_url)
|
||||||
metadata_defaults = Application.get_env(:parrhesia, :metadata, [])
|
metadata_defaults = Application.get_env(:parrhesia, :metadata, [])
|
||||||
|
database_defaults = Application.get_env(:parrhesia, :database, [])
|
||||||
|
storage_defaults = Application.get_env(:parrhesia, :storage, [])
|
||||||
|
|
||||||
moderation_cache_enabled_default =
|
moderation_cache_enabled_default =
|
||||||
Application.get_env(:parrhesia, :moderation_cache_enabled, true)
|
Application.get_env(:parrhesia, :moderation_cache_enabled, true)
|
||||||
@@ -140,6 +152,9 @@ if config_env() == :prod do
|
|||||||
enable_expiration_worker_default =
|
enable_expiration_worker_default =
|
||||||
Application.get_env(:parrhesia, :enable_expiration_worker, true)
|
Application.get_env(:parrhesia, :enable_expiration_worker, true)
|
||||||
|
|
||||||
|
enable_partition_retention_worker_default =
|
||||||
|
Application.get_env(:parrhesia, :enable_partition_retention_worker, true)
|
||||||
|
|
||||||
limits_defaults = Application.get_env(:parrhesia, :limits, [])
|
limits_defaults = Application.get_env(:parrhesia, :limits, [])
|
||||||
policies_defaults = Application.get_env(:parrhesia, :policies, [])
|
policies_defaults = Application.get_env(:parrhesia, :policies, [])
|
||||||
listeners_defaults = Application.get_env(:parrhesia, :listeners, %{})
|
listeners_defaults = Application.get_env(:parrhesia, :listeners, %{})
|
||||||
@@ -156,6 +171,29 @@ if config_env() == :prod do
|
|||||||
default_read_queue_interval =
|
default_read_queue_interval =
|
||||||
Keyword.get(read_repo_defaults, :queue_interval, default_queue_interval)
|
Keyword.get(read_repo_defaults, :queue_interval, default_queue_interval)
|
||||||
|
|
||||||
|
default_storage_backend =
|
||||||
|
storage_defaults
|
||||||
|
|> Keyword.get(:backend, :postgres)
|
||||||
|
|> case do
|
||||||
|
:postgres -> :postgres
|
||||||
|
:memory -> :memory
|
||||||
|
other -> raise "unsupported storage backend default: #{inspect(other)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
storage_backend = storage_backend_env.("PARRHESIA_STORAGE_BACKEND", default_storage_backend)
|
||||||
|
postgres_backend? = storage_backend == :postgres
|
||||||
|
|
||||||
|
separate_read_pool? =
|
||||||
|
postgres_backend? and Keyword.get(database_defaults, :separate_read_pool?, true)
|
||||||
|
|
||||||
|
database_url =
|
||||||
|
if postgres_backend? do
|
||||||
|
System.get_env("DATABASE_URL") ||
|
||||||
|
raise "environment variable DATABASE_URL is missing. Example: ecto://USER:PASS@HOST/DATABASE"
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
pool_size = int_env.("POOL_SIZE", default_pool_size)
|
pool_size = int_env.("POOL_SIZE", default_pool_size)
|
||||||
queue_target = int_env.("DB_QUEUE_TARGET_MS", default_queue_target)
|
queue_target = int_env.("DB_QUEUE_TARGET_MS", default_queue_target)
|
||||||
queue_interval = int_env.("DB_QUEUE_INTERVAL_MS", default_queue_interval)
|
queue_interval = int_env.("DB_QUEUE_INTERVAL_MS", default_queue_interval)
|
||||||
@@ -633,19 +671,47 @@ if config_env() == :prod do
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
config :parrhesia, Parrhesia.Repo,
|
storage =
|
||||||
url: database_url,
|
case storage_backend do
|
||||||
pool_size: pool_size,
|
:postgres ->
|
||||||
queue_target: queue_target,
|
[
|
||||||
queue_interval: queue_interval
|
backend: :postgres,
|
||||||
|
events: Parrhesia.Storage.Adapters.Postgres.Events,
|
||||||
|
acl: Parrhesia.Storage.Adapters.Postgres.ACL,
|
||||||
|
moderation: Parrhesia.Storage.Adapters.Postgres.Moderation,
|
||||||
|
groups: Parrhesia.Storage.Adapters.Postgres.Groups,
|
||||||
|
admin: Parrhesia.Storage.Adapters.Postgres.Admin
|
||||||
|
]
|
||||||
|
|
||||||
config :parrhesia, Parrhesia.ReadRepo,
|
:memory ->
|
||||||
url: database_url,
|
[
|
||||||
pool_size: read_pool_size,
|
backend: :memory,
|
||||||
queue_target: read_queue_target,
|
events: Parrhesia.Storage.Adapters.Memory.Events,
|
||||||
queue_interval: read_queue_interval
|
acl: Parrhesia.Storage.Adapters.Memory.ACL,
|
||||||
|
moderation: Parrhesia.Storage.Adapters.Memory.Moderation,
|
||||||
|
groups: Parrhesia.Storage.Adapters.Memory.Groups,
|
||||||
|
admin: Parrhesia.Storage.Adapters.Memory.Admin
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
if postgres_backend? do
|
||||||
|
config :parrhesia, Parrhesia.Repo,
|
||||||
|
url: database_url,
|
||||||
|
pool_size: pool_size,
|
||||||
|
queue_target: queue_target,
|
||||||
|
queue_interval: queue_interval
|
||||||
|
|
||||||
|
config :parrhesia, Parrhesia.ReadRepo,
|
||||||
|
url: database_url,
|
||||||
|
pool_size: read_pool_size,
|
||||||
|
queue_target: read_queue_target,
|
||||||
|
queue_interval: read_queue_interval
|
||||||
|
end
|
||||||
|
|
||||||
config :parrhesia,
|
config :parrhesia,
|
||||||
|
database: [
|
||||||
|
separate_read_pool?: separate_read_pool?
|
||||||
|
],
|
||||||
relay_url: string_env.("PARRHESIA_RELAY_URL", relay_url_default),
|
relay_url: string_env.("PARRHESIA_RELAY_URL", relay_url_default),
|
||||||
metadata: [
|
metadata: [
|
||||||
name: Keyword.get(metadata_defaults, :name, "Parrhesia"),
|
name: Keyword.get(metadata_defaults, :name, "Parrhesia"),
|
||||||
@@ -679,11 +745,17 @@ if config_env() == :prod do
|
|||||||
bool_env.("PARRHESIA_MODERATION_CACHE_ENABLED", moderation_cache_enabled_default),
|
bool_env.("PARRHESIA_MODERATION_CACHE_ENABLED", moderation_cache_enabled_default),
|
||||||
enable_expiration_worker:
|
enable_expiration_worker:
|
||||||
bool_env.("PARRHESIA_ENABLE_EXPIRATION_WORKER", enable_expiration_worker_default),
|
bool_env.("PARRHESIA_ENABLE_EXPIRATION_WORKER", enable_expiration_worker_default),
|
||||||
|
enable_partition_retention_worker:
|
||||||
|
bool_env.(
|
||||||
|
"PARRHESIA_ENABLE_PARTITION_RETENTION_WORKER",
|
||||||
|
enable_partition_retention_worker_default
|
||||||
|
),
|
||||||
listeners: listeners,
|
listeners: listeners,
|
||||||
limits: limits,
|
limits: limits,
|
||||||
policies: policies,
|
policies: policies,
|
||||||
retention: retention,
|
retention: retention,
|
||||||
features: features
|
features: features,
|
||||||
|
storage: storage
|
||||||
|
|
||||||
case System.get_env("PARRHESIA_EXTRA_CONFIG") do
|
case System.get_env("PARRHESIA_EXTRA_CONFIG") do
|
||||||
nil -> :ok
|
nil -> :ok
|
||||||
|
|||||||
@@ -19,27 +19,55 @@ defmodule Parrhesia.PostgresRepos do
|
|||||||
|
|
||||||
@spec started_repos() :: [module()]
|
@spec started_repos() :: [module()]
|
||||||
def started_repos do
|
def started_repos do
|
||||||
if separate_read_pool_enabled?() do
|
cond do
|
||||||
[Repo, ReadRepo]
|
not postgres_enabled?() ->
|
||||||
else
|
[]
|
||||||
[Repo]
|
|
||||||
|
separate_read_pool_enabled?() ->
|
||||||
|
[Repo, ReadRepo]
|
||||||
|
|
||||||
|
true ->
|
||||||
|
[Repo]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec postgres_enabled?() :: boolean()
|
||||||
|
def postgres_enabled? do
|
||||||
|
case Process.whereis(Config) do
|
||||||
|
pid when is_pid(pid) ->
|
||||||
|
Config.get([:storage, :backend], storage_backend_default()) == :postgres
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
storage_backend_default() == :postgres
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec separate_read_pool_enabled?() :: boolean()
|
@spec separate_read_pool_enabled?() :: boolean()
|
||||||
def separate_read_pool_enabled? do
|
def separate_read_pool_enabled? do
|
||||||
case Process.whereis(Config) do
|
case {postgres_enabled?(), Process.whereis(Config)} do
|
||||||
pid when is_pid(pid) ->
|
{false, _pid} ->
|
||||||
Config.get([:database, :separate_read_pool?], application_default())
|
false
|
||||||
|
|
||||||
nil ->
|
{true, pid} when is_pid(pid) ->
|
||||||
application_default()
|
Config.get(
|
||||||
|
[:database, :separate_read_pool?],
|
||||||
|
application_default(:separate_read_pool?, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
{true, nil} ->
|
||||||
|
application_default(:separate_read_pool?, false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp application_default do
|
defp application_default(key, default) do
|
||||||
:parrhesia
|
:parrhesia
|
||||||
|> Application.get_env(:database, [])
|
|> Application.get_env(:database, [])
|
||||||
|> Keyword.get(:separate_read_pool?, false)
|
|> Keyword.get(key, default)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp storage_backend_default do
|
||||||
|
:parrhesia
|
||||||
|
|> Application.get_env(:storage, [])
|
||||||
|
|> Keyword.get(:backend, :postgres)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,12 +13,20 @@ defmodule Parrhesia.Storage.Supervisor do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(_init_arg) do
|
def init(_init_arg) do
|
||||||
children =
|
children = moderation_cache_children() ++ PostgresRepos.started_repos()
|
||||||
[
|
|
||||||
{Parrhesia.Storage.Adapters.Postgres.ModerationCache,
|
|
||||||
name: Parrhesia.Storage.Adapters.Postgres.ModerationCache}
|
|
||||||
] ++ PostgresRepos.started_repos()
|
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
Supervisor.init(children, strategy: :one_for_one)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp moderation_cache_children do
|
||||||
|
if PostgresRepos.postgres_enabled?() and
|
||||||
|
Application.get_env(:parrhesia, :moderation_cache_enabled, true) do
|
||||||
|
[
|
||||||
|
{Parrhesia.Storage.Adapters.Postgres.ModerationCache,
|
||||||
|
name: Parrhesia.Storage.Adapters.Postgres.ModerationCache}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,9 +25,13 @@ defmodule Parrhesia.Tasks.Supervisor do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp partition_retention_children do
|
defp partition_retention_children do
|
||||||
[
|
if Application.get_env(:parrhesia, :enable_partition_retention_worker, true) do
|
||||||
{Parrhesia.Tasks.PartitionRetentionWorker, name: Parrhesia.Tasks.PartitionRetentionWorker}
|
[
|
||||||
]
|
{Parrhesia.Tasks.PartitionRetentionWorker, name: Parrhesia.Tasks.PartitionRetentionWorker}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp nip66_children do
|
defp nip66_children do
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ usage:
|
|||||||
./scripts/run_bench_compare.sh
|
./scripts/run_bench_compare.sh
|
||||||
|
|
||||||
Runs the same nostr-bench suite against:
|
Runs the same nostr-bench suite against:
|
||||||
1) Parrhesia (temporary prod relay via run_e2e_suite.sh)
|
1) Parrhesia (Postgres, temporary prod relay via run_e2e_suite.sh)
|
||||||
2) strfry (ephemeral instance) — optional, skipped if not in PATH
|
2) Parrhesia (in-memory storage, temporary prod relay via run_e2e_suite.sh)
|
||||||
3) nostr-rs-relay (ephemeral sqlite instance) — optional, skipped if not in PATH
|
3) strfry (ephemeral instance) — optional, skipped if not in PATH
|
||||||
|
4) nostr-rs-relay (ephemeral sqlite instance) — optional, skipped if not in PATH
|
||||||
|
|
||||||
Environment:
|
Environment:
|
||||||
PARRHESIA_BENCH_RUNS Number of comparison runs (default: 2)
|
PARRHESIA_BENCH_RUNS Number of comparison runs (default: 2)
|
||||||
@@ -247,7 +248,7 @@ echo " ${NOSTR_BENCH_VERSION}"
|
|||||||
echo
|
echo
|
||||||
|
|
||||||
for run in $(seq 1 "$RUNS"); do
|
for run in $(seq 1 "$RUNS"); do
|
||||||
echo "[run ${run}/${RUNS}] Parrhesia"
|
echo "[run ${run}/${RUNS}] Parrhesia (Postgres)"
|
||||||
parrhesia_log="$WORK_DIR/parrhesia_${run}.log"
|
parrhesia_log="$WORK_DIR/parrhesia_${run}.log"
|
||||||
if ! ./scripts/run_nostr_bench.sh all >"$parrhesia_log" 2>&1; then
|
if ! ./scripts/run_nostr_bench.sh all >"$parrhesia_log" 2>&1; then
|
||||||
echo "Parrhesia benchmark failed. Log: $parrhesia_log" >&2
|
echo "Parrhesia benchmark failed. Log: $parrhesia_log" >&2
|
||||||
@@ -255,6 +256,14 @@ for run in $(seq 1 "$RUNS"); do
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "[run ${run}/${RUNS}] Parrhesia (Memory)"
|
||||||
|
parrhesia_memory_log="$WORK_DIR/parrhesia_memory_${run}.log"
|
||||||
|
if ! PARRHESIA_BENCH_STORAGE_BACKEND=memory ./scripts/run_nostr_bench.sh all >"$parrhesia_memory_log" 2>&1; then
|
||||||
|
echo "Parrhesia memory benchmark failed. Log: $parrhesia_memory_log" >&2
|
||||||
|
tail -n 120 "$parrhesia_memory_log" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if (( HAS_STRFRY )); then
|
if (( HAS_STRFRY )); then
|
||||||
echo "[run ${run}/${RUNS}] strfry"
|
echo "[run ${run}/${RUNS}] strfry"
|
||||||
strfry_log="$WORK_DIR/strfry_${run}.log"
|
strfry_log="$WORK_DIR/strfry_${run}.log"
|
||||||
@@ -364,6 +373,7 @@ function loadRuns(prefix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parrhesiaRuns = loadRuns("parrhesia");
|
const parrhesiaRuns = loadRuns("parrhesia");
|
||||||
|
const parrhesiaMemoryRuns = loadRuns("parrhesia_memory");
|
||||||
const strfryRuns = hasStrfry ? loadRuns("strfry") : [];
|
const strfryRuns = hasStrfry ? loadRuns("strfry") : [];
|
||||||
const nostrRsRuns = hasNostrRs ? loadRuns("nostr_rs_relay") : [];
|
const nostrRsRuns = hasNostrRs ? loadRuns("nostr_rs_relay") : [];
|
||||||
|
|
||||||
@@ -382,7 +392,10 @@ function summarise(allRuns) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
const summary = { parrhesia: summarise(parrhesiaRuns) };
|
const summary = {
|
||||||
|
parrhesia: summarise(parrhesiaRuns),
|
||||||
|
parrhesiaMemory: summarise(parrhesiaMemoryRuns),
|
||||||
|
};
|
||||||
if (hasStrfry) summary.strfry = summarise(strfryRuns);
|
if (hasStrfry) summary.strfry = summarise(strfryRuns);
|
||||||
if (hasNostrRs) summary.nostrRsRelay = summarise(nostrRsRuns);
|
if (hasNostrRs) summary.nostrRsRelay = summarise(nostrRsRuns);
|
||||||
|
|
||||||
@@ -404,16 +417,22 @@ const metricLabels = [
|
|||||||
["req throughput (MiB/s) ↑", "reqSizeMiBS"],
|
["req throughput (MiB/s) ↑", "reqSizeMiBS"],
|
||||||
];
|
];
|
||||||
|
|
||||||
const headers = ["metric", "parrhesia"];
|
const headers = ["metric", "parrhesia-pg", "parrhesia-memory"];
|
||||||
if (hasStrfry) headers.push("strfry");
|
if (hasStrfry) headers.push("strfry");
|
||||||
if (hasNostrRs) headers.push("nostr-rs-relay");
|
if (hasNostrRs) headers.push("nostr-rs-relay");
|
||||||
|
headers.push("memory/parrhesia");
|
||||||
if (hasStrfry) headers.push("strfry/parrhesia");
|
if (hasStrfry) headers.push("strfry/parrhesia");
|
||||||
if (hasNostrRs) headers.push("nostr-rs/parrhesia");
|
if (hasNostrRs) headers.push("nostr-rs/parrhesia");
|
||||||
|
|
||||||
const rows = metricLabels.map(([label, key]) => {
|
const rows = metricLabels.map(([label, key]) => {
|
||||||
const row = [label, toFixed(summary.parrhesia[key])];
|
const row = [
|
||||||
|
label,
|
||||||
|
toFixed(summary.parrhesia[key]),
|
||||||
|
toFixed(summary.parrhesiaMemory[key]),
|
||||||
|
];
|
||||||
if (hasStrfry) row.push(toFixed(summary.strfry[key]));
|
if (hasStrfry) row.push(toFixed(summary.strfry[key]));
|
||||||
if (hasNostrRs) row.push(toFixed(summary.nostrRsRelay[key]));
|
if (hasNostrRs) row.push(toFixed(summary.nostrRsRelay[key]));
|
||||||
|
row.push(ratioVsParrhesia("parrhesiaMemory", key));
|
||||||
if (hasStrfry) row.push(ratioVsParrhesia("strfry", key));
|
if (hasStrfry) row.push(ratioVsParrhesia("strfry", key));
|
||||||
if (hasNostrRs) row.push(ratioVsParrhesia("nostrRsRelay", key));
|
if (hasNostrRs) row.push(ratioVsParrhesia("nostrRsRelay", key));
|
||||||
return row;
|
return row;
|
||||||
@@ -444,8 +463,10 @@ if (hasStrfry || hasNostrRs) {
|
|||||||
console.log("Run details:");
|
console.log("Run details:");
|
||||||
for (let i = 0; i < runs; i += 1) {
|
for (let i = 0; i < runs; i += 1) {
|
||||||
const p = parrhesiaRuns[i];
|
const p = parrhesiaRuns[i];
|
||||||
|
const pm = parrhesiaMemoryRuns[i];
|
||||||
let line = ` run ${i + 1}: ` +
|
let line = ` run ${i + 1}: ` +
|
||||||
`parrhesia(echo_tps=${toFixed(p.echoTps, 0)}, event_tps=${toFixed(p.eventTps, 0)}, req_tps=${toFixed(p.reqTps, 0)}, connect_avg_ms=${toFixed(p.connectAvgMs, 0)})`;
|
`parrhesia-pg(echo_tps=${toFixed(p.echoTps, 0)}, event_tps=${toFixed(p.eventTps, 0)}, req_tps=${toFixed(p.reqTps, 0)}, connect_avg_ms=${toFixed(p.connectAvgMs, 0)})` +
|
||||||
|
` | parrhesia-memory(echo_tps=${toFixed(pm.echoTps, 0)}, event_tps=${toFixed(pm.eventTps, 0)}, req_tps=${toFixed(pm.reqTps, 0)}, connect_avg_ms=${toFixed(pm.connectAvgMs, 0)})`;
|
||||||
if (hasStrfry) {
|
if (hasStrfry) {
|
||||||
const s = strfryRuns[i];
|
const s = strfryRuns[i];
|
||||||
line += ` | strfry(echo_tps=${toFixed(s.echoTps, 0)}, event_tps=${toFixed(s.eventTps, 0)}, req_tps=${toFixed(s.reqTps, 0)}, connect_avg_ms=${toFixed(s.connectAvgMs, 0)})`;
|
line += ` | strfry(echo_tps=${toFixed(s.echoTps, 0)}, event_tps=${toFixed(s.eventTps, 0)}, req_tps=${toFixed(s.reqTps, 0)}, connect_avg_ms=${toFixed(s.connectAvgMs, 0)})`;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ if [[ "$MIX_ENV" != "test" && "$MIX_ENV" != "prod" ]]; then
|
|||||||
fi
|
fi
|
||||||
export MIX_ENV
|
export MIX_ENV
|
||||||
|
|
||||||
|
SKIP_ECTO="${PARRHESIA_E2E_SKIP_ECTO:-0}"
|
||||||
|
|
||||||
SUITE_SLUG="$(printf '%s' "$SUITE_NAME" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')"
|
SUITE_SLUG="$(printf '%s' "$SUITE_NAME" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')"
|
||||||
SUITE_UPPER="$(printf '%s' "$SUITE_SLUG" | tr '[:lower:]' '[:upper:]')"
|
SUITE_UPPER="$(printf '%s' "$SUITE_SLUG" | tr '[:lower:]' '[:upper:]')"
|
||||||
PORT_ENV_VAR="PARRHESIA_${SUITE_UPPER}_E2E_RELAY_PORT"
|
PORT_ENV_VAR="PARRHESIA_${SUITE_UPPER}_E2E_RELAY_PORT"
|
||||||
@@ -56,14 +58,16 @@ if [[ -z "${DATABASE_URL:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$MIX_ENV" == "test" ]]; then
|
if [[ "$SKIP_ECTO" != "1" ]]; then
|
||||||
PARRHESIA_TEST_HTTP_PORT=0 mix ecto.drop --quiet --force || true
|
if [[ "$MIX_ENV" == "test" ]]; then
|
||||||
PARRHESIA_TEST_HTTP_PORT=0 mix ecto.create --quiet
|
PARRHESIA_TEST_HTTP_PORT=0 mix ecto.drop --quiet --force || true
|
||||||
PARRHESIA_TEST_HTTP_PORT=0 mix ecto.migrate --quiet
|
PARRHESIA_TEST_HTTP_PORT=0 mix ecto.create --quiet
|
||||||
else
|
PARRHESIA_TEST_HTTP_PORT=0 mix ecto.migrate --quiet
|
||||||
mix ecto.drop --quiet --force || true
|
else
|
||||||
mix ecto.create --quiet
|
mix ecto.drop --quiet --force || true
|
||||||
mix ecto.migrate --quiet
|
mix ecto.create --quiet
|
||||||
|
mix ecto.migrate --quiet
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SERVER_LOG="${ROOT_DIR}/.${SUITE_SLUG}-e2e-server.log"
|
SERVER_LOG="${ROOT_DIR}/.${SUITE_SLUG}-e2e-server.log"
|
||||||
@@ -75,7 +79,7 @@ cleanup() {
|
|||||||
wait "$SERVER_PID" 2>/dev/null || true
|
wait "$SERVER_PID" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${PARRHESIA_E2E_DROP_DB_ON_EXIT:-0}" == "1" ]]; then
|
if [[ "$SKIP_ECTO" != "1" && "${PARRHESIA_E2E_DROP_DB_ON_EXIT:-0}" == "1" ]]; then
|
||||||
if [[ "$MIX_ENV" == "test" ]]; then
|
if [[ "$MIX_ENV" == "test" ]]; then
|
||||||
PARRHESIA_TEST_HTTP_PORT=0 mix ecto.drop --quiet --force || true
|
PARRHESIA_TEST_HTTP_PORT=0 mix ecto.drop --quiet --force || true
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ usage:
|
|||||||
Runs nostr-bench against a temporary Parrhesia prod server started via
|
Runs nostr-bench against a temporary Parrhesia prod server started via
|
||||||
./scripts/run_e2e_suite.sh.
|
./scripts/run_e2e_suite.sh.
|
||||||
|
|
||||||
|
Benchmark target:
|
||||||
|
PARRHESIA_BENCH_STORAGE_BACKEND postgres|memory (default: postgres)
|
||||||
|
|
||||||
Pool tuning:
|
Pool tuning:
|
||||||
POOL_SIZE optional override for prod pool size
|
POOL_SIZE optional override for prod pool size
|
||||||
DB_QUEUE_TARGET_MS optional Repo queue target override
|
DB_QUEUE_TARGET_MS optional Repo queue target override
|
||||||
@@ -39,6 +42,10 @@ Default "all" run can be tuned via env vars:
|
|||||||
PARRHESIA_BENCH_REQ_RATE (default: 50)
|
PARRHESIA_BENCH_REQ_RATE (default: 50)
|
||||||
PARRHESIA_BENCH_REQ_LIMIT (default: 10)
|
PARRHESIA_BENCH_REQ_LIMIT (default: 10)
|
||||||
PARRHESIA_BENCH_KEEPALIVE_SECONDS (default: 5)
|
PARRHESIA_BENCH_KEEPALIVE_SECONDS (default: 5)
|
||||||
|
|
||||||
|
By default benchmark runs also lift relay limits so the benchmark client, not
|
||||||
|
relay-side ceilings, is the bottleneck. Set `PARRHESIA_BENCH_LIFT_LIMITS=0` to
|
||||||
|
disable that behavior.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,11 +70,54 @@ if [[ "$MODE" == "all" && $# -gt 0 ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${PGDATABASE:-}" ]]; then
|
BENCH_STORAGE_BACKEND="${PARRHESIA_BENCH_STORAGE_BACKEND:-postgres}"
|
||||||
export PGDATABASE="parrhesia_bench_prod_$(date +%s)_$RANDOM"
|
case "$BENCH_STORAGE_BACKEND" in
|
||||||
|
postgres|memory)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "PARRHESIA_BENCH_STORAGE_BACKEND must be postgres or memory, got: ${BENCH_STORAGE_BACKEND}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
export PARRHESIA_STORAGE_BACKEND="$BENCH_STORAGE_BACKEND"
|
||||||
|
export PARRHESIA_ENABLE_EXPIRATION_WORKER="${PARRHESIA_ENABLE_EXPIRATION_WORKER:-0}"
|
||||||
|
export PARRHESIA_ENABLE_PARTITION_RETENTION_WORKER="${PARRHESIA_ENABLE_PARTITION_RETENTION_WORKER:-0}"
|
||||||
|
|
||||||
|
if [[ "${PARRHESIA_BENCH_LIFT_LIMITS:-1}" == "1" ]]; then
|
||||||
|
export PARRHESIA_PUBLIC_MAX_CONNECTIONS="${PARRHESIA_PUBLIC_MAX_CONNECTIONS:-infinity}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_FRAME_BYTES="${PARRHESIA_LIMITS_MAX_FRAME_BYTES:-16777216}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_EVENT_BYTES="${PARRHESIA_LIMITS_MAX_EVENT_BYTES:-4194304}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_FILTERS_PER_REQ="${PARRHESIA_LIMITS_MAX_FILTERS_PER_REQ:-1024}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_FILTER_LIMIT="${PARRHESIA_LIMITS_MAX_FILTER_LIMIT:-100000}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_TAGS_PER_EVENT="${PARRHESIA_LIMITS_MAX_TAGS_PER_EVENT:-4096}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_TAG_VALUES_PER_FILTER="${PARRHESIA_LIMITS_MAX_TAG_VALUES_PER_FILTER:-4096}"
|
||||||
|
export PARRHESIA_LIMITS_IP_MAX_EVENT_INGEST_PER_WINDOW="${PARRHESIA_LIMITS_IP_MAX_EVENT_INGEST_PER_WINDOW:-1000000}"
|
||||||
|
export PARRHESIA_LIMITS_RELAY_MAX_EVENT_INGEST_PER_WINDOW="${PARRHESIA_LIMITS_RELAY_MAX_EVENT_INGEST_PER_WINDOW:-1000000}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_SUBSCRIPTIONS_PER_CONNECTION="${PARRHESIA_LIMITS_MAX_SUBSCRIPTIONS_PER_CONNECTION:-4096}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_EVENT_FUTURE_SKEW_SECONDS="${PARRHESIA_LIMITS_MAX_EVENT_FUTURE_SKEW_SECONDS:-31536000}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_EVENT_INGEST_PER_WINDOW="${PARRHESIA_LIMITS_MAX_EVENT_INGEST_PER_WINDOW:-1000000}"
|
||||||
|
export PARRHESIA_LIMITS_AUTH_MAX_AGE_SECONDS="${PARRHESIA_LIMITS_AUTH_MAX_AGE_SECONDS:-31536000}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_OUTBOUND_QUEUE="${PARRHESIA_LIMITS_MAX_OUTBOUND_QUEUE:-65536}"
|
||||||
|
export PARRHESIA_LIMITS_OUTBOUND_DRAIN_BATCH_SIZE="${PARRHESIA_LIMITS_OUTBOUND_DRAIN_BATCH_SIZE:-4096}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_NEGENTROPY_PAYLOAD_BYTES="${PARRHESIA_LIMITS_MAX_NEGENTROPY_PAYLOAD_BYTES:-1048576}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_NEGENTROPY_SESSIONS_PER_CONNECTION="${PARRHESIA_LIMITS_MAX_NEGENTROPY_SESSIONS_PER_CONNECTION:-256}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_NEGENTROPY_TOTAL_SESSIONS="${PARRHESIA_LIMITS_MAX_NEGENTROPY_TOTAL_SESSIONS:-100000}"
|
||||||
|
export PARRHESIA_LIMITS_MAX_NEGENTROPY_ITEMS_PER_SESSION="${PARRHESIA_LIMITS_MAX_NEGENTROPY_ITEMS_PER_SESSION:-1000000}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export PARRHESIA_E2E_DROP_DB_ON_EXIT="${PARRHESIA_E2E_DROP_DB_ON_EXIT:-1}"
|
if [[ "$BENCH_STORAGE_BACKEND" == "memory" ]]; then
|
||||||
|
export PARRHESIA_E2E_SKIP_ECTO="${PARRHESIA_E2E_SKIP_ECTO:-1}"
|
||||||
|
export PARRHESIA_E2E_DROP_DB_ON_EXIT=0
|
||||||
|
export PARRHESIA_MODERATION_CACHE_ENABLED="${PARRHESIA_MODERATION_CACHE_ENABLED:-0}"
|
||||||
|
else
|
||||||
|
if [[ -z "${PGDATABASE:-}" ]]; then
|
||||||
|
export PGDATABASE="parrhesia_bench_prod_$(date +%s)_$RANDOM"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PARRHESIA_E2E_SKIP_ECTO="${PARRHESIA_E2E_SKIP_ECTO:-0}"
|
||||||
|
export PARRHESIA_E2E_DROP_DB_ON_EXIT="${PARRHESIA_E2E_DROP_DB_ON_EXIT:-1}"
|
||||||
|
fi
|
||||||
|
|
||||||
PARRHESIA_E2E_MIX_ENV="prod" \
|
PARRHESIA_E2E_MIX_ENV="prod" \
|
||||||
exec ./scripts/run_e2e_suite.sh \
|
exec ./scripts/run_e2e_suite.sh \
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
defmodule Parrhesia.StorageTest do
|
defmodule Parrhesia.StorageTest do
|
||||||
use Parrhesia.IntegrationCase, async: false
|
use Parrhesia.IntegrationCase, async: false
|
||||||
|
|
||||||
|
alias Parrhesia.PostgresRepos
|
||||||
alias Parrhesia.Storage
|
alias Parrhesia.Storage
|
||||||
|
|
||||||
test "resolves default storage modules" do
|
test "resolves default storage modules" do
|
||||||
@@ -26,4 +27,23 @@ defmodule Parrhesia.StorageTest do
|
|||||||
Storage.events()
|
Storage.events()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "postgres repos are disabled for non-postgres storage backends" do
|
||||||
|
[{:config, previous}] = :ets.lookup(Parrhesia.Config, :config)
|
||||||
|
|
||||||
|
updated_storage =
|
||||||
|
previous
|
||||||
|
|> Map.get(:storage, [])
|
||||||
|
|> Keyword.put(:backend, :memory)
|
||||||
|
|
||||||
|
:ets.insert(Parrhesia.Config, {:config, Map.put(previous, :storage, updated_storage)})
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
:ets.insert(Parrhesia.Config, {:config, previous})
|
||||||
|
end)
|
||||||
|
|
||||||
|
refute PostgresRepos.postgres_enabled?()
|
||||||
|
refute PostgresRepos.separate_read_pool_enabled?()
|
||||||
|
assert PostgresRepos.started_repos() == []
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user