{"record":{"id":"a5328c7539958d28","repo":"instructure/canvas-lms","slug":"operation-timed-out","errorCode":null,"errorMessage":"operation timed out","messagePattern":"operation timed out","errorType":"exception","errorClass":"GraphQLPostgresTimeout::Error","httpStatus":null,"severity":"error","filePath":"app/graphql/graphql_postgres_timeout.rb","lineNumber":40,"sourceCode":"  class << self\n    attr_accessor :do_not_wrap\n  end\n\n  TIMEOUT = 60_000\n\n  def self.wrap(query)\n    if do_not_wrap\n      yield\n    else\n      ActiveRecord::Base.transaction do\n        ActiveRecord::Base.connection.execute \"SET statement_timeout = #{TIMEOUT}\"\n        yield\n      rescue ActiveRecord::StatementInvalid => e\n        if e.cause.is_a?(PG::QueryCanceled)\n          Rails.logger.warn do\n            \"GraphQL Operation failed due to postgres statement_timeout:\\n#{query.query_string}\"\n          end\n          raise GraphQLPostgresTimeout::Error, \"operation timed out\"\n        end\n        raise\n      end\n    end\n  end\n\n  Error = Class.new(StandardError)\nend\n","sourceCodeStart":22,"sourceCodeEnd":49,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/graphql_postgres_timeout.rb#L22-L49","documentation":"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.","triggerScenarios":"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.","commonSituations":"Development environments with small timeouts querying large production datasets; queries requesting huge page sizes; missing indexes or pathological filters causing long scans.","solutions":["Reduce query scope: request fewer/smaller fields and use smaller page sizes with pagination.","Increase postgres statement_timeout or the GraphQL-specific timeout setting if the query is legitimately needed.","Optimize the underlying query/indexes if the timeout is consistently hit."],"exampleFix":"// before\n{allCourses { nodes { enrollments { nodes { submissions { nodes { comments { nodes { body } } } } } } } }}\n// after\n{allCourses(first: 10) { nodes { enrollments(first: 10) { nodes { _id } } } }}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { const data = await graphQL(query, vars) } catch (e) { if (e.originalError?.message === 'operation timed out') { /* reduce query scope and paginate */ } else throw e }","preventionTips":["Paginate with first/after instead of unbounded lists","Request only needed fields","Watch statement_timeout settings when querying large datasets","Add indexes for filters used by heavy GraphQL queries"],"tags":["graphql","postgres","timeout"],"backgroundTag":"request-timeout","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}