alibaba/spring-ai-alibaba · error · Exception

Unable to update checkpoint

Error message

Unable to update checkpoint

What it means

updateCheckpoint() wraps SQLException or IOException raised during the transactional update of a checkpoint into an Exception with this message, after logging and rolling back. Unlike the zero-rows case (NoSuchElementException), this indicates a database or encoding failure, not a missing record.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/mysql/MysqlSaver.java:534

				preparedStatement.setString(2, checkpoint.getNodeId());
				preparedStatement.setString(3, checkpoint.getNextNodeId());
				preparedStatement.setString(4, encodeState(checkpoint.getState()));
				preparedStatement.setString(5, threadName);
				preparedStatement.setString(6, checkpointId);
				int rowsAffected = preparedStatement.executeUpdate();
				if (rowsAffected == 0) {
					conn.rollback();
					throw new NoSuchElementException(format("Checkpoint with id %s not found!", checkpointId));
				}
			}

			conn.commit();
			log.debug("Checkpoint with id {} for thread {} updated successfully.", checkpoint.getId(), threadName);
		}
		catch (SQLException | IOException ex) {
			log.error("Error updating checkpoint with id {} in thread {}", checkpoint.getId(), threadName, ex);
			rollback(conn, checkpoint, threadName);
			throw new Exception("Unable to update checkpoint", ex);
		}
	}

	@Override
	protected void deleteCheckpoints(String threadName, Collection<String> checkpointIds) throws Exception {
		if (checkpointIds.isEmpty()) {
			return;
		}
		try (Connection connection = dataSource.getConnection();
				PreparedStatement preparedStatement = connection.prepareStatement(
						DELETE_CHECKPOINTS.formatted(String.join(", ", Collections.nCopies(checkpointIds.size(), "?"))))) {
			preparedStatement.setString(1, threadName);
			int index = 2;
			for (String checkpointId : checkpointIds) {
				preparedStatement.setString(index++, checkpointId);
			}
			preparedStatement.executeUpdate();
		}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Read the logged cause to identify the SQL error (deadlock, timeout, schema).
  2. Retry the update on transient failures (deadlock/lock-wait-timeout) with backoff.
  3. Ensure all updated state values are Serializable.
  4. Validate table schema against the current library version and re-run migrations.

Example fix

// before
saver.updateCheckpoint(threadId, cpId, cp); // throws on transient deadlock
// after
try {
    saver.updateCheckpoint(threadId, cpId, cp);
} catch (Exception e) {
    if (isTransient(e.getCause())) retryWithBackoff(() -> saver.updateCheckpoint(threadId, cpId, cp));
    else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// serialize-check updated state first
byte[] probe;
try (ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream())) { oos.writeObject(state); }

Try / catch

try {
    saver.updateCheckpoint(threadId, cpId, cp);
} catch (Exception e) {
    if (isTransient(e.getCause())) retryWithBackoff(() -> saver.updateCheckpoint(threadId, cpId, cp));
    else throw e;
}

Prevention

When it happens

Trigger: Updating a checkpoint when the UPDATE statement fails (connection loss, deadlock, lock wait timeout, table/schema mismatch) or encoding the new checkpoint state throws IOException (non-serializable content).

Common situations: Concurrent graph runs deadlocking on the same checkpoint row; MySQL connection dropped mid-transaction; schema drift after upgrade; putting non-serializable objects into updated state.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/ebbbbf390c823f8e. Report an issue: GitHub.