instructure/canvas-lms · error · GraphQLPostgresTimeout::Error

operation timed out

Error message

operation timed out

What it means

Canvas wraps GraphQL query execution so that when PostgreSQL kills a statement for exceeding statement_timeout (PG::QueryCanceled, wrapped in ActiveRecord::StatementInvalid), it logs the query and re-raises as GraphQLPostgresTimeout::Error 'operation timed out' instead of an opaque DB error.

Solutions

  1. Reduce query scope: request fewer/smaller fields and use smaller page sizes with pagination.
  2. Increase postgres statement_timeout or the GraphQL-specific timeout setting if the query is legitimately needed.
  3. Optimize the underlying query/indexes if the timeout is consistently hit.

Example fix

// before
{allCourses { nodes { enrollments { nodes { submissions { nodes { comments { nodes { body } } } } } } } }}
// after
{allCourses(first: 10) { nodes { enrollments(first: 10) { nodes { _id } } } }}
Defensive patterns

Strategy: try-catch

Try / catch

try { const data = await graphQL(query, vars) } catch (e) { if (e.originalError?.message === 'operation timed out') { /* reduce query scope and paginate */ } else throw e }

Prevention

When it happens

Trigger: Running a GraphQL query whose SQL exceeds the postgres statement_timeout — e.g. huge user/collection queries, unbounded pagination, or expensive enrollment/gradebook lookups under load.

Common situations: Development environments with small timeouts querying large production datasets; queries requesting huge page sizes; missing indexes or pathological filters causing long scans.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/a5328c7539958d28. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/graphql_postgres_timeout.rb:40

  class << self
    attr_accessor :do_not_wrap
  end

  TIMEOUT = 60_000

  def self.wrap(query)
    if do_not_wrap
      yield
    else
      ActiveRecord::Base.transaction do
        ActiveRecord::Base.connection.execute "SET statement_timeout = #{TIMEOUT}"
        yield
      rescue ActiveRecord::StatementInvalid => e
        if e.cause.is_a?(PG::QueryCanceled)
          Rails.logger.warn do
            "GraphQL Operation failed due to postgres statement_timeout:\n#{query.query_string}"
          end
          raise GraphQLPostgresTimeout::Error, "operation timed out"
        end
        raise
      end
    end
  end

  Error = Class.new(StandardError)
end

View on GitHub (pinned to 1c9f0bb801)