Database and Instance Migration: OrientDB Sunset, H2/PostgreSQL Paths, and Migrator Workflows: Guided Hands-On Workflow and Core Operations
Build a migration the way you would build a release pipeline: establish preconditions, capture source evidence, choose a supported path, execute against disposable state, and compare the target against independent acceptance evidence. The mandatory lab is fixture-driven so it remains free, reproducible, and safe; live Nexus/PostgreSQL steps are optional extensions.
Learning objectives
- Create a synthetic source-state manifest and validate source/target compatibility gates before mutation.
- Model an H2→PostgreSQL database migration without pretending blob content is transferred by Database Migrator.
- Validate database owner, schema, pg_trgm, resource headroom, backup, and downtime prerequisites.
- Compare source/target repositories, components, assets, roles, and SHA-256 evidence after migration.
- Map the fixture to optional current Database Migrator and Instance Migrator workflows without exposing real secrets or production paths.
1. Mandatory lab: create a disposable source
The fixture separates database metadata from blob bytes to make the migration boundary observable.
from pathlib import Path
import hashlib, json, shutil
root=Path('ch26-lab')
if root.exists(): shutil.rmtree(root)
source=root/'source'; target=root/'target'; backup=root/'backup'
(source/'db').mkdir(parents=True); (source/'blobs').mkdir(parents=True)
assets={
'com/example/app/1.0.0/app-1.0.0.jar': b'CH26 APP 1.0.0\n',
'com/example/lib/2.1.0/lib-2.1.0.jar': b'CH26 LIB 2.1.0\n',
}
rows=[]
for path,data in assets.items():
p=source/'blobs'/path; p.parent.mkdir(parents=True,exist_ok=True); p.write_bytes(data)
rows.append({'path':path,'sha256':hashlib.sha256(data).hexdigest(),'repository':'ch26-maven-releases'})
state={
'nexusVersion':'3.95.2-01','java':21,'database':'H2','edition':'Community',
'repositories':[{'name':'ch26-maven-releases','format':'maven2','type':'hosted','blobStore':'ch26-file'}],
'roles':[{'id':'ch26-reader','privileges':['nx-repository-view-maven2-ch26-maven-releases-read']}],
'assets':rows
}
(source/'db/state.json').write_text(json.dumps(state,indent=2)+'\n')
print('source assets:',len(rows))
2. Preflight gate: stop if an assumption is unknown
from pathlib import Path
import json, shutil
root=Path('ch26-lab'); state=json.loads((root/'source/db/state.json').read_text())
preflight={
'backupVerified': True,
'sourceVersion': state['nexusVersion'],
'sourceDb': state['database'],
'targetDb': 'PostgreSQL',
'nexusWillBeStoppedForDatabaseMigrator': True,
'postgresOwner': True,
'pgTrgmInstalled': True,
'schemaCreateUsage': True,
'ramGiB': 16,
'dbDirGiB': 2,
'freeGiB': 12,
}
required=['backupVerified','nexusWillBeStoppedForDatabaseMigrator','postgresOwner','pgTrgmInstalled','schemaCreateUsage']
assert all(preflight[k] for k in required)
assert preflight['ramGiB'] >= 16
assert preflight['freeGiB'] >= max(10,preflight['dbDirGiB']*3)
(root/'preflight.json').write_text(json.dumps(preflight,indent=2)+'\n')
print('PRECHECK: PASS')
The fixture uses a 2 GiB modeled database to demonstrate the current disk-headroom rule. A real operator measures actual filesystem free space, temp space, PostgreSQL capacity, and backup locations.
3. Preserve an independently verifiable source checkpoint
from pathlib import Path
import shutil, hashlib, json
root=Path('ch26-lab'); source=root/'source'; backup=root/'backup'
if backup.exists(): shutil.rmtree(backup)
shutil.copytree(source,backup/'payload')
manifest=[]
for p in sorted((backup/'payload').rglob('*')):
if p.is_file():
manifest.append({'path':p.relative_to(backup/'payload').as_posix(),'sha256':hashlib.sha256(p.read_bytes()).hexdigest()})
(backup/'manifest.json').write_text(json.dumps(manifest,indent=2)+'\n')
print('backup files:',len(manifest))
In a live Nexus migration, use the supported backup procedures from Chapter 25 rather than filesystem-copying an active database. The fixture copy is deliberately not presented as a production H2 backup recipe.
4. Choose the migration engine from topology
def choose(source_db, same_instance, blob_location_changes, source_version):
if source_db == 'OrientDB' and source_version == '3.70.5' and (not same_instance or blob_location_changes):
return 'Instance Migrator candidate — verify target-specific workflow'
if same_instance and not blob_location_changes:
return 'Database Migrator candidate'
return 'Review current Sonatype decision tree; topology needs explicit design'
print(choose('H2', True, False, '3.95.2-01'))
print(choose('OrientDB', False, True, '3.70.5'))
Expected output: H2→PostgreSQL in the same deployment selects Database Migrator; legacy source→new target with content movement selects Instance Migrator as the candidate.
5. Simulate the database migration without moving blobs
from pathlib import Path
import json, shutil
root=Path('ch26-lab'); source=root/'source'; target=root/'target'
if target.exists(): shutil.rmtree(target)
(target/'db').mkdir(parents=True)
state=json.loads((source/'db/state.json').read_text())
state['database']='PostgreSQL'
state['migration']={'tool':'Database Migrator','contentMovedByTool':False,'status':'completed'}
(target/'db/state.json').write_text(json.dumps(state,indent=2)+'\n')
print('target metadata created; target blobs intentionally absent')
At this point, metadata migration has “succeeded,” but target content is not available. That is the intended teaching failure.
6. Prove the blob boundary
from pathlib import Path
import json
root=Path('ch26-lab'); state=json.loads((root/'target/db/state.json').read_text())
missing=[]
for a in state['assets']:
p=root/'target/blobs'/a['path']
if not p.exists(): missing.append(a['path'])
print('missing after DB-only migration:', len(missing))
for x in missing: print(' -',x)
This is the state that a production runbook must prevent. If the actual deployment keeps the same file/object blob store reachable from the migrated database, validate that mapping. If the blob location changes, migrate/copy content using a supported storage or Instance Migrator plan and prove checksums.
7. Complete the modeled storage plan
from pathlib import Path
import shutil
root=Path('ch26-lab')
shutil.copytree(root/'source/blobs', root/'target/blobs')
print('modeled blob transfer complete')
This copy exists only because the lab blobs are ordinary synthetic files under our control. Do not translate this into “copy a live Nexus blob store whenever convenient.” Production storage movement must be coordinated with Nexus state, downtime/cutover, object-store semantics, and the exact supported topology.
8. Validate target counts and immutable content evidence
from pathlib import Path
import hashlib, json
root=Path('ch26-lab')
src=json.loads((root/'source/db/state.json').read_text())
tgt=json.loads((root/'target/db/state.json').read_text())
assert len(src['repositories']) == len(tgt['repositories'])
assert len(src['roles']) == len(tgt['roles'])
assert len(src['assets']) == len(tgt['assets'])
for a in tgt['assets']:
p=root/'target/blobs'/a['path']
assert p.exists(), a['path']
assert hashlib.sha256(p.read_bytes()).hexdigest()==a['sha256'], a['path']
print('TARGET ACCEPTANCE: PASS')
9. Optional PostgreSQL prerequisite inspection
On a disposable PostgreSQL target, verify ownership and extension state before running any real migrator:
SELECT current_database(), current_user;
SELECT pg_get_userbyid(datdba) AS database_owner
FROM pg_database
WHERE datname = current_database();
SELECT extname, extversion
FROM pg_extension
WHERE extname = 'pg_trgm';
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name = 'nexus';
If the Nexus user is not the database owner or
pg_trgm is absent, stop and fix the database
provisioning with the DBA. Do not grant superuser broadly as a
shortcut.
10. Current Database Migrator command shape
# Nexus is stopped. Run from the disposable $data-dir/db directory.
java -Xmx16G -Xms16G -XX:+UseG1GC -XX:MaxDirectMemorySize=28672M -jar nexus-db-migrator-*.jar --migration_type=h2_to_postgres --db_url="jdbc:postgresql://127.0.0.1:5432/nexus_lab?user=nexus_lab&password=password-FAKE_DO_NOT_USE¤tSchema=nexus"
After migration, current guidance includes PostgreSQL maintenance
such as VACUUM(FULL, ANALYZE, VERBOSE);, Nexus startup,
and completion of post-migration rebuild tasks before further
upgrade/cutover work.
11. Optional legacy Instance Migrator mapping
For a real legacy OrientDB migration, the current source gate is 3.70.5. Instance Migrator itself requires Java 25, while the legacy Nexus source remains on its supported Java level. Prepare a separate modern target, configure target blob stores and other documented prerequisites, use a migrator cipher if you need supported encrypted secrets to transfer, and capture the transfer/failure logs. Do not infer that every external identity setting or generated format metadata migrates automatically.
12. Challenge: same counts, wrong blob mapping
Your target reports two assets—the same as source—but the target
repository is configured to ch26-file-new while bytes
were copied into ch26-file-old. Does the migration
pass?
No. Counts prove database rows, not reachable content. Fix the supported repository→blob mapping or move content according to the migration/storage plan; never edit blob metadata files to make the names “match.”
13. Verification and cleanup
-
Save
preflight.json, source/target state, and validation output. - Confirm the source fixture still exists unchanged.
- Confirm the target contains both migrated metadata and the intentionally planned blob content.
-
Delete only the disposable
ch26-labtree after preserving evidence. - For optional live instances, decommission only named lab databases/instances after verifying no shared blob store or database is referenced elsewhere.
14. Knowledge check
Why does the fixture deliberately create a DB-only target first?
To prove that Database Migrator metadata success does not imply blob content moved or is reachable.
What five PostgreSQL preconditions should you prove before migration?
At minimum: database owner is the Nexus user, pg_trgm exists, schema/search-path permissions are correct, capacity/headroom is sufficient, and the Nexus connection configuration is prepared but not prematurely started against an unmigrated database.
Why not start Nexus with the new PostgreSQL configuration before the migration finishes?
Because the target database is not yet the migrated Nexus state; current Sonatype H2→PostgreSQL guidance explicitly prepares PostgreSQL but defers Nexus startup until after migration.
Why do matching component counts not prove migration integrity?
Counts can match while blob paths are wrong or bytes are corrupt. Validate actual asset reachability and cryptographic hashes.
What is the safest status of the source at the end of this lesson?
Untouched and still available as the rollback source until the target has passed the full acceptance and cutover process.
15. Summary and bridge
You practiced the migration lifecycle without risking a live repository: gate assumptions, checkpoint source state, select the right tool, distinguish metadata from content, verify PostgreSQL prerequisites, validate counts plus hashes, and preserve rollback. Lesson 3 turns these mechanics into architecture and operational tradeoffs.
Official references and version notes
- Sonatype: What to Choose — Instance Migrator or Database Migrator — current division between live source→target migration and offline database transformation.
- Sonatype: Instance Migrator — supported OrientDB source/target versions, Java requirement, resumability, logging, and asset migration behavior.
- Sonatype: OrientDB to Self-Hosted PostgreSQL Migration — target preparation, migrator cipher, migrated/excluded state, and known limitations.
- Sonatype: Migrating to a New Database — Database Migrator scenarios, offline requirements, H2→PostgreSQL sequence, and post-migration tasks.
- Sonatype: Upgrading to Nexus Repository 3.71.0 and Beyond — OrientDB 3.70.x migration gate, H2/PostgreSQL paths, and Java transition.
- Sonatype: Nexus Repository Upgrade Paths — current version sequencing and breaking-version gates.
- Sonatype: Reverting Back to OrientDB — why the old database is a recovery point only when matching blob state is restored.
-
Sonatype: Install Nexus Repository with PostgreSQL
— database ownership, schema privileges, and
pg_trgmrequirement. - Sonatype: Nexus Repository Database — H2 production limitations, PostgreSQL recommendation, and OrientDB sunset state.
- Sonatype: Nexus Repository 3.70.x Downloads with OrientDB — final OrientDB line and matching migration downloads.
- Sonatype: Download — current Nexus/Database Migrator/Instance Migrator download references.
- Sonatype: System Requirements — current Java/PostgreSQL requirements and supported database expectations.
- Sonatype: Nexus Repository 3.95.x Release Notes — dated current release line used as the modern target reference.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.