Introduction
On paper, moving data between two systems looks simple. In practice, it rarely is. Halfway through, files go missing, mappings stop lining up, long jobs time out, and duplicate records slip in. When the work is finally done, someone still has to prove that more than a million records arrived where they belong.
Our team at MagnusMinds recently faced this exact challenge. The goal was to migrate a large collection of document attachments from an older source environment into a modern business platform. With over one million files, complex connections between records, and no room for errors, a simple file transfer was out of the question.
Here is how we broke down the problem, built a reliable setup with Azure Data Factory and Python, and made sure the migration could recover smoothly from any issue.
What Is Large-Scale Attachment Migration?
Large-scale attachment migration is the controlled transfer of files along with their details and business connections. The goal is to make sure every file lands in the target system linked to the right business record, so it can be easily found and opened.
Three key things separate it from a standard file copy:
- Context travels with the file: Every file belongs to a business record, such as a material, product, or vendor. That connection must stay in place.
- Details matter as much as the file itself: File names, types, IDs, and dates decide whether the target platform can actually read and use the document.
- Accuracy must be easy to prove: When handling millions of items, you need clear proof of what succeeded, what failed, and that nothing was lost or duplicated.
The Challenge
Several technical challenges came together to make this migration difficult:
- Huge volume: Moving over one million attachment records ruled out manual or half-manual methods from day one.
- Complex mapping: Documents had to be matched to specific material numbers using CSV files that listed document IDs, file names, file extensions, and material numbers.
- One material, multiple documents: A single material could have several files attached to it. The system had to support valid multi-file connections without creating unwanted duplicate records.
- Files on an SFTP server: The actual documents lived on an external SFTP server. Each file had to be found and retrieved as the pipeline ran.
- Pipeline time limits: Cloud systems have strict run-time limits. Pushing too much data at once causes jobs to time out or stop unexpectedly.
- Recovering failed batches: If an error occurred halfway through, we needed a way to re-run only the failed records without starting the whole migration over.
- Platform synchronization: After updating the database, a sync API had to be called so the new files appeared right away in the platform user interface.
How Azure Data Factory and Python Solve It
We split the work between two core tools with clear roles: Azure Data Factory (ADF) acts as the manager, deciding what runs, in what order, and what happens if something fails. A Python-based Azure Function acts as the worker, reading the mapping files, finding the documents, and preparing database updates.

The pipeline runs through eight clear steps:
- Find mapping files: A GetMetadata activity checks a folder in Azure Blob Storage and makes a list of all CSV mapping files waiting to be processed.
- Process files one at a time: A ForEach activity picks up each mapping file in order. Processing one file at a time keeps things stable and makes errors easy to pinpoint.
- Run the Azure Function: For each file, ADF calls the Python Azure Function with a batch size of 100 records. The Function searches the SFTP server, matches files against the CSV rows, and returns three sets of database instructions: one for attachment records, one for the sync API, and one for file names.
- Update database records: ADF runs the attachment database instructions returned by the Function directly against the target database.
- Sync only when needed: An IfCondition checks whether there are platform updates to apply. If yes, it calls the platform sync API. If not, the step is skipped cleanly.
- Update file names: A final database step updates document names and paths in the platform tables.
- Keep going after a failure: Every step has a fallback path. If an individual file hits an issue, the pipeline logs the error and moves straight to the next file instead of stopping the entire run.
- Retry temporary errors automatically: If the Azure Function call fails due to a temporary network issue, it retries up to two times automatically, waiting 60 seconds between attempts.
Key Features
- Automatic file discovery: picks up incoming mapping files on its own without needing a manual list.
- Step-by-step batch execution: handles one CSV at a time with 100 records per Function call to keep runs smooth and safe.
- Centralized business logic: the Azure Function builds ready-to-run database scripts, keeping all rules in code rather than cluttered pipeline settings.
- Three-stage database updates: handles attachment records, sync payloads, and file names in separate, safe steps.
- Smart API calls: triggers the sync API only when there are real changes to send, avoiding wasted calls.
- Non-stop error handling: built-in fallbacks allow the pipeline to keep processing remaining files even if one file fails.
Advantages
- No manual tracking: Mapping files are found and processed automatically from start to finish.
- Predictable run times: Batches of 100 records keep memory usage low and prevent jobs from timing out.
- Runs without constant supervision: Built-in fallbacks mean the pipeline can process thousands of files without needing someone to watch it the whole time.
- Targeted re-runs: Failed files can be reprocessed on their own without repeating work that already finished successfully.
- Clear visibility: ADF provides a clear record of every single file and step, making it easy to see what succeeded and what failed.
Limitations to Keep in Mind
- Mapping file quality is very important: The pipeline depends completely on accurate CSV mapping files. Any missing or wrong information in those files will cause errors.
- External SFTP dependency: If the SFTP server slows down or drops its connection, file lookups will fail and those batches will be skipped.
- Function updates require a quick code release: Because the business rules live inside the Python Azure Function, adding new file types or conditions requires updating the code.
- One-by-one processing trades speed for safety: Processing files one at a time is very stable and easy to fix, but it takes longer than running everything at once.
Business Impact
Migrating more than one million document attachments requires both accuracy and dependability. A single failure should never put days of work at risk.
This solution gave the team a reliable, automated way to move records. It transferred data in controlled batches, caught and isolated errors without losing completed work, and kept the business platform updated in real time. The migration finished without manual file hunting or guessing which records had made it across.
Conclusion
Large-scale data migrations are rarely just about copying files. True reliability comes from designing workflows that respect system limits, protect connections between business records, and recover smoothly when something goes wrong.
By combining Azure Data Factory for pipeline management with Python Azure Functions for flexible processing, teams can turn massive, stressful migrations into predictable, everyday operations.