Em releases mais recentes do MongoDB, depois da 7.0, o downgrade, que antes era direto e tinha processo documentado, virou incógnita quando a Mongo Inc. publicou isto: Downgrade 7.0 to 6.0
-
Binary downgrades are no longer supported for MongoDB Community Edition.
-
The setFeatureCompatibilityVersion command requires an additional parameter, confirm, which must be set to true to upgrade or downgrade FCV*.*
-
MongoDB only supports single-version downgrades. You cannot downgrade to a release that is multiple versions behind your current release.
-
For example, you may downgrade a 7.0-series to a 6.0-series deployment. However, further downgrading that 6.0-series deployment to a 5.0-series deployment is not supported.
O procedimento ficou ainda mais enxuto no MongoDB 8.0: Downgrade 8.0 to 7.0
- Binary downgrades are not supported for MongoDB Community Edition.
Só isso nas release notes. Resumindo:
- Enterprise + suporte: provavelmente você está coberto.
- Community sem suporte: se der problema depois do upgrade e quiser voltar, pode ficar sem saída.
Daí a pergunta:
“Mas, mas o processo de downgrade ainda é possível?”
Sim, com ressalvas. Detalho abaixo. Primeiro, como funciona no MongoDB 8.0.
- Cenário típico:
- Upgrade completo de 7.0.x para 8.0.x, FCV (featureCompatibilityVersion) em 8.0, e agora você quer voltar para 7.0.
rs0 [direct: primary] admin> db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
{
featureCompatibilityVersion: { version: '8.0' },
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1739388689, i: 1 }),
signature: {
hash: Binary.createFromBase64('Ufa9JQOJi8bbBFs0VPuQw1wZhi4=', 0),
keyId: Long('7470593229067386887')
}
},
operationTime: Timestamp({ t: 1739388689, i: 1 })
}
Como está documentado, upgrade e downgrade passam por 4 fases para mudar a versão, tudo via FCV.
- O FCV expõe a versão desejada no cluster e liga/desliga features conforme a release de destino.
- Daí o “sim, mas com ressalvas”: você precisa estar livre de features incompatíveis entre releases.
Com isso em mente, as fases internas do downgrade em um Replica Set:
Fase 1: Início da transição
A primeira fase sinaliza ao cluster que o downgrade começou, via setFeatureCompatibilityVersion:
db.adminCommand({setFeatureCompatibilityVersion: "7.0", confirm: true})
Com o comando emitido, o Mongo transiciona para requestedVersion atualizando o documento local de FCV para kDowngradingFrom_X_To_Y. Etapa rápida, deve sempre passar. O Mongo pega lock exclusivo e bloqueia outra mudança de FCV em paralelo:
// Only allow one instance of setFeatureCompatibilityVersion to run at a time.
Lock::ExclusiveLock setFCVCommandLock(opCtx, commandMutex);
No mongo.log, a transição aparece mais ou menos assim:
{"t":{"$date":"2025-07-08T14:24:47.608-03:00"},"s":"I", "c":"-", "id":6744300, "ctx":"conn47","msg":"setFeatureCompatibilityVersion command called","attr":{"upgradeOrDowngrade":"downgrade","serverType":"replica set/maintenance mode","fromVersion":"8.0","toVersion":"7.0"}}
[...]
{"t":{"$date":"2025-07-08T14:24:47.608-03:00"},"s":"I", "c":"REPL", "id":20459, "ctx":"conn47","msg":"Setting featureCompatibilityVersion","attr":{"currentVersion":"8.0","newVersion":"downgrading from 8.0 to 7.0"}}
[...]
{"t":{"$date":"2025-07-08T14:24:47.618-03:00"},"s":"I", "c":"-", "id":6744301, "ctx":"conn47","msg":"setFeatureCompatibilityVersion has set the FCV to the transitional state","attr":{"upgradeOrDowngrade":"downgrade","serverType":"replica set/maintenance mode","fromVersion":"8.0","toVersion":"7.0"}}
- Mongo reconhece downgrade 8.0 → 7.0.
- Atualiza metadados do FCV em
admin.system.version. - Entra no estado de transição.
Nessa fase o Mongo também aborta index builders incompatíveis e atualiza o wire protocol interno.
Fase 2: Validação e preparação
Aqui o Mongo checa o downgrade via _prepareToDowngrade e funções como _prepareToDowngradeActions, tudo antes de mexer em metadados, sem feature exclusiva do 8.0 rodando. Ainda não há limpeza de metadados nesta fase.
-
_prepareToDowngraderoda o que precisa acontecer antes de alterar metadados no downgrade de FCV. Código novo de downgrade vai nas helpers: -
_prepareToDowngradeActions: downgrade antes de pegar global lock em modo S.
_prepareToUpgradeActions(opCtx, requestedVersion, changeTimestamp);
{
// Take the global lock in S mode to create a barrier for operations taking the global
// IX or X locks. This ensures that either:
// - The global IX/X locked operation will start after the FCV change, see the
// upgrading to the latest FCV and act accordingly.
// - The global IX/X locked operation began prior to the FCV change, is acting on that
// assumption and will finish before upgrade procedures begin right after this.
Lock::GlobalLock lk(opCtx, MODE_S);
}
-
_userCollectionsUassertsForDowngrade: checagens em dados/settings do usuário; disparauassertcomCannotDowngradese precisar limpar manualmente.
uassert(ErrorCodes::Error(549181),
"Failing downgrade due to 'failDowngrading' failpoint set",
!failDowngrading.shouldFail());
hangWhileDowngrading.pauseWhileSet(opCtx);
// This helper function is for any uasserts for users to clean up user collections. Uasserts
// for users to change settings or wait for settings to change should also happen here.
// These uasserts happen before the internal server downgrade cleanup. The code in this
// helper function is required to be idempotent in case the node crashes or downgrade fails
// in a way that the user has to run setFCV again. The code added/modified in this helper
// function should not leave the server in an inconsistent state if the actions in this
// function failed part way through.
// This helper function can only fail with some transient error that can be retried (like
// InterruptedDueToReplStateChange) or ErrorCode::CannotDowngrade. The uasserts added to
// this helper function can only have the CannotDowngrade error code indicating that the
// user must manually clean up some user data in order to retry the FCV downgrade.
_userCollectionsUassertsForDowngrade(opCtx, requestedVersion);
É aqui que o MongoDB barra o downgrade se encontrar feature do 8.0 que o 7.0 não suporta. Antes de tentar, revise a versão e veja se o ambiente usa alguma delas:
Pra mostrar a falha no prepare, habilitamos featureFlagRecordIdsReplicated:
/mongod --replSet replset --dbpath /replset/rs1/db --logpath /replset/rs1/mongod.log --port 8083 --fork --keyFile /keyfile --wiredTigerCacheSizeGB 1 --setParameter featureFlagRecordIdsReplicated=true
No 8.0, essa feature mantém RecordId consistente entre membros do replica set. RecordIds são IDs internos do storage engine; com recordIdsReplicated ligado, o primário inclui RecordIds no oplog (campo rid) e força secundários a usar os mesmos RecordIds pros mesmos documentos. O 7.0 não entende essa mudança no protocolo de replicação; downgrade fica inviável. Exemplo:
- Com
recordIdsReplicated: true
replset [direct: primary] blogpost> db.createCollection("test_recordids", {
recordIdsReplicated: true
})
replset [direct: primary] blogpost> db.test_recordids.insertMany([
... { name: "Alice", age: 25, city: "New York" },
... { name: "Bob", age: 30, city: "London" },
... { name: "Charlie", age: 35, city: "Tokyo" }
... ])
{
acknowledged: true,
insertedIds: {
'0': ObjectId('686ea382fd1484cd608b1fbd'),
'1': ObjectId('686ea382fd1484cd608b1fbe'),
'2': ObjectId('686ea382fd1484cd608b1fbf')
}
}
replset [direct: primary] local> db.oplog.rs.find({"ns": "blogpost.test_recordids","op": "i"},{'o._id':1 ,rid: 1})
[
{ o: { _id: ObjectId('686ea382fd1484cd608b1fbd') }, rid: Long('1') },
{ o: { _id: ObjectId('686ea382fd1484cd608b1fbe') }, rid: Long('2') },
{ o: { _id: ObjectId('686ea382fd1484cd608b1fbf') }, rid: Long('3') }
]
-- SECONDARY --
use local
replset [direct: secondary] local> db.oplog.rs.find({"ns": "blogpost.test_recordids","op": "i"},{'o._id':1 ,rid: 1})
[
{ o: { _id: ObjectId('686ea382fd1484cd608b1fbd') }, rid: Long('1') },
{ o: { _id: ObjectId('686ea382fd1484cd608b1fbe') }, rid: Long('2') },
{ o: { _id: ObjectId('686ea382fd1484cd608b1fbf') }, rid: Long('3') }
]
Cada documento replica com o rid que veio do PRIMARY. Em collection normal, a replicação é assim:
replset [direct: primary] blogpost> db.test_regular.insertMany([
... { name: "Alice", age: 25, city: "New York" },
... { name: "Bob", age: 30, city: "London" },
... { name: "Charlie", age: 35, city: "Tokyo" }
... ])
{
acknowledged: true,
insertedIds: {
'0': ObjectId('686ea5ad15ad32da8c8b1fbd'),
'1': ObjectId('686ea5ad15ad32da8c8b1fbe'),
'2': ObjectId('686ea5ad15ad32da8c8b1fbf')
}
}
replset [direct: primary] local> db.oplog.rs.find({"ns": "blogpost.test_regular","op": "i"},{'o._id':1 ,rid: 1})
[
{ o: { _id: ObjectId('686ea5ad15ad32da8c8b1fbd') } },
{ o: { _id: ObjectId('686ea5ad15ad32da8c8b1fbe') } },
{ o: { _id: ObjectId('686ea5ad15ad32da8c8b1fbf') } }
]
Sem rid. Tentando o downgrade, a prepare phase devolve:
replset [direct: primary] blogpost> db.adminCommand( { setFeatureCompatibilityVersion: "7.0", confirm: true } )
MongoServerError[CannotDowngrade]: Cannot downgrade the cluster when there are collections with 'recordIdsReplicated' enabled. Please unset the option or drop the collection(s) before downgrading. First detected collection with 'recordIdsReplicated' enabled: blogpost.test_recordids (UUID: 0ed7067b-8384-4e08-b66e-f0a781405ea6).
{"t":{"$date":"2025-07-09T02:06:40.869-03:00"},"s":"I", "c":"-", "id":6744301, "ctx":"conn42","msg":"setFeatureCompatibilityVersion has set the FCV to the transitional state","attr":{"upgradeOrDowngrade":"downgrade","serverType":"replica set/maintenance mode","fromVersion":"8.0","toVersion":"7.0"}}
{"t":{"$date":"2025-07-09T02:06:40.870-03:00"},"s":"D1", "c":"ASSERT", "id":23074, "ctx":"conn42","msg":"User assertion","attr":{"error":"CannotDowngrade: Cannot downgrade the cluster when there are collections with 'recordIdsReplicated' enabled. Please unset the option or drop the collection(s) before downgrading. First detected collection with 'recordIdsReplicated' enabled: blogpost.test_recordids (UUID: 0ed7067b-8384-4e08-b66e-f0a781405ea6).","file":"src/mongo/db/commands/set_feature_compatibility_version_command.cpp","line":1265}}
{"t":{"$date":"2025-07-09T02:06:40.870-03:00"},"s":"D3", "c":"ASSERT", "id":4892201, "ctx":"conn42","msg":"Internal assertion","attr":{"error":{"code":332,"codeName":"CannotDowngrade","errmsg":"Cannot downgrade the cluster when there are collections with 'recordIdsReplicated' enabled. Please unset the option or drop the collection(s) before downgrading. First detected collection with 'recordIdsReplicated' enabled: blogpost.test_recordids (UUID: 0ed7067b-8384-4e08-b66e-f0a781405ea6)."},"location":"{fileName:\"src/mongo/db/service_entry_point_common.cpp\", line:1449, functionName:\"_handleError\"}"}}
{"t":{"$date":"2025-07-09T02:06:40.870-03:00"},"s":"D3", "c":"ASSERT", "id":4892201, "ctx":"conn42","msg":"Internal assertion","attr":{"error":{"code":332,"codeName":"CannotDowngrade","errmsg":"Cannot downgrade the cluster when there are collections with 'recordIdsReplicated' enabled. Please unset the option or drop the collection(s) before downgrading. First detected collection with 'recordIdsReplicated' enabled: blogpost.test_recordids (UUID: 0ed7067b-8384-4e08-b66e-f0a781405ea6)."},"location":"{fileName:\"src/mongo/db/service_entry_point_common.cpp\", line:730, functionName:\"run\"}"}}
{"t":{"$date":"2025-07-09T02:06:40.870-03:00"},"s":"D1", "c":"COMMAND", "id":21962, "ctx":"conn42","msg":"Assertion while executing command","attr":{"command":"setFeatureCompatibilityVersion","db":"admin","commandArgs":{"setFeatureCompatibilityVersion":"7.0","confirm":true,"lsid":{"id":{"$uuid":"88d019cc-8649-4963-9e7c-1297c79d45de"}},"$clusterTime":{"clusterTime":{"$timestamp":{"t":1752037588,"i":1}},"signature":{"hash":{"$binary":{"base64":"rK0IXY19pYAiE/3DaS3Rw4rRhm8=","subType":"0"}},"keyId":7519659317708980230}},"$readPreference":{"mode":"primaryPreferred"},"$db":"admin"},"error":"CannotDowngrade: Cannot downgrade the cluster when there are collections with 'recordIdsReplicated' enabled. Please unset the option or drop the collection(s) before downgrading. First detected collection with 'recordIdsReplicated' enabled: blogpost.test_recordids (UUID: 0ed7067b-8384-4e08-b66e-f0a781405ea6)."}}
{"t":{"$date":"2025-07-09T02:06:40.870-03:00"},"s":"I", "c":"COMMAND", "id":51803, "ctx":"conn42","msg":"Slow query","attr":{"type":"command","isFromUserConnection":true,"ns":"admin.$cmd","collectionType":"admin","appName":"mongosh 2.5.3","command":{"setFeatureCompatibilityVersion":"7.0","confirm":true,"lsid":{"id":{"$uuid":"88d019cc-8649-4963-9e7c-1297c79d45de"}},"$clusterTime":{"clusterTime":{"$timestamp":{"t":1752037588,"i":1}},"signature":{"hash":{"$binary":{"base64":"rK0IXY19pYAiE/3DaS3Rw4rRhm8=","subType":"0"}},"keyId":7519659317708980230}},"$readPreference":{"mode":"primaryPreferred"},"$db":"admin"},"numYields":0,"ok":0,"errMsg":"Cannot downgrade the cluster when there are collections with 'recordIdsReplicated' enabled. Please unset the option or drop the collection(s) before downgrading. First detected collection with 'recordIdsReplicated' enabled: blogpost.test_recordids (UUID: 0ed7067b-8384-4e08-b66e-f0a781405ea6).","errName":"CannotDowngrade","errCode":332,"reslen":509,"locks":{"MultiDocumentTransactionsBarrier":{"acquireCount":{"R":1}},"ReplicationStateTransition":{"acquireCount":{"w":10}},"Global":{"acquireCount":{"r":8,"w":3,"R":1}},"Database":{"acquireCount":{"r":6,"w":3}},"Collection":{"acquireCount":{"r":4,"w":3}},"Mutex":{"acquireCount":{"W":2}}},"flowControl":{"acquireCount":3},"readConcern":{"level":"local","provenance":"implicitDefault"},"writeConcern":{"w":"majority","wtimeout":0,"provenance":"implicitDefault"},"storage":{},"cpuNanos":2236818,"remote":"127.0.0.1:60690","protocol":"op_msg","queues":{"execution":{"admissions":11},"ingress":{"admissions":2}},"workingMillis":7,"durationMillis":7}}
-
Fase 3: Limpeza (
_runDowngradecomisCleaningServerMetadata)
Passou a Fase 2, entra a Fase 3: limpeza de metadados internos do servidor, o downgrade de fato.
O Mongo remove metadados específicos do 8.0 e ajusta o que precisa pra rodar sob restrições do 7.0.
// Set the isCleaningServerMetadata field to true. This prohibits the downgrading to
// upgrading transition until the isCleaningServerMetadata is unset when we successfully
// finish the FCV downgrade and transition to the DOWNGRADED state.
{
const auto fcvChangeRegion(FeatureCompatibilityVersion::enterFCVChangeRegion(opCtx));
FeatureCompatibilityVersion::updateFeatureCompatibilityVersionDocument(
opCtx,
actualVersion,
requestedVersion,
isFromConfigServer,
changeTimestamp,
true /* setTargetVersion */,
true /* setIsCleaningServerMetadata*/);
}
A flag impede o cluster de voltar pra upgrade no meio da limpeza. Downgrade precisa terminar de uma vez.
Outra peça central: _internalServerCleanupForDowngrade:
// This helper function is for any internal server downgrade cleanup, such as dropping
// collections or aborting. This cleanup will happen after user collection downgrade
// cleanup.
// The code in this helper function is required to be IDEMPOTENT and RETRYABLE in case the
// node crashes or downgrade fails in a way that the user has to run setFCV again. It cannot
// fail for a non-retryable reason since at this point user data has already been cleaned
// up.
// It also MUST be able to be rolled back. This is because we cannot guarantee the safety of
// any server metadata that is not replicated in the event of a rollback.
//
// This helper function can only fail with some transient error that can be retried
// (like InterruptedDueToReplStateChange), ManualInterventionRequired, or fasserts. For
// any non-retryable error in this helper function, it should error either with an
// uassert with ManualInterventionRequired as the error code (indicating a server bug
// but that all the data is consistent on disk and for reads/writes) or with an fassert
// (indicating a server bug and that the data is corrupted). ManualInterventionRequired
// and fasserts are errors that are not expected to occur in practice, but if they did,
// they would turn into a Support case.
_internalServerCleanupForDowngrade(
opCtx, getTransitionFCVInfo(fcvSnapshot.getVersion()).from, requestedVersion);
Três requisitos dessa cleanup function:
- IDEMPOTENT: pode rodar de novo sem quebrar
- RETRYABLE: recupera de falha transitória
- ROLLBACK SAFE: dá pra desfazer se precisar
No log, Fase 3 aparece assim:
{"t":{"$date":"2025-07-09T15:00:42.458-03:00"},"s":"I", "c":"WRITE", "id":51803, "ctx":"conn616","msg":"Slow query","attr":{"type":"update","isFromUserConnection":true,"ns":"admin.system.version","collectionType":"system","appName":"mongosh 2.5.3","command":{"q":{"_id":"featureCompatibilityVersion"},"u":{"_id":"featureCompatibilityVersion","version":"7.0","targetVersion":"7.0","previousVersion":"8.0","isCleaningServerMetadata":true},"multi":false,"upsert":true},"planSummary":"IDHACK","planningTimeMicros":26,"totalOplogSlotDurationMicros":103,"keysExamined":1,"docsExamined":1,"nMatched":1,"nModified":1,"nUpserted":0,"keysInserted":0,"keysDeleted":0,"numYields":0,"locks":{"MultiDocumentTransactionsBarrier":{"acquireCount":{"R":1}},"ReplicationStateTransition":{"acquireCount":{"w":18}},"Global":{"acquireCount":{"r":15,"w":4,"R":1}},"Database":{"acquireCount":{"r":11,"w":4}},"Collection":{"acquireCount":{"r":25,"w":4}},"Mutex":{"acquireCount":{"W":3}},"oplog":{"acquireCount":{"r":1}}},"flowControl":{"acquireCount":4},"readConcern":{"level":"local","provenance":"implicitDefault"},"storage":{"data":{"txnBytesDirty":889}},"cpuNanos":403960,"remote":"127.0.0.1:47132","queues":{"execution":{"admissions":19},"ingress":{"admissions":3}},"workingMillis":0,"durationMillis":0}}
Documento de FCV com isCleaningServerMetadata:true; MongoDB roda cleanup de cluster parameters.
-
Fase 4: Finalização
Downgrade não tem _finalizeDowngrade separado; upgrade tem _finalizeUpgrade. Downgrade fecha com um update atômico no documento.
- Upgrade ativa features novas depois do FCV; downgrade só remove. Isso já rolou na Fase 3.
Um update atômico leva o FCV do estado transitório pra versão final:
- Antes da Fase 4 (estado
isCleaningServerMetadata):
{
"_id": "featureCompatibilityVersion",
"version": "7.0",
"targetVersion": "7.0",
"previousVersion": "8.0",
"isCleaningServerMetadata": true
}
- Depois da Fase 4 (estado final):
{
"_id": "featureCompatibilityVersion",
"version": "7.0"
}
No código-fonte, isso fica em SetFeatureCompatibilityVersionCommand:
// Complete transition by updating the local FCV document to the fully upgraded or
// downgraded requestedVersion.
const auto fcvChangeRegion(FeatureCompatibilityVersion::enterFCVChangeRegion(opCtx));
uassert(ErrorCodes::Error(6794601),
"Failing downgrade due to 'failBeforeUpdatingFcvDoc' failpoint set",
!failBeforeUpdatingFcvDoc.shouldFail());
hangBeforeUpdatingFcvDoc.pauseWhileSet();
FeatureCompatibilityVersion::updateFeatureCompatibilityVersionDocument(
opCtx,
serverGlobalParams.featureCompatibility.acquireFCVSnapshot().getVersion(),
requestedVersion,
isFromConfigServer,
changeTimestamp,
false /* setTargetVersion */,
false /* setIsCleaningServerMetadata */);
}
Deu certo, o mongo.log registra algo nesta linha:
{"t":{"$date":"2025-07-09T02:00:11.129-03:00"},"s":"I", "c":"-", "id":6744302, "ctx":"conn50","msg":"setFeatureCompatibilityVersion succeeded","attr":{"upgradeOrDowngrade":"downgrade","serverType":"replica set/maintenance mode","fromVersion":"8.0","toVersion":"7.0"}}
E os binários?
Aqui fica nebuloso. FCV está bem detalhado no código, docs internas e em parte na doc pública. Downgrade de binário quase não aparece. Só cavando o código, que também fala pouco. Diferença de arquitetura: FCV tem fases e validação explícitas; trocar binário é trial-and-error. Ao substituir os binários, antes mesmo do FCV, rola checagem automática de compatibilidade dos arquivos:
void WiredTigerKVEngine::_openWiredTiger(const std::string& path, const std::string& wtOpenConfig) {
// MongoDB 4.4 will always run in compatibility version 10.0.
std::string configStr = wtOpenConfig + ",compatibility=(require_min=\"10.0.0\")";
auto wtEventHandler = _eventHandler.getWtEventHandler();
int ret = wiredtiger_open(path.c_str(), wtEventHandler, configStr.c_str(), &_conn);
if (!ret) {
_fileVersion = {WiredTigerFileVersion::StartupVersion::IS_44_FCV_44};
return;
}
if (_eventHandler.isWtIncompatible()) {
// WT 4.4+ will refuse to startup on datafiles left behind by 4.0 and earlier. This behavior
// is enforced outside of `require_min`. This condition is detected via a specific error
// message from WiredTiger.
if (_inRepairMode) {
// In case this process was started with `--repair`, remove the "repair incomplete"
// file.
StorageRepairObserver::get(getGlobalServiceContext())->onRepairDone(nullptr);
}
LOGV2_FATAL_NOTRACE(
4671205,
"This version of MongoDB is too recent to start up on the existing data files. "
"Try MongoDB 4.2 or earlier.");
}
[...]
Na prática, trial-and-error acontece em todo startup. O Mongo valida se os data files batem com aquele binário.
Conclusão
Nada te impede de fazer downgrade. O procedimento oficial sumiu, mas dá pra olhar os antigos, tipo 6.0 to 5.0. A doc desencoraja downgrade de binário na Community, mas o FCV por baixo é o que faz o trabalho pesado na troca de versão. O processo é bem estruturado. Duas opções na prática:
- Só FCV.
- Binários 8.0, FCV 7.0 (Supported Process).
- FCV + binários juntos.
- Cai no Unsupported Process.
Dois fatores na decisão: tem contrato de suporte ou não.
- Com suporte: segue o plano deles.
- Sem suporte: Supported vs Unsupported importa menos. Você é o suporte; entenda o risco.
Backup/restore decente, teste antes, Replica Set/Shard bem montado. Isso corta risco, inclusive na troca de binário. Entenda o fluxo, respeite as travas do FCV e teste no seu ambiente antes. Comentários abertos. See ya!