From 00e412f16bc4a081e86b00c879c978641243c817 Mon Sep 17 00:00:00 2001 From: Mike Zornek Date: Tue, 20 Oct 2026 12:51:20 -0400 Subject: [PATCH 1/3] feat: enable pg_stat_statements where the server preloads it --- AGENTS.md | 2 +- ...261020164842_enable_pg_stat_statements.exs | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs diff --git a/AGENTS.md b/AGENTS.md index 8704da7..9b0ce2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,7 +80,7 @@ _build/prod/rel/flick/bin/flick remote In that shell, run `:observer_cli.start()`. Press `q` and then Enter to quit observer_cli. Quitting leaves the node running. Never run `System.stop/0` or `:init.stop/0` in the remote shell, because they stop production. Leave the shell with Ctrl+C and then `a`, which closes only the remote shell. The release also includes `:recon` for one-off queries such as `:recon.proc_count(:memory, 10)`. -Phoenix LiveDashboard runs at `/admin/dashboard`, behind the same basic auth as `/admin/ballots`. In development, the credentials are `flick-admin` / `unsafe-password`. In production, they come from `BASIC_AUTH_ADMIN_USERNAME` and `BASIC_AUTH_ADMIN_PASSWORD`. Its Processes page shows each process's dictionary and stack trace, but not its state. It still exposes the node's internals, so never move it out of the `/admin` scope. Its Ecto Stats page reads table sizes, index usage, and locks through `ecto_psql_extras` over the private network. It needs no remote shell and starts no second VM, so try it before observer_cli. Use observer_cli when the web endpoint cannot serve a LiveView. +Phoenix LiveDashboard runs at `/admin/dashboard`, behind the same basic auth as `/admin/ballots`. In development, the credentials are `flick-admin` / `unsafe-password`. In production, they come from `BASIC_AUTH_ADMIN_USERNAME` and `BASIC_AUTH_ADMIN_PASSWORD`. Its Processes page shows each process's dictionary and stack trace, but not its state. It still exposes the node's internals, so never move it out of the `/admin` scope. Its Ecto Stats page reads table sizes, index usage, and locks through `ecto_psql_extras` over the private network. It needs no remote shell and starts no second VM, so try it before observer_cli. Its "outliers" and "calls" tabs need `pg_stat_statements`, which a migration creates only on a server that preloads the library. They work in production but not on local or CI Postgres. Use observer_cli when the web endpoint cannot serve a LiveView. `rel/env.sh.eex` binds EPMD and Erlang distribution to loopback and names the node `flick@localhost`, so only a shell inside the container can reach it. Keep it that way. Never expose distribution on Render's network, and never set a custom release cookie. The default cookie never leaves the container. A custom cookie would sit in Render's environment variables, and its only use is connecting from outside, where a cookie grants full code execution on the node. diff --git a/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs b/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs new file mode 100644 index 0000000..f3e3daf --- /dev/null +++ b/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs @@ -0,0 +1,25 @@ +defmodule Flick.Repo.Migrations.EnablePgStatStatements do + use Ecto.Migration + + # LiveDashboard's Ecto Stats reads this extension for its "outliers" and + # "calls" tabs. Its view raises an error unless the server preloads the + # library, and local and CI Postgres do not. So the migration creates the + # extension only where the library is preloaded, and Ecto Stats elsewhere + # keeps reporting it as not enabled. + def change do + # The raw SQL touches no table, and the down step reverses it. + # excellent_migrations:safety-assured-for-next-line raw_sql_executed + execute """ + DO $$ + BEGIN + IF 'pg_stat_statements' = ANY ( + string_to_array(replace(current_setting('shared_preload_libraries'), ' ', ''), ',') + ) THEN + CREATE EXTENSION IF NOT EXISTS pg_stat_statements; + END IF; + END + $$ + """, + "DROP EXTENSION IF EXISTS pg_stat_statements" + end +end From 7bd349e6f25cf71604e606db092d55f7d48b9bed Mon Sep 17 00:00:00 2001 From: Mike Zornek Date: Tue, 20 Oct 2026 12:55:22 -0400 Subject: [PATCH 2/3] fix: detect pg_stat_statements without reading a superuser-only setting --- AGENTS.md | 2 +- ...261020164842_enable_pg_stat_statements.exs | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9b0ce2b..3754907 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,7 +80,7 @@ _build/prod/rel/flick/bin/flick remote In that shell, run `:observer_cli.start()`. Press `q` and then Enter to quit observer_cli. Quitting leaves the node running. Never run `System.stop/0` or `:init.stop/0` in the remote shell, because they stop production. Leave the shell with Ctrl+C and then `a`, which closes only the remote shell. The release also includes `:recon` for one-off queries such as `:recon.proc_count(:memory, 10)`. -Phoenix LiveDashboard runs at `/admin/dashboard`, behind the same basic auth as `/admin/ballots`. In development, the credentials are `flick-admin` / `unsafe-password`. In production, they come from `BASIC_AUTH_ADMIN_USERNAME` and `BASIC_AUTH_ADMIN_PASSWORD`. Its Processes page shows each process's dictionary and stack trace, but not its state. It still exposes the node's internals, so never move it out of the `/admin` scope. Its Ecto Stats page reads table sizes, index usage, and locks through `ecto_psql_extras` over the private network. It needs no remote shell and starts no second VM, so try it before observer_cli. Its "outliers" and "calls" tabs need `pg_stat_statements`, which a migration creates only on a server that preloads the library. They work in production but not on local or CI Postgres. Use observer_cli when the web endpoint cannot serve a LiveView. +Phoenix LiveDashboard runs at `/admin/dashboard`, behind the same basic auth as `/admin/ballots`. In development, the credentials are `flick-admin` / `unsafe-password`. In production, they come from `BASIC_AUTH_ADMIN_USERNAME` and `BASIC_AUTH_ADMIN_PASSWORD`. Its Processes page shows each process's dictionary and stack trace, but not its state. It still exposes the node's internals, so never move it out of the `/admin` scope. Its Ecto Stats page reads table sizes, index usage, and locks through `ecto_psql_extras` over the private network. It needs no remote shell and starts no second VM, so try it before observer_cli. Its "outliers" and "calls" tabs need `pg_stat_statements`, so they work only on a server that preloads the library. Local and CI Postgres do not, and their migration skips the extension with a notice. Use observer_cli when the web endpoint cannot serve a LiveView. `rel/env.sh.eex` binds EPMD and Erlang distribution to loopback and names the node `flick@localhost`, so only a shell inside the container can reach it. Keep it that way. Never expose distribution on Render's network, and never set a custom release cookie. The default cookie never leaves the container. A custom cookie would sit in Render's environment variables, and its only use is connecting from outside, where a cookie grants full code execution on the node. diff --git a/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs b/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs index f3e3daf..cf189e7 100644 --- a/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs +++ b/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs @@ -1,22 +1,24 @@ defmodule Flick.Repo.Migrations.EnablePgStatStatements do use Ecto.Migration - # LiveDashboard's Ecto Stats reads this extension for its "outliers" and - # "calls" tabs. Its view raises an error unless the server preloads the - # library, and local and CI Postgres do not. So the migration creates the - # extension only where the library is preloaded, and Ecto Stats elsewhere - # keeps reporting it as not enabled. + # LiveDashboard's Ecto Stats needs this extension for its "outliers" and + # "calls" tabs. The extension's view raises an error unless the server + # preloads the library. Local and CI Postgres do not preload it. + # + # Only a superuser can read `shared_preload_libraries`, so the block creates + # the extension and reads the view once. On an error, the block rolls back + # and leaves no extension. A diagnostics extension must never block a deploy. def change do - # The raw SQL touches no table, and the down step reverses it. + # The raw SQL touches no table. The down step reverses it. # excellent_migrations:safety-assured-for-next-line raw_sql_executed execute """ DO $$ BEGIN - IF 'pg_stat_statements' = ANY ( - string_to_array(replace(current_setting('shared_preload_libraries'), ' ', ''), ',') - ) THEN - CREATE EXTENSION IF NOT EXISTS pg_stat_statements; - END IF; + CREATE EXTENSION IF NOT EXISTS pg_stat_statements; + PERFORM 1 FROM pg_stat_statements LIMIT 1; + EXCEPTION + WHEN object_not_in_prerequisite_state OR insufficient_privilege THEN + RAISE NOTICE 'pg_stat_statements skipped: %', SQLERRM; END $$ """, From c6765ffc7f37253f7b3c4cb56c174240ba77e85e Mon Sep 17 00:00:00 2001 From: Mike Zornek Date: Tue, 20 Oct 2026 13:02:21 -0400 Subject: [PATCH 3/3] fix: never let the pg_stat_statements block stop a deploy --- .../migrations/20261020164842_enable_pg_stat_statements.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs b/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs index cf189e7..9bb8bed 100644 --- a/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs +++ b/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs @@ -17,7 +17,7 @@ defmodule Flick.Repo.Migrations.EnablePgStatStatements do CREATE EXTENSION IF NOT EXISTS pg_stat_statements; PERFORM 1 FROM pg_stat_statements LIMIT 1; EXCEPTION - WHEN object_not_in_prerequisite_state OR insufficient_privilege THEN + WHEN OTHERS THEN RAISE NOTICE 'pg_stat_statements skipped: %', SQLERRM; END $$