How Multi-Target Migration Works
The Jobs tab runs each job in its own background thread — not sequentially. All jobs start at the same time (up to the concurrency limit you set). Each job maintains a completely independent connection, progress bar, and log. One source database can fan out to 3, 5, or 10 different target servers in a single click.
source database
Thread 1
Thread 2
Thread 3
All three jobs read from the same source simultaneously — each writes to its own target server — wall-clock time = slowest single job, not sum of all.
Part 1 — Setting Up Multi-Target Jobs (Step by Step)
Follow these steps in order. Each step shows exactly what the screen looks like at that point.
↑ Set concurrency to 3 here: all 3 jobs start at once. Set it to 1 to run them sequentially (useful if the source DB is under load).
↑ All three jobs show the same "salesdb" source → "salesdb" target. Without Override Target checked, all three would go to the SAME target server (Step 5 default). See Part 2 below to point each job to a different server.
Part 2 — Override Target: Where to Enter Target Server Info
By default every job uses the target server from Step 5. To send a specific job to a different server (AWS RDS, Azure SQL, on-prem VM, etc.), check "Override target" and load a saved connection profile. Here is exactly how to do it.
AWS-RDS-us-east-1 or
AzureSQL-DR. You only do this once per server.
↑ Fill host, port, credentials → type a profile name → click Save Profile. Repeat for every target server (AWS RDS, Azure SQL DR, on-prem VM, etc.)
• Target DB field (top row, right of "→") — the database name to create/use on the target server. Can be different from the source DB name.
• Target profile dropdown (expanded row) — pick the saved profile for the target server.
• Load button — applies the profile (host, port, credentials) to this job. The server address summary appears to the right of the button after loading.
↑ All three jobs use the same source DB "salesdb" but each overrides the target to a different server. Concurrency = 3 means they all run simultaneously.
combined_migration_report.html
and attach it to your change ticket or DR runbook.
Part 3 — BACPAC Seed for Large Tables (≥0.5 TB)
The standard row-copy engine is efficient up to a few hundred GB. For tables that are 500 GB or larger (Audit log tables, historical fact tables, large JSON blob columns), it is faster and safer to use BACPAC export via SqlPackage combined with a bulk-load import. The tool handles this automatically with the BACPAC Seed button.
| Target | Export | Import method | Supported by tool? |
|---|---|---|---|
| Azure SQL Database | SqlPackage /Action:Export → .bacpac | bcp.exe bulk-load (2-part name + -d flag) |
✅ Built-in — use the Seed button |
| AWS RDS for SQL Server | SqlPackage /Action:Export → .bacpac | Manual — S3 + native restore (see below) | ⛔ Not automated — manual steps required |
Path A — Azure SQL Database → Azure SQL Database
Azure SQL → .bacpac
on local disk
.bacpac → Azure SQL
target database
rows written during
export/import window
Tables are processed sequentially to avoid overloading local disk with multiple large .bacpac files.
SqlPackage /Action:Import requires a completely empty target database (it will error
SQL71659 if any schema or data exists). Since your target already has schema deployed and
other tables migrated by the standard engine, import would fail. Instead the tool extracts
the BCP data files from inside the .bacpac ZIP and bulk-loads them directly with
bcp.exe — this inserts rows into an existing table without touching anything else.Azure SQL note: bcp on Azure SQL requires a two-part name (
schema.table) plus the -d database flag. The three-part name
(database.schema.table) that works on on-prem SQL Server is rejected by Azure SQL
with Invalid object name (NativeError 208). The tool handles this automatically.
Tick the tables too large for direct row-copy. Leave normal tables unchecked.
Path B — Azure SQL Database → AWS RDS for SQL Server (Manual Steps)
On on-prem SQL Server and Azure SQL Database, bcp.exe connects directly over port 1433 and bulk-loads rows into any table you have write access to.
On AWS RDS for SQL Server, AWS controls the underlying OS. There is no mechanism to push a file directly into the instance — bulk operations must go through Amazon S3 via the managed stored procedures (
msdb.dbo.rds_backup_database / rds_restore_database).
Attempting bcp against RDS will either time out at the network layer or fail at
the bulk-load permission level because ADMINISTER BULK OPERATIONS is
not available to RDS users. There is no workaround — AWS deliberately removes this
path to maintain managed-instance guarantees.
Azure SQL → .bacpac
(tool button)
to S3 bucket
in same region as RDS
to RDS instance
(IAM role / option group)
from S3 path
into empty RDS DB
⚠ SqlPackage /Action:Import requires an empty target database. If the RDS database already has schema or data, use native .bak restore via S3 instead (see note below).
kms:Decrypt.
s3:GetObject and s3:ListBucket
on your staging bucket. This is the same setup used for native .bak restores.
SELECT * FROM msdb.dbo.rds_task_status — SqlPackage /Action:Import
on RDS runs as a managed task, not inline. It may take several minutes to appear in task status.
SqlPackage /Action:Import will fail with SQL71659.
The correct approach in that case is to export a native .bak backup
from on-prem or Azure, upload to S3, and restore with
msdb.dbo.rds_restore_database. This replaces the whole database — plan
accordingly with a cutover window.
Part 3B — Complete Workflow: BACPAC Seed + Main Migration Together (Azure → Azure)
BACPAC Seed is designed for tables that already exist on the target. When you use it against a fresh or partially-migrated target, you will hit a specific cascade of errors that look like failures but are actually sequencing problems. This section gives you the step-by-step sequence to complete a full migration where some tables need BACPAC Seed.
You have a source database (e.g.
cloudbasis_Pubconfig2) with 154 tables.
A subset of those tables are very large and must be seeded via BACPAC (e.g. 8 tables).
The remaining 146 tables will be copied by the standard migration engine.
Run them in this order — do not run BACPAC Seed first.
Schema + Data
PK-range copy
Import Only mode
(use .bacpac on disk)
Views / Functions
now succeed
Temporal versioning
Foreign keys
BACPAC Seed step 2 uses the .bacpac files already exported in step 1 — no re-export needed.
BACPAC Seed's bcp import step issues
bcp schema.table in ... against the
target database. If the table doesn't exist yet, bcp returns
NativeError 208 — Invalid object name and the import fails.
The main migration creates all 154 tables (schema + data) first, so all FK parent tables
are in place when BACPAC Seed runs its import. Additionally, the 38 temporal tables and
any views/functions that reference BACPAC tables cannot be applied until those tables
contain data — run Re-apply Schema Objects last.
The BACPAC tables (large ones) will also be copied here via PK-range. That is intentional — this ensures their schema and FK-parent data are on the target. The BACPAC Seed step later will overwrite just the data for the large tables using the fast bcp path.
- Select all 8 tables
- Set output dir to the folder containing the .bacpac files
- Tick "Import only — skip export, use existing .bacpac files"
- The file status panel shows ✓ for each file found on disk, ✗ for missing files
coredata_CustomEditOverrideFilterItemValue.bacpac ✓ 1.24 GB
coredata_PricerParameter.bacpac ✗ not found — will export
coredata_ProviderType.bacpac ✗ not found — will export
- Temporal tables: re-enables
SYSTEM_VERSIONING = ONfor all 38 tables (previously failed because the base tables had no data) - Functions / views referencing BACPAC tables (e.g.
GetCustomEditIdByConfigurationId— previously failed with NativeError 208 oncoredata.Configuration)
Common BACPAC Errors and What They Mean
| Error | Message | Cause | Fix |
|---|---|---|---|
| NativeError 208 | Invalid object name 'schema.Table' | Table does not exist on target — main migration has not run yet | Run main migration first (Step 1 above) |
| Export code 1 | SqlPackage export exited with code 1 | FK chain includes a temporal table or a table SqlPackage can't include in scope (SQL71564) | Check the Migration Log for the exact SQL71xxx error; if the table's data already came from main migration, use Import Only for that table |
| SQLState 22001 | String data right truncation (NativeError 0) | bcp native format expects a wider column than what exists on target; column length mismatch between source and target | Compare CHARACTER_MAXIMUM_LENGTH for that table in source vs target INFORMATION_SCHEMA.COLUMNS; re-run main migration schema-only for that table |
| PK-range fallback | BULK INSERT cannot access a local temp file; falling back to PK-range copy | Azure SQL PaaS does not allow BULK INSERT from local disk — by design | No action needed — PK-range copy works correctly; this is informational only |
| Temporal FAILED ×38 | system-versioning FAILED — Invalid object name 'schema.Table' | Re-apply ran before data was present; the base temporal tables don't exist or are empty | Run Re-apply Schema Objects again after BACPAC Seed and main migration both complete |
| Schema batch failed | FK references invalid table 'dbo.RuleOverrideItem' (1767) | BACPAC table has FK to a table outside the 8-table selection scope — parent table not in the batch | FK parent tables are created by main migration (all 154 tables); table creates fine once parents exist |
Part 4 — Running Small & Large Table Migrations at the Same Time
BACPAC Seed runs on its own independent background thread — completely separate from the normal migration engine. This means you can run small tables concurrently with BACPAC, but only if they target different tables. The right strategy depends on whether you are going to the same target server or a different one.
(see Option 1 below)
no coordination needed
BACPAC Seed and Jobs/Run Migration each run on independent daemon threads — they never block each other at the engine level. The only conflict risk is writing to the same table on the same target server simultaneously.
If BACPAC is loading to AWS RDS while Jobs migrates small tables to Azure SQL DR (or on-prem VM), there is zero conflict. Just start both — BACPAC Seed button first, then switch to the Jobs tab and click Run All. Both run fully in parallel.
→ AWS RDS
→ Azure SQL DR
Split the work by table using the checkbox list on the DB Migration tab:
dbo.AuditLog, dbo.ClaimJson etc.
Leave small tables unchecked. Click BACPAC Seed — it starts on its own thread.… 45% · 4h remaining …
Copying dbo.Product (480K rows) ✓
Copying dbo.OrderLine … 62%
If you want zero risk of any coordination mistake, wait for BACPAC to finish, then click Resume (not Run Migration). Resume automatically detects which tables already have data on the target and skips them — it will only copy the remaining small tables. No need to manually deselect anything.
copies remaining only
needed
Part 5 — Tips & Common Issues
- Always verify the summary text next to the Load button shows the correct host before clicking Run All
- Use concurrency 1 during your first test run — easier to debug a single log
- Each job creates the target database automatically if it does not exist
- The Stop button sets a stop signal — jobs finish the current table before halting
- Duplicate job warning: if two jobs map to the same source+target+server, the tool asks before proceeding
- Disk space: .bacpac is compressed but plan for 50–80% of raw table size on the staging drive
- Network: import over the internet is slow — run from a VM in the same region as the target
- SqlPackage times out at 600 seconds by default — for very large tables, run SqlPackage manually with
/p:CommandTimeout=0 - BACPAC imports drop all indexes then rebuild — target DB needs enough tempdb space
- Not supported for on-prem or RDS source — Azure SQL source only
- Forgetting to click Load after selecting a profile — the override is not active until Load is clicked (check for the host summary text)
- Profile was saved with wrong port — verify at Step 5 → Connection Profiles before running jobs
- Target DB name field left blank — the job will fail immediately with a "missing database name" warning
- Firewall not open on target server — test connectivity from Step 5 Test Connection before running jobs
- DR seeding: migrate same DB to primary RDS + DR RDS in one run
- Environment promotion: dev → staging → prod simultaneously
- Multi-region replication: us-east-1 + eu-west-1 in parallel
- Validation testing: migrate to a test server, validate, then migrate to prod
- Large table bypass: BACPAC 500 GB table while normal tables copy via row engine