tutorial

How to Monitor TypedEctoSchema with Vigilmon

TypedEctoSchema generates Ecto schemas with compile-time typespecs. Learn how to monitor the database layer it drives, track query health, and alert on schema-level failures using Vigilmon.

TypedEctoSchema is an Elixir library that eliminates the gap between your Ecto schema definitions and your typespecs. By generating both at once from a single declaration, it catches type mismatches at compile time rather than at runtime in production. The schemas TypedEctoSchema generates power every query your app makes — when the database layer they wrap becomes unhealthy, the result is failed reads, dropped writes, and inconsistent data. Vigilmon gives you health monitoring, query latency alerting, and heartbeat checks so database failures are caught before they cascade into user-facing errors.

What You'll Set Up

  • HTTP health endpoint that validates TypedEctoSchema-driven queries
  • Pool utilisation monitoring to catch connection exhaustion
  • Cron heartbeat for scheduled Ecto operations
  • SSL certificate monitoring for database connections

Prerequisites

  • Elixir 1.14+ with typed_ecto_schema and ecto_sql in mix.exs
  • At least one TypedEctoSchema schema used in a production query path
  • A free Vigilmon account

Step 1: Add a Health Endpoint That Runs a Live Query

Your health check should exercise a real TypedEctoSchema-backed query so Vigilmon detects both application crashes and database connectivity failures:

# lib/my_app_web/controllers/health_controller.ex
defmodule MyAppWeb.HealthController do
  use MyAppWeb, :controller

  import Ecto.Query

  alias MyApp.Repo
  alias MyApp.Accounts.User

  def index(conn, _params) do
    checks = %{
      database: check_database(),
      schema_query: check_schema_query()
    }

    status = if Enum.all?(checks, fn {_, v} -> v == :ok end), do: 200, else: 503

    conn
    |> put_status(status)
    |> json(%{
      status: (if status == 200, do: "ok", else: "error"),
      checks: checks
    })
  end

  defp check_database do
    case Ecto.Adapters.SQL.query(Repo, "SELECT 1", []) do
      {:ok, _} -> :ok
      {:error, _} -> :error
    end
  end

  defp check_schema_query do
    # Run a lightweight query against a TypedEctoSchema-backed table
    try do
      _ = Repo.one(from u in User, select: u.id, limit: 1)
      :ok
    rescue
      _ -> :error
    end
  end
end

A typical TypedEctoSchema definition it validates against:

# lib/my_app/accounts/user.ex
defmodule MyApp.Accounts.User do
  use TypedEctoSchema

  typed_schema "users" do
    field :email, :string
    field :role, :string
    field :active, :boolean, default: true
    timestamps()
  end
end

Wire the route and verify:

curl http://localhost:4000/health
# {"status":"ok","checks":{"database":"ok","schema_query":"ok"}}

Step 2: Add the Monitor in Vigilmon

  1. Log in to vigilmon.online and click Add Monitor.
  2. Set Type to HTTP / HTTPS.
  3. Enter your health URL: https://myapp.example.com/health.
  4. Set Check interval to 1 minute.
  5. Set Expected HTTP status to 200.
  6. Under Response body, enable Contains keyword and enter "ok".
  7. Click Save.

Step 3: Expose Connection Pool Metrics

TypedEctoSchema schemas drive every query, so pool exhaustion is the most common failure mode. Expose pool metrics in your health endpoint:

defmodule MyAppWeb.HealthController do
  use MyAppWeb, :controller

  import Ecto.Query
  alias MyApp.{Repo, Accounts.User}

  @pool_warn_threshold 0.8

  def index(conn, _params) do
    pool_size = Application.get_env(:my_app, Repo)[:pool_size] || 10
    pool_status = check_pool_health(pool_size)
    db_status = check_database()
    query_status = check_schema_query()

    all_ok = pool_status == :ok and db_status == :ok and query_status == :ok
    http_status = if all_ok, do: 200, else: 503

    conn
    |> put_status(http_status)
    |> json(%{
      status: (if all_ok, do: "ok", else: "error"),
      database: db_status,
      schema_query: query_status,
      pool: pool_status,
      pool_size: pool_size
    })
  end

  defp check_pool_health(pool_size) do
    # DBConnection exposes checked-out count via process dictionary
    # For a rough check, verify we can acquire a connection quickly
    case Ecto.Adapters.SQL.query(Repo, "SELECT 1", [], timeout: 500) do
      {:ok, _} -> :ok
      {:error, %DBConnection.ConnectionError{}} -> :exhausted
      {:error, _} -> :error
    end
  end

  defp check_database do
    case Ecto.Adapters.SQL.query(Repo, "SELECT 1", []) do
      {:ok, _} -> :ok
      _ -> :error
    end
  end

  defp check_schema_query do
    try do
      _ = Repo.one(from u in User, select: u.id, limit: 1)
      :ok
    rescue
      _ -> :error
    end
  end
end

Vigilmon alerts immediately when the pool returns "exhausted" rather than waiting for a complete outage.


Step 4: Instrument Ecto Query Telemetry

TypedEctoSchema-backed schemas emit Ecto Telemetry events for every query. Attach a handler to track slow queries:

# In your Application.start/2
:telemetry.attach(
  "ecto-query-monitor",
  [:my_app, :repo, :query],
  fn _event, %{total_time: total_ns}, %{query: query}, _config ->
    total_ms = System.convert_time_unit(total_ns, :nanosecond, :millisecond)

    if total_ms > 500 do
      require Logger
      Logger.warning("Slow Ecto query (#{total_ms}ms): #{String.slice(query, 0, 120)}")
    end
  end,
  nil
)

Add the Ecto telemetry configuration in your Repo:

# config/config.exs
config :my_app, MyApp.Repo,
  telemetry_prefix: [:my_app, :repo]

For production, expose per-query latency percentiles in a /metrics endpoint (using telemetry_metrics and telemetry_metrics_prometheus) and add a second Vigilmon HTTP monitor that checks the Prometheus scrape endpoint is reachable.


Step 5: Heartbeat for Scheduled Ecto Operations

If your app runs scheduled jobs that write through TypedEctoSchema-backed schemas — data cleanup, aggregation tasks, export pipelines — add a heartbeat:

  1. In Vigilmon, click Add MonitorCron Heartbeat.
  2. Set Expected ping interval to your job frequency (e.g. 1440 minutes for a daily job).
  3. Copy the heartbeat URL.
defmodule MyApp.CleanupJob do
  require Logger

  alias MyApp.{Repo, Audit.EventLog}
  import Ecto.Query

  def run do
    cutoff = DateTime.add(DateTime.utc_now(), -30, :day)

    {deleted, _} =
      Repo.delete_all(
        from e in EventLog,
          where: e.inserted_at < ^cutoff
      )

    Logger.info("Deleted #{deleted} stale event log entries")
    ping_vigilmon()
  end

  defp ping_vigilmon do
    url = System.get_env("VIGILMON_CLEANUP_HEARTBEAT_URL")
    if url, do: :httpc.request(:get, {String.to_charlist(url), []}, [], [])
  end
end

If the job fails before the ping, Vigilmon alerts after the window expires.


Step 6: Monitor the Database SSL Certificate

Most production Ecto configurations connect over TLS. Monitor the certificate separately from the application health check:

  1. Open your Vigilmon project and click Add MonitorHTTP / HTTPS.
  2. Enter the HTTPS URL of your app (not just /health) to get the certificate from the application's TLS terminator.
  3. Enable Monitor SSL certificate.
  4. Set Alert when certificate expires in less than 21 days.

Alternatively, monitor the database host directly if your provider exposes a status URL. Add your database replica endpoint as a second monitor — many Aurora or Cloud SQL setups have separate read replica endpoints that can go stale independently.


Summary

| Monitor | Target | What It Catches | |---|---|---| | HTTP health check | /health on your app | Database down, Ecto query failures | | Response keyword | "ok" in body | Schema query degraded despite 200 response | | Pool timeout check | Short-timeout SELECT 1 | Connection pool exhaustion | | Cron heartbeat | Heartbeat URL | Scheduled Ecto write jobs silently failing | | SSL certificate | App TLS endpoint | Certificate expiry breaking DB connections | | Ecto telemetry | Query event handler | Slow queries approaching timeout thresholds |

TypedEctoSchema makes your database schemas more correct at compile time — Vigilmon makes sure the runtime database they point to stays healthy. With a schema-exercising health endpoint, connection pool monitoring, and heartbeats for scheduled writes, you get full observability over the data layer.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →