stefanchrobot

stefanchrobot

Hey, recently I started to have issues during deployments to Digital Ocean App Platform. I’m deploying an Elixir release packed as a Docker image. During startup I’m often getting a database connection error:

[myapp] [2023-03-26 09:31:39] 09:31:39.044 [error] Could not create schema migrations table. This error usually happens due to the following:
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39]   * The database does not exist
[myapp] [2023-03-26 09:31:39]   * The "schema_migrations" table, which Ecto uses for managing
[myapp] [2023-03-26 09:31:39]     migrations, was defined by another library
[myapp] [2023-03-26 09:31:39]   * There is a deadlock while migrating (such as using concurrent
[myapp] [2023-03-26 09:31:39]     indexes with a migration_lock)
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39] To fix the first issue, run "mix ecto.create".
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39] To address the second, you can run "mix ecto.drop" followed by
[myapp] [2023-03-26 09:31:39] "mix ecto.create". Alternatively you may configure Ecto to use
[myapp] [2023-03-26 09:31:39] another table and/or repository for managing migrations:
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39]     config :myapp, MyApp.Repo,
[myapp] [2023-03-26 09:31:39]       migration_source: "some_other_table_for_schema_migrations",
[myapp] [2023-03-26 09:31:39]       migration_repo: AnotherRepoForSchemaMigrations
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39] The full error report is shown below.
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39] ** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2483ms. This means requests are coming in and your connection pool cannot serve them fast enough. You can address this by:
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39]   1. Ensuring your database is available and that you can connect to it
[myapp] [2023-03-26 09:31:39]   2. Tracking down slow queries and making sure they are running fast enough
[myapp] [2023-03-26 09:31:39]   3. Increasing the pool_size (although this increases resource consumption)
[myapp] [2023-03-26 09:31:39]   4. Allowing requests to wait longer by increasing :queue_target and :queue_interval
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39] See DBConnection.start_link/2 for more information
[myapp] [2023-03-26 09:31:39] 
[myapp] [2023-03-26 09:31:39]     (ecto_sql 3.9.2) lib/ecto/adapters/sql.ex:913: Ecto.Adapters.SQL.raise_sql_call_error/1
[myapp] [2023-03-26 09:31:39]     (elixir 1.14.3) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
[myapp] [2023-03-26 09:31:39]     (ecto_sql 3.9.2) lib/ecto/adapters/sql.ex:1005: Ecto.Adapters.SQL.execute_ddl/4
[myapp] [2023-03-26 09:31:39]     (ecto_sql 3.9.2) lib/ecto/migrator.ex:677: Ecto.Migrator.verbose_schema_migration/3
[myapp] [2023-03-26 09:31:39]     (ecto_sql 3.9.2) lib/ecto/migrator.ex:491: Ecto.Migrator.lock_for_migrations/4
[myapp] [2023-03-26 09:31:39]     (ecto_sql 3.9.2) lib/ecto/migrator.ex:403: Ecto.Migrator.run/4
[myapp] [2023-03-26 09:31:39]     (ecto_sql 3.9.2) lib/ecto/migrator.ex:146: Ecto.Migrator.with_repo/3
[myapp] [2023-03-26 09:31:39]     nofile:1: (file)

My migration task seems unable to connect to the database.

  • The DB exists and is reachable via other means,
  • The DB connection pool size is 22,
  • The app’s pool size is set to 6,
  • I’m only running one instance,
  • I use zero-downtime deployments, so max connections should be 12 during deployment.

I’m only experiencing this during deployments which makes them fail. Any idea what might be causing this?

Showing Posts 1 to 10

jerdew

jerdew

Not sure if this is the same problem, but on DO I found I had to set the :maintenance_database to defaultdb (Ecto default is postgres) if I wanted to do create/migrate as part of a deploy.

stefanchrobot

stefanchrobot OP

Thanks! I’ve updated the value for :maintenance_database. But it’s something else.

I’ve wrapped my migration function with try do ... catch ... end so that it doesn’t crash. This no longer stops the deployment process - the app is happily booting and connecting to the database :man_shrugging: :thinking: Not sure what is going on here. It feels like it’s working sometimes. Sounds like some sort of a race condition.

Should I be manually starting the :postgrex application?

dimitarvp

dimitarvp

Sorry for a generic suggestion: have you tried the migration_lock option?

ryanwinchester

ryanwinchester

Late to the party, but if you have multiple instances on the app platform, but are they perhaps trying to run migrations at the same time?

stefanchrobot

stefanchrobot OP

I only have one instance running, so the migrations are not attempted by multiple nodes.

For some reason if I connect to the DB manually before running migrations, things seem to work. If I don’t do it, the migration task doesn’t connect. Any ideas why that might be? I’m guessing maybe different connection timeouts?

Just to recap:

  • Digital Ocean App Platform with hosted PostgreSQL over SSL
  • MIGRATION_PRECONNECT=true ./bin/myapp eval MyApp.Release.migrate works
  • MIGRATION_PRECONNECT=false ./bin/myapp eval MyApp.Release.migrate fails
defmodule MyApp.Release do
  @moduledoc """
  Release tasks.
  """

  require Logger

  @app :myapp

  def migration_preconnect do
    Application.ensure_all_started(:ssl)
    Application.ensure_all_started(:postgrex)

    database_url = System.get_env("DATABASE_URL")
    conn_opts = Ecto.Repo.Supervisor.parse_url(database_url)
    Logger.info("Connecting: #{inspect(Keyword.delete(conn_opts, :password))}")

    {:ok, pid} =
      Postgrex.start_link(
        conn_opts ++
          [
            ssl: true,
            ssl_opts: [
              verify: :verify_peer,
              cacerts: [
                "DATABASE_CA_CERT"
                |> System.get_env()
                |> then(fn pem ->
                  [{_type, der, _info}] = :public_key.pem_decode(pem)
                  der
                end)
              ]
            ]
          ]
      )

    Logger.info("Querying...")

    %Postgrex.Result{rows: [[count]]} =
      Postgrex.query!(pid, "SELECT count(*) FROM accounts;", [])

    Logger.info("Found #{count} account(s).")
  end

  def migrate do
    try do
      if System.get_env("MIGRATION_PRECONNECT") do
        migration_preconnect()
      end

      load_app()

      for repo <- repos() do
        {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
      end
    catch
      kind, value ->
        Logger.warning("Migration failed: #{inspect(kind)}, #{inspect(value)}")
    end
  end

  def rollback(repo, version) do
    load_app()
    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
  end

  defp repos do
    Application.fetch_env!(@app, :ecto_repos)
  end

  defp load_app do
    Application.ensure_all_started(:ssl)
    Application.load(@app)
  end
end
johantell

johantell

I actually battled with this error yesterday but on heroku where I got quite unhelpful error messages:

** EXIT (exited #pid<0.98.0>) shutdown

Turned out it was the changed default value for ssl’s verify option (changed in OTP 26). So what @stefanchrobot wrote will probably solve it for you (but if you like me didn’t want to get the certificate injected into the platform yet), you can revert to the old behaviour (although it’s considered to be less safe):

# runtime.exs

config :my_app, MyApp.Repo,
  ssl: true,
  verify_ssl: true,
# repo.ex
defmodule MyApp.Repo do
 # ...
 @impl Ecto.Repo
  def init(_type, config) do
    config =
      if config[:verify_ssl] do
        Keyword.put(config, :ssl_opts, ssl_options(config[:hostname]))
      else
        config
      end

    {:ok, config}
  end


  defp ssl_options(_server) do
    [
      # The `verify` is (since OTP 26) set to `:verify_peer` as default since that is a much safer option.
      # However in order to do so we'll need to merge the AWS certificate into our castore (since it's not
      # there by default).
      #
      # See https://hexdocs.pm/postgrex/Postgrex.html#start_link/1-ssl-client-authentication for more details
      verify: :verify_none,
      cacert: :public_key.cacerts_get()
    ]
  end
end
stefanchrobot

stefanchrobot OP

Not sure if it’s the same issue though. Digital Ocean provides the cert via an ENV variable, so I’m always connecting with SSL.

johantell

johantell

Right, I just got so used to the error by working on it the whole day yesterday that I assumed it would be the same :sweat_smile:.

How are the certificates injected into SSL during normal a normal start of postgres? Are you running OTP 26?

stefanchrobot

stefanchrobot OP

No worries. It might turn out that it does have something to do with SSL.

I’m on OTP26, but the issue was there with OTP25 too. Here’s more or less my repo config:

config :myapp, MyApp.Repo,
  url: System.get_env("DATABASE_URL"),
  maintenance_database: System.get_env("MAINTENANCE_DATABASE"),
  pool_size: System.get_env("DATABASE_POOL_SIZE") |> String.to_integer(),
  log: System.get_env("LOG_LEVEL_ECTO") |> String.to_existing_atom(),
  ssl: true,
  ssl_opts: [
    verify: :verify_peer,
    cacerts: [
      "DATABASE_CA_CERT"
      |> System.get_env()
      |> then(fn pem ->
        [{_type, der, _info}] = :public_key.pem_decode(pem)
        der
      end)
    ]
  ]

As written above, the cert is provided in an ENV variable by Digital Ocean App Platform (PaaS), so it’s just a matter of passing it through.

johantell

johantell

@stefanchrobot Perhaps a very stupid question, but is the application live and running already so that it’s been verified that the ssl_opts is passed through from the repo configuration properly?

I’m assuming that your configuration lives inside the runtime configuration so that shouldn’t be a problem either..

Have you been able to look at the connection logs at the database to see if it’s trying to connect?

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New
thiagogsr
** (ArgumentError) expected :max_attempts to be a positive integer, got: {:@, [line: 10, column: 19], [{:max_attempts, [line: 10, column:...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews