{"record":{"id":"0db579d8e0ec91c9","repo":"gofr-dev/gofr","slug":"failed-to-acquire-migration-lock","errorCode":null,"errorMessage":"failed to acquire migration lock","messagePattern":"failed to acquire migration lock","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gofr/migration/migration.go","lineNumber":19,"sourceCode":"package migration\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"reflect\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/gogo/protobuf/sortkeys\"\n\t\"github.com/google/uuid\"\n\tgoRedis \"github.com/redis/go-redis/v9\"\n\n\t\"gofr.dev/pkg/gofr/container\"\n\tgofrSql \"gofr.dev/pkg/gofr/datasource/sql\"\n)\n\nvar (\n\terrLockAcquisitionFailed = errors.New(\"failed to acquire migration lock\")\n\terrLockReleaseFailed     = errors.New(\"failed to release migration lock\")\n)\n\nconst (\n\t// lockKey is the key used for distributed locking.\n\tlockKey = \"gofr_migrations_lock\"\n\n\t// Default values for configuration.\n\tdefaultRetry = 500 * time.Millisecond\n\t// defaultLockTTL is the duration for which the migration lock is valid.\n\t// It is kept at 15 seconds to provide a safety margin for network jitters or transient failures.\n\tdefaultLockTTL = 15 * time.Second\n\t// defaultRefresh is the interval at which the migration lock is renewed.\n\t// A 5-second interval allows for up to 2 failed refresh attempts before the 15-second TTL expires,\n\t// ensuring the lock stays robust while still allowing fairly quick recovery if a process crashes.\n\tdefaultRefresh = 5 * time.Second\n)\n","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/migration/migration.go#L1-L37","documentation":"errLockAcquisitionFailed is returned by lock() when the distributed lock (Redis key gofr_migrations_lock) cannot be acquired before running migrations. This prevents concurrent instances from migrating the same datastore simultaneously. It usually means another process holds the lock or the lock store is unavailable.","triggerScenarios":"Another app instance holds gofr_migrations_lock; SETNX fails because the key exists; Redis client error while attempting to set the lock key.","commonSituations":"Multiple pods deployed and one crashed without releasing the lock (stale lock); Redis misconfigured/unreachable; long-running migration overlapping a deployment.","solutions":["Confirm no other migration is currently running (check lock owner/TTL on gofr_migrations_lock)","If the lock is stale (crashed holder), delete the key or wait for TTL expiry","Verify Redis connectivity and auth in container config","Re-run migrations once the lock is free"],"exampleFix":"// before\nredis-cli SET gofr_migrations_lock 1 NX // keeps failing: key held by crashed pod\n// after\nredis-cli DEL gofr_migrations_lock // remove stale lock, then rerun migration","handlingStrategy":"retry","validationCode":"// check the lock is free before attempting\nn, _ := redisClient.Exists(ctx, \"gofr_migrations_lock\").Result()\nif n == 1 { return errors.New(\"another migration is in progress; wait or clear stale lock\") }","typeGuard":null,"tryCatchPattern":"err := migrator.Run(c)\nif errors.Is(err, migration.ErrLockAcquisitionFailed) {\n    // wait and retry with backoff until the holder finishes or TTL expires\n    time.Sleep(10 * time.Second)\n    return migrator.Run(c)\n}","preventionTips":["Run migrations from a single serialized job (init container / pre-deploy hook)","Always set a TTL on the lock so crashed holders self-release","Monitor for stale gofr_migrations_lock keys","Verify Redis connectivity in config before deploying"],"tags":["migration","distributed-lock","redis"],"backgroundTag":"lock-acquisition-failed","analyzedSha":"187eb24962502e91f1fee856230670958b66e89c","analyzedAt":"2026-09-01T20:34:54.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}