apache/pulsar · info · RestException

Migration has already been completed

Error message

Migration has already been completed

What it means

A 409 CONFLICT thrown when starting a metadata migration whose stored state phase is already COMPLETED. The migration is idempotent-by-guard: once finished, the flag in the source store permanently records COMPLETED and further starts are rejected instead of re-running.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/MetadataMigrationBase.java:112

        try {
            // Check if metadata store is wrapped with DualMetadataStore
            if (!(pulsar().getLocalMetadataStore() instanceof DualMetadataStore dualStore)) {
                throw new RestException(Response.Status.BAD_REQUEST, "Metadata store is not configured for migration. "
                        + "Please ensure you're using a supported source metadata store (e.g., ZooKeeper).");
            }

            // Reject the request if a migration is already in progress or was completed. The migration
            // flag is always kept in the source store, so read it from there: after a completed
            // migration the dual store would route the read to the target store.
            var existingFlag = dualStore.getSourceStore().get(MigrationState.MIGRATION_FLAG_PATH).get();
            if (existingFlag.isPresent()) {
                MigrationState currentState = ObjectMapperFactory.getMapper().reader()
                        .readValue(existingFlag.get().getValue(), MigrationState.class);
                switch (currentState.getPhase()) {
                    case PREPARATION, COPYING -> throw new RestException(Response.Status.CONFLICT,
                            "Migration is already in progress (phase: " + currentState.getPhase() + ")");
                    case COMPLETED -> throw new RestException(Response.Status.CONFLICT,
                            "Migration has already been completed");
                    default -> {
                        // NOT_STARTED or FAILED: ok to start (or retry) the migration
                    }
                }
            }

            // Create coordinator
            MigrationCoordinator coordinator = new MigrationCoordinator(pulsar().getLocalMetadataStore(), targetUrl);

            // Start migration in background thread
            pulsar().getExecutor().submit(() -> {
                try {
                    log.info().attr("targetUrl", targetUrl).log("Starting metadata migration");
                    coordinator.startMigration();
                    log.info("Metadata migration completed successfully");
                } catch (Exception e) {
                    log.error().exception(e).log("Metadata migration failed");

View on GitHub (pinned to 820761864e)

Solutions

  1. No action needed — the migration is done; verify the broker is reading from the target store.
  2. Use the migration status endpoint to confirm phase COMPLETED instead of starting again.
  3. If a re-migration is truly required, follow the documented reset procedure (which implies reconfiguring stores/flag), not the start endpoint.

Example fix

// before: unconditional start after DR runbook step
startMigration(target); // 409
// after: skip when already completed
if (getMigrationStatus().getPhase() != Phase.COMPLETED) {
    startMigration(target);
}
Defensive patterns

Strategy: validation

Validate before calling

if (getMigrationStatus().getPhase() == Phase.COMPLETED) {
  return; // migration already finished, skip start
}

Try / catch

try { startMigration(target); }
catch (PulsarAdminException e) {
  if (e.getStatusCode() == 409 && e.getMessage().contains("completed")) {
    // treat as success / no-op
  } else throw e;
}

Prevention

When it happens

Trigger: Re-invoking the migration start endpoint after a prior migration finished successfully; replaying an idempotent automation script; stale client state unaware the migration already ran.

Common situations: Post-migration verification scripts accidentally calling start again; disaster-recovery runbooks that include the start step without checking state; expected behavior after a successful migration.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/d2c26dea3f2b1399. Report an issue: GitHub.