{"record":{"id":"51c8ae6fae57313e","repo":"instructure/canvas-lms","slug":"operation-timed-out-mutation-type","errorCode":null,"errorMessage":"operation timed out","messagePattern":"operation timed out","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/types/mutation_type.rb","lineNumber":27,"sourceCode":"# the terms of the GNU Affero General Public License as published by the Free\n# Software Foundation, version 3 of the License.\n#\n# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY\n# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\n# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nclass PostgresTimeoutFieldExtension < GraphQL::Schema::FieldExtension\n  def resolve(object:, arguments:, context:, **)\n    GraphQLPostgresTimeout.wrap(context.query) do\n      yield(object, arguments)\n    end\n  rescue GraphQLPostgresTimeout::Error\n    raise GraphQL::ExecutionError, \"operation timed out\"\n  end\nend\n\nclass Types::MutationType < Types::ApplicationObjectType\n  graphql_name \"Mutation\"\n\n  ##\n  # wraps all mutation fields with necessary\n  # extensions (e.g. pg timeout)\n  def self.field(*, **)\n    super(*, **, extensions: [PostgresTimeoutFieldExtension, AuditLogFieldExtension])\n  end\n\n  field :add_conversation_message, mutation: Mutations::AddConversationMessage\n  field :create_conversation, mutation: Mutations::CreateConversation\n  field :create_group_in_set, mutation: Mutations::CreateGroupInSet\n  field :create_group_set, mutation: Mutations::CreateGroupSet\n  field :hide_assignment_grades, mutation: Mutations::HideAssignmentGrades","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/types/mutation_type.rb#L9-L45","documentation":"PostgresTimeoutFieldExtension wraps every mutation resolution in GraphQLPostgresTimeout.wrap; if the mutation's SQL exceeds the configured statement timeout, GraphQLPostgresTimeout::Error is rescued and re-raised as GraphQL::ExecutionError 'operation timed out'.","triggerScenarios":"Any mutation whose database work exceeds the PostgreSQL statement timeout set by GraphQLPostgresTimeout (large batch updates, missing indexes, huge submissions/course datasets).","commonSituations":"Timeout configured too low for heavy mutations; unindexed queries under new data volumes; long-running bulk import mutations; DB load spikes pushing previously-fast queries over the limit.","solutions":["Optimize the mutation's queries (add indexes, batch/slice the work)","Break the mutation into smaller operations or a background job","Raise the GraphQLPostgresTimeout statement timeout if it is set too aggressively","Profile the offending mutation with EXPLAIN ANALYZE to find the slow statement"],"exampleFix":"// before\nmutation { updateGrades(input: { courseGid: ..., allGradeData: <10k rows> }) }  // times out\n// after\n// chunk into a background job, mutate in slices:\nupdateGrades(input: { courseGid: ..., gradeData: <500-row slice> })","handlingStrategy":"retry","validationCode":"// client: keep mutation payloads small\nif (payloadSize > CHUNK_LIMIT) scheduleBackgroundJob(payload)","typeGuard":null,"tryCatchPattern":"try { await mutate(UPDATE) } catch (e) { if (e.message === 'operation timed out') { await retryWithSmallerBatch(mutate, UPDATE) } else throw e }","preventionTips":["Keep mutations small; move bulk work to background jobs","Add indexes for fields the mutation filters on","Profile heavy mutations with EXPLAIN ANALYZE","Add retry-with-backoff for timeouts on idempotent mutations"],"tags":["graphql","timeout","database"],"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"}