콘텐츠로 이동

대용량 적재

19개 언어로도 제공

English · Español · 日本語 · Português (Brasil) · Français · Deutsch · Italiano · 简体中文 · Português (Portugal) · 繁體中文 · Bahasa Indonesia · Türkçe · Polski · Tiếng Việt · ไทย · Українська · Español (México) · Français (Canada) · Deutsch (Schweiz)

In a greenhouse packing hall, three rows of tall plant trolleys, one all red flowers, one all yellow and one all white, wait by the open door.
AI로 생성한 이미지

Data Architect 과정 10장인 마이그레이션과 동기화의 모듈 2/5. 각 모듈은 독립적으로 읽을 수 있습니다. 이전: 마이그레이션은 무엇보다 먼저 품질 프로젝트입니다 · 다음: 적재 중의 자동화

이 모듈이 답하는 질문: 0의 개수는 Salesforce에 데이터를 적재하는 방식을 어떻게 바꿀까요?

레코드 열 개요? 거의 손으로 입력할 수 있습니다. 천 개요? 이제 도구가 필요합니다. 100만 개요? 그건 아키텍처 문제입니다. 작업은 바뀌지 않았지만(레코드 생성 또는 갱신), 0의 개수가 그 방법을 바꿉니다. 모델링, 쿼리, 공유, 통합에서 보았고, 마이그레이션도 다르지 않습니다. 대용량에서는 데이터를 어떻게 옮기는지, 일을 어떻게 나누는지, 어떤 순서로 움직이는지, 적재하는 동안 Salesforce가 그 밖에 무엇을 하는지, 무언가 실패하면 어떻게 복구하는지, 전체를 다시 돌릴 수 있는지를 신경 씁니다.

Same landscape, same amber arrow as module 1: RouteDesk’s open work coming into Salesforce the weekend RouteDesk retires. ⟲

The arrow that runs once Steve Trucking's landscape from Checkpoint 1, in the same places, the weekend RouteDesk retires: people on the left, Salesforce with Field Service in the middle with the Retail Partner portal and the tracking page, the integration layer from chapter 9 to its right, and the systems around it: Snowflake, FleetLink, the finance ERP, photo storage, and RouteDesk, retiring, with 8 years and about 5 million deliveries. One new arrow, in amber, the migration this module zooms in on, carries what's still open from RouteDesk into Salesforce, migrated once. Underneath: one arrow runs once and then disappears; a migration is an integration with an end. {"author": "Steve Simpson", "blueprint": "1.0.1", "builder": "standard 1.54.0", "built": "2026-10-30", "id": "DGM-DA-MG-10", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "system-landscape 0.13.0", "theme": "stevetecharc 1.4.0"} ABOVE: IDENTITY, APPEXCHANGE, WEB PEOPLE RIGHT: INTERNAL SYSTEMS BELOW: CUSTOMER CHANNELS BOTTOM RIGHT: PARTNERS AND PROVIDERS Dispatch coordinators 45, inside 9 partner DCs Delivery crews ~700 on 330 trucks Care and office staff 100: service, ops, finance Retail Partner users ~300 at 3 partners Recipients ~1 million a year Corporate IdP SSO for office staff only Retail Partner portal Tracking page INTEGRATION Snowflake status history, analytics FleetLink raw GPS stays here Finance ERP invoices partners, pays crews Photo storage 4–5 million a year, off core RouteDesk retiring: 8 years, ~5 million deliveries Phone care agents Email email-to-case SMS and email provider day-before message, with a link Redrock REST API, webhooks Keystone CSV over SFTP Brightwave EDI through a third-party network Salesforce Field Service, one US org a link, no login office staff only calls cases in manifests in status out manifests in status out manifests in status out upserts, partner key status events status events latest position only delivery counts delivery counts photos, from the crews' app day-before, with a link what's still open: migrated once Salesforce a community people identity, apps, web an internal system a customer channel the integration layer starts at the system that opens the connection; the label says what moves a shared certificate: trust, no direct call the migration we zoom in on Checkpoint 1's landscape, the weekend RouteDesk retires. One arrow runs once and then disappears: a migration is an integration with an end. © 2026 Twinshark LLC · stevetecharc.com
클릭하여 확대

Module 1 decided what moves on this arrow. This module is about how it moves. ⟲

Module 1 decided what comes in: the deliveries not yet made and the ones still inside the 60-day claim window, about 196,400 deliveries. Each one becomes a Work Order, with its line items (about 1.6 per delivery, so about 314,000) and its Service Appointment (about 196,400 more). That’s roughly 707,000 records, prepared in the preparation data store and ready to go. Not millions, but well past anything I’d send one record at a time. So, how do they get in? ⟲

Bulk API를 가르칠 때 저는 은행을 씁니다. 입금 전표를 한 뭉치 들고 들어가면, 창구 직원이 200장을 받고 줄 맨 뒤로 보냅니다. 레코드가 100만 개라면 줄을 5,000번 서는 것이고, API 호출 5,000번입니다. 그동안 트럭 한 대가 은행 옆에 서서 엄청난 양의 입금을 내리고, 여러분이 아직 줄에 서 있는 동안 20분 뒤에 떠납니다. 그것이 하역장이고, 이름은 Bulk API입니다.

The 200 is still real. A REST sObject Collections request takes “up to 200 records” (Create Records Using sObject Collections (새 탭에서 열림), REST API Developer Guide), and a SOAP create() call the same: “If a create request exceeds 200 records, then the entire operation fails” (create() (새 탭에서 열림), SOAP API Developer Guide; both checked 2026-10-27). Every 200 more records is another call. RouteDesk’s 707,000 records would be over 3,500 trips to the teller. ⟲

The teller window and the loading dock Two equal panels. The teller window, the synchronous APIs: each trip, up to 200 records a call; a million records, about 5,000 calls, back of the line each time; you get back an answer for every record, right away; it fits singles and sets, fewer than about 2,000 records. The loading dock, the Bulk API: each trip, whole files that Salesforce splits into batches; a million records, a handful of calls; you get back a job ID now and the results later; it fits bulk, more than about 2,000 records. Underneath: same records, different door; over about 2,000 records, Salesforce points you to the loading dock. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.46.0", "built": "2026-10-27", "id": "DGM-DA-MG-20", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "comparison 0.4.0", "theme": "stevetecharc 1.4.0"} The teller window the synchronous APIs EACH TRIP Up to 200 records a call A MILLION RECORDS About 5,000 calls, back of the line each time YOU GET BACK An answer for every record, right away FITS Singles and sets: fewer than about 2,000 records The loading dock the Bulk API EACH TRIP Whole files; Salesforce splits them into batches A MILLION RECORDS A handful of calls YOU GET BACK A job ID now, the results later FITS Bulk: more than about 2,000 records Same records, different door. Over about 2,000 records, Salesforce points you to the loading dock. © 2026 Twinshark LLC · stevetecharc.com
클릭하여 확대

Same records, different door. ⟲

하역장에는 다섯 단계가 있습니다.

  1. 작업 열기. 빨간 버튼을 누르고 번호표를 받습니다. 작업 ID입니다.
  2. 업로드. 하역장이 열리고, 그 번호표로 CSV 파일을 올립니다. 파일 하나든 백 개든.
  3. 작업 닫기. 큰 파란 버튼, 업로드 완료를 누르기 전까지 Salesforce는 여러분이 끝냈다는 것을 모릅니다. 이제 처리가 시작됩니다.
  4. “다 왔어요?” 뒷좌석의 제 아이들처럼, 작업 ID를 들고 끝났는지 묻습니다. 끝났어요? 끝났어요?
  5. 결과 받기. 성공한 레코드(새 Salesforce ID 포함)나 실패한 레코드(오류 포함)를 내려받습니다.
Five steps at the loading dock The loading dock in five steps, left to right. 1, open the job: get a ticket, the job ID. 2, upload: one file or a hundred. 3, close the job: upload complete, now it processes. 4, are we there yet?: ask with the job ID. 5, highlighted, get the results: successes with new IDs, failures with errors. Underneath: submit, wait, collect; nobody answers per record, so you keep track of the job. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.46.0", "built": "2026-10-27", "id": "DGM-DA-MG-21", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "cells 0.3.0", "theme": "stevetecharc 1.4.0"} THE LOADING DOCK 1. Open the job get a ticket: the job ID 2. Upload one file or a hundred 3. Close the job upload complete: now it processes 4. Are we there yet? ask with the job ID 5. Get the results successes with new IDs, failures with errors job ID files in processing done Submit, wait, collect. Nobody answers per record, so you keep track of the job. HOW TO READ IT the step migrations depend on: keep every result © 2026 Twinshark LLC · stevetecharc.com
클릭하여 확대

Submit, wait, collect. ⟲

레코드 100만 개가 수천 번이 아니라 몇 번의 호출로 끝납니다. 하지만 무엇이 바뀌었는지 보십시오. 작업이 비동기입니다. 레코드마다 답을 받지 않고, 제출하고, 기다리고, 받아 옵니다. 그러니 호출하는 쪽은 무엇을 제출했는지, 무엇이 아직 처리 중인지, 무엇이 완료되었는지, 무엇이 실패했는지, 무엇이 돌아왔는지 알아야 합니다. API 세부 사항이 아닙니다. 마이그레이션 통제입니다.

하역장 두 곳: Bulk API 2.0과 Bulk API

섹션 제목: “하역장 두 곳: Bulk API 2.0과 Bulk API ”

버전이 두 개 있고, 어느 쪽을 쓰는지가 중요합니다.

  • Bulk API 2.0 is the one to start with. You upload the data for a job and Salesforce splits it into batches for you, retries for you and runs them in parallel. A job can take 150 MB, measured after Salesforce encodes it, so Salesforce advises uploading no more than 100 MB of CSV per job. ⟲
  • Bulk API (1.0) makes you create the batches yourself, up to 10,000 records each. It’s older, but it still has two levers 2.0 doesn’t give you: serial mode for loads, and PK chunking for very large exports (module 5). ⟲

Both share a rolling 24-hour allowance of 15,000 batches, and either can load up to 150 million records in 24 hours. Job results are kept for seven days after the job completes (Bulk API and Bulk API 2.0 Limits and Allocations (새 탭에서 열림), checked 2026-10-27). Salesforce’s line between the doors: “Any data operation that includes more than 2,000 records is a good candidate for Bulk API 2.0”; below that, “bulkified” synchronous calls are fine (Loading Data from the API (새 탭에서 열림), Best Practices for Deployments with Large Data Volumes, Winter ‘27, checked 2026-10-27). Those are today’s numbers: check them before you plan around them. ⟲

단건, 묶음, 대량은 제가 일의 모양을 생각하는 방식입니다. 사용자 행동에서 나오는 단건 갱신 하나, 비즈니스 프로세스에서 나오는 관련 레코드 수백 개, 마이그레이션의 이력 레코드 수백만 개. 제품 이름이 아닙니다. 도구를 고르기 전에 일의 모양이 무엇인지 물으십시오.

Test knowledge. Know when the Bulk API is the answer: more than about 2,000 records, asynchronous, a job you submit and collect. Know the two versions apart: Bulk API 2.0 splits the batches for you and runs them in parallel; Bulk API (1.0) has you build batches of up to 10,000 records, and only it offers serial mode and PK chunking. Both share 15,000 batches in a rolling 24 hours. ⟲

도구가 아키텍처를 없애 주지는 않습니다

섹션 제목: “도구가 아키텍처를 없애 주지는 않습니다 ”

“Bulk API를 쓸 거예요.” 좋습니다. 하지만 그것으로는 어떤 객체를 먼저 적재할지, 삽입인지 upsert인지, 외부 키는 무엇인지, 관계를 어떻게 다시 세울지, 일부 레코드가 실패하면 어떻게 할지, 작업을 병렬로 돌려도 되는지, 병렬 작업이 같은 부모를 두고 싸울지, 어떤 자동화가 실행될지, 어떻게 대사하고 다시 시작할지 알 수 없습니다. Bulk API는 운송 문제를 풉니다. 마이그레이션을 풀지는 않습니다.

The order comes from the model, and module 1 drew it for RouteDesk: reference data first, then the Work Orders, then their line items and appointments, then the claim Cases that point at them (Preserve the Source Identity, and Load by Keys). Each wave is its own job, and the next one doesn’t start until the last one’s results are in. Junction objects depend on two parents, self-referencing hierarchies may need several passes, and circular relationships may need records first and relationships second. Don’t discover these on cutover weekend. ⟲

때로는 레코드를 먼저, 관계를 나중에 적재합니다. 모회사, 자회사, 지역 자회사가 모두 마이그레이션에서 만들어집니다. 모든 관계를 한 번에 풀려고 하지 말고, 안정적인 원본 키와 함께 계정을 적재한 다음, 계층을 설정하는 두 번째 실행을 돌리십시오. 같은 생각이 자기 참조와 순환 의존에도 도움이 됩니다. 목표는 한 번에 끝내는 것이 아닙니다. 결정론적이고 반복 가능한 결과입니다.

Insert says create this as new. Update says I already know the Salesforce record I’m changing. Upsert says use this key to find out whether the record exists; update it if it does, create it if it doesn’t. With stable External IDs, upsert makes rehearsals far easier: load, find a transformation problem, fix it, rerun, without the run assuming everything is a first-time insert. Keys are architecture, not just integration plumbing. RouteDesk’s Work Orders upsert on the partner key, the same field the integration uses, so a manifest Redrock resends on cutover weekend updates the migrated record instead of duplicating it. ⟲

Upsert has a price, though. Salesforce’s own Large Data Volumes guide ranks the operations: “insert() is fastest, update() is next, and upsert() is next after that. If possible, also break upsert() into two operations: create() and update()” (Loading Data from the API (새 탭에서 열림), checked 2026-10-27). How do you know which records are new and which already exist? The preparation data store knows, because it pulled down what Salesforce already has and keeps the IDs that came back (module 1). So the 190,000 delivered Work Orders that nothing in Salesforce has touched can go in as a plain insert, and only the open ones the integration may already have created need the upsert. ⟲

Two more lines from the same guide. Load clean data: “Errors in batches trigger single-row processing for that batch, and that processing heavily impacts performance.” That’s module 1 paying off at the loading dock. And when you update, “send only fields that have changed.” A reload that resends every field of 196,400 Work Orders does work nobody asked for. ⟲

하지만 upsert는 마법이 아닙니다. 키 전략이 틀리면 작업도 틀립니다. 같은 고객에 대한 원본 레코드 둘이 키가 다르면 Salesforce에게는 레코드 둘이고, 원본 키 하나가 여러 대상을 가리킬 수 있다면 우리가 카디널리티를 잘못 이해한 것입니다. 모듈 1의 품질 작업은 여전히 중요하고, API 기능이 그것을 대신하지 않습니다.

Test knowledge. Insert is the fastest operation, then update, then upsert; Salesforce suggests splitting an upsert into creates and updates where you can. Upsert needs an External ID, and it’s what makes reruns safe. Load clean data, because errors in a Bulk API batch push that batch into slow single-row processing, and send only the fields that changed. ⟲

병렬은 빠릅니다, 빠르지 않을 때까지는

섹션 제목: “병렬은 빠릅니다, 빠르지 않을 때까지는 ”

병렬 처리는 처리량을 높입니다. 하지만 적재의 많은 레코드가 같은 부모를 가리키면, 트랜잭션이 그 부모의 잠금을 두고 경쟁하고, 빨라지려고 한 일이 잠금 경합, 오류, 재시도를 만듭니다. 다시 7장의 핫스팟입니다. 자식 100만 개가 소수의 부모로 모인다면, 데이터의 모양이 적재에 중요합니다. 두 배치가 서로 상대가 쥔 잠금을 기다리면 교착 상태가 생깁니다. 교차로에 갇힌 두 차입니다.

RouteDesk is exactly that shape. Almost 196,400 Work Orders, and nearly every one points at one of three Retail Partner Accounts. Load them in parallel in the order RouteDesk exported them, and every batch holds Work Orders for all three partners. ⟲

Sort the load by parent Three batches of Work Orders loading in parallel on the left; on the right, their parents, the Retail Partner Accounts Keystone, Redrock and Brightwave. First, each batch holds Work Orders from all three partners, so all three need Redrock at the same moment: batch 2 locks it, and batches 1 and 3 wait, their arrows red with an X. Then the same work, sorted by partner, tagged "sorted": batch 1 holds only Keystone's Work Orders, batch 2 only Redrock's, batch 3 only Brightwave's, and each goes to its own parent. Underneath: batches that share parents wait on each other's locks; sort the file by parent, and each batch works on its own. BATCHES, IN PARALLEL PARENTS: THE RETAIL PARTNER ACCOUNTS Batch 1 Work Orders from all three partners Batch 2 Work Orders from all three partners Batch 3 Work Orders from all three partners Redrock Retail Partner Account Keystone Retail Partner Account Brightwave Retail Partner Account locks waits waits Batches that share parents wait on each other's locks. Sort the file by parent, and each batch works on its own. © 2026 Twinshark LLC · stevetecharc.com
클릭하여 확대

In export order, every batch needs every parent. One locks, the others wait. ⟲

Two levers. First, sort the files by parent, so the children of one parent travel together in the same batch instead of being spread across batches that collide. Salesforce gives the same advice: “When changing child records, group them by parent—group records by the field ParentId in the same batch to minimize locking conflicts” (Loading Data from the API (새 탭에서 열림), checked 2026-10-27). ⟲

Sort the load by parent Three batches of Work Orders loading in parallel on the left; on the right, their parents, the Retail Partner Accounts Keystone, Redrock and Brightwave. First, each batch holds Work Orders from all three partners, so all three need Redrock at the same moment: batch 2 locks it, and batches 1 and 3 wait, their arrows red with an X. Then the same work, sorted by partner, tagged "sorted": batch 1 holds only Keystone's Work Orders, batch 2 only Redrock's, batch 3 only Brightwave's, and each goes to its own parent. Underneath: batches that share parents wait on each other's locks; sort the file by parent, and each batch works on its own. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.46.0", "built": "2026-10-27", "id": "DGM-DA-MG-22", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "cells 0.3.0", "theme": "stevetecharc 1.4.0"} BATCHES, IN PARALLEL PARENTS: THE RETAIL PARTNER ACCOUNTS Batch 1 Keystone's Work Orders only sorted Batch 2 Redrock's Work Orders only sorted Batch 3 Brightwave's Work Orders only sorted Redrock Retail Partner Account Keystone Retail Partner Account Brightwave Retail Partner Account its own parent its own parent its own parent Batches that share parents wait on each other's locks. Sort the file by parent, and each batch works on its own. © 2026 Twinshark LLC · stevetecharc.com
클릭하여 확대

Sorted by partner, each batch works on its own parent. ⟲

Second, on Bulk API 1.0, serial mode processes one batch at a time: slower, but it removes the contention. I reach for serial mode when the data is complex and we’re running into collisions and timeouts. Salesforce’s own guide is more cautious: serial mode “is used only when previously submitted asynchronous jobs have accidentally produced contention because of locks. Use only when advised by Salesforce” (Limits (새 탭에서 열림), Bulk API 2.0 and Bulk API Developer Guide, checked 2026-10-27). So sort first, and treat serial as the fallback. And it’s only on the older dock: Bulk API 2.0’s job reference says “Currently only parallel mode is supported,” and if other modes ever arrive, “the mode will be chosen automatically by the API and will not be user configurable” (Create a Job (새 탭에서 열림), Bulk API 2.0, checked 2026-10-27). A load that needs serial processing is a reason to use Bulk API 1.0. ⟲

Serial mode is a setting on the job, chosen when you create it. On Bulk API 1.0, RouteDesk’s Work Orders would be: ⟲

POST /services/async/68.0/job
{
"operation": "upsert",
"object": "WorkOrder",
"externalIdFieldName": "Partner_Delivery_Key__c",
"contentType": "CSV",
"concurrencyMode": "Serial"
}

Leave concurrencyMode out and you get Parallel, the default. Salesforce’s job reference says when to change it: “If parallel mode results in too many failed records or failed jobs, submit the job with Serial concurrency mode,” and adds that it “can significantly increase the processing time” (JobInfo (새 탭에서 열림), Bulk API 2.0 and Bulk API Developer Guide, checked 2026-10-31). ⟲

And if three parents are too few for any sorting to help, look at the model. Checkpoint 1 already offered a way to spread them: link Work Orders to a child Account per distribution center, so nine parents share the load instead of three. That’s a design decision, made before the migration, not a loader setting. ⟲

저는 “항상 병렬”이나 “항상 직렬”로 시작하지 않습니다. 관계의 모양, 뜨거운 부모, 실행되는 자동화, 그 밖에 잠그는 것, 리허설에서 일어난 일을 봅니다. 그다음 고릅니다. 먼저 정확하게, 그다음 빠르게.

Test knowledge. Parallel loads lock parent records. When many children share a few parents, expect “unable to lock row” errors: sort the load file by parent so each batch touches as few parents as possible. Serial mode (Bulk API 1.0 only) removes the contention at the cost of speed; Salesforce reserves it for lock contention the sorting didn’t solve. ⟲

사람들은 마법의 숫자를 원합니다. 저에게는 없습니다. 맞는 크기는 객체, 레코드 복잡도, 자동화, 관계, 잠금, 페이로드, 메커니즘에 달려 있습니다. 너무 크면 실패가 비싸지고 트랜잭션이 일을 너무 많이 하고, 너무 작으면 오버헤드가 지배해서 실행이 필요 이상으로 오래 걸립니다(1.0에서는 배치 할당량도 태웁니다). 도구가 지원하는 한도 안에서 시작하고, 대표성 있는 데이터로 측정하십시오.

And the rehearsal data needs the same shape as production. Twenty million records in production, ten thousand in the test, and everything flies: what did we prove? Not much about scale. The test doesn’t need every record, but it must expose the architecture: heavy concentration on a few parents, millions of children, large text fields, complex relationships, historical skew, automation triggered by particular values. The Number of Zeros matters, and so does the shape behind the zeros. For RouteDesk that means a rehearsal set where a third of the Work Orders still point at Redrock. ⟲

적재 사용자, 그리고 레코드의 소유자

섹션 제목: “적재 사용자, 그리고 레코드의 소유자 ”

For a substantial migration I want a dedicated migration identity, not someone’s personal admin account: for traceability, controlled permissions, automation decisions (module 3), monitoring and troubleshooting. A million records created by the migration user during cutover tells me something. A million created by Daniel Reyes because Daniel ran Data Loader on Saturday night mixes human identity with system activity. Give it the permissions the job needs, and don’t let it become a permanent mystery superuser nobody dares disable. ⟲

Then don’t confuse who loaded a record with who should own it. Created By is provenance; Owner is architecture, with sharing and business consequences. Set business ownership deliberately. Sometimes there’s no good answer in the source: the record’s owner left years ago, or never existed. It can be hard to assign records to active users, and I’ve created a dedicated “data load” user to own them. That’s sound practice with one condition from chapter 7: a user who owns more than 10,000 records of an object is ownership skew, so that owner should have no role, or a role of its own at the top of the hierarchy (Ownership Data Skew (새 탭에서 열림)), and the records should have a path to real owners. Who owns the records, and where that owner sits in the role hierarchy, is an important decision the team makes for any bulk import, not something the loader defaults. For RouteDesk’s Work Orders, chapter 7’s question decides it: does ownership drive who sees a delivery? At Steve Trucking it doesn’t (partners see their own deliveries through the partner on the record), so one owner with no role is fine. ⟲

도구: 반복 가능성으로 고르십시오

섹션 제목: “도구: 반복 가능성으로 고르십시오 ”

There are many ways to load data, and the tool matters less than how you use it. Salesforce’s Data Loader has been tried and true, but it’s aged: a desktop tool that works CSV by CSV. Its command-line version can be scripted and pointed at your preparation data store, which is how I’ve run repeated loads. Some tools connect a SQL database straight to Salesforce, copying data down and sending changes back with SQL. ETL and integration platforms do the same job at enterprise scale. And with the APIs you can drive the Bulk API directly: the Salesforce CLI’s sf data upsert bulk loads a CSV through Bulk API 2.0 with an External ID from one command (data upsert bulk (새 탭에서 열림), Salesforce CLI Command Reference, checked 2026-10-27). You can even have AI write the small scripts that run each complex step. ⟲

도구가 무엇이든 제 가장 강한 선호는 이것입니다. 운영 마이그레이션이 우리가 마이그레이션을 처음 해 보는 때여서는 안 되고, 리허설마다 손으로 만드는 모험이어서도 안 됩니다. 17단계가 “이 CSV를 열고, G열로 정렬하고, 빈 행을 지우고, 이 수식을 아래로 복사한 다음, 이 설정으로 Data Loader를 돌려라”라면 저는 불안해집니다. 수작업 단계마다 잘못된 파일, 빠진 행, 낡은 버전, 잘못된 정렬, 잊은 필터, 리허설과 다른 설정이 끼어들 기회가 생깁니다. 일부는 피할 수 없을지도 모릅니다. 문서화하고 테스트하십시오. 하지만 목표는 반복 가능성이니, 추출, 변환, 검증, 적재, 결과 수집, 대사가 매번 같은 방식으로 돌아가야 합니다.

적재를 예약한다면, 끝낼 수 있는 것보다 자주 예약하지 마십시오. 가끔 90분이 걸리는 매시간 작업은 자기 위에 겹쳐서 시작합니다. 다음 실행이 시작되기 전에 이전 실행이 끝났는지 확인하십시오.

Keep Every Result, and Bring the Keys Back

섹션 제목: “Keep Every Result, and Bring the Keys Back”

적재마다 입력 버전, 변환된 적재 파일, 작업 ID, 오류를 포함한 성공과 실패 결과, 소요 시간, 레코드 건수, 대사 결과를 보관하십시오. 큰 마이그레이션이 끝나면 누군가 “이 레코드는 어떻게 된 거죠?”라고 물을 것이고, 저는 “토요일 아침 세 번째 CSV에 있었던 것 같아요”가 아니라 증거를 원합니다. Bulk 결과는 7일 동안만 보관되니, 그때그때 내려받으십시오. 이 산출물에는 나중에 수명 주기를 주되, 이전을 증명해야 하는 동안에는 버리지 마십시오.

Then bring the keys back. The success results carry each record’s new Salesforce ID next to the key you sent. Write them back into the preparation data store, on the row that produced the record: that’s the last frame of module 1’s three sets of keys. On the Zendesk migration I described in module 1, that’s what made everything else possible. With the Salesforce IDs back on every row, I could load in phases (accounts and contacts first, then the cases, then the comments and files that hang off them), and each phase found its parents by the IDs the last one returned. I could do a partial load: just the rows that failed, or just one group. And I could reload: fix a transformation, regenerate the affected rows, and send only those, knowing exactly which Salesforce records they’d update. Without the keys back, every rerun is a guess. ⟲

실패의 크기도 0의 개수 문제입니다. 100건 중 20건 실패는 나쁩니다. 2,000만 건 중 20건도 조사해야 하지만 다른 신호입니다. 건수만이 아니라 비율을 보고, 실패를 분류하십시오. 유효성 검사, 부모 누락, 중복 키, 잠금, 데이터 유형, 자동화, 권한. 분류하면 거대한 오류 파일 대신 아키텍처 문제가 보입니다.

그리고 성능은 초당 레코드 수 이상입니다. “처리량을 두 배로 올렸어요.” 무엇이 바뀌었나요? 잠금, 실패, 재시도가 늘었나요? 하위 자동화가 뒤처지고, 공유 재계산이 밀리고, 다른 통합이 넘쳤나요? 전환 전체가 실제로 짧아졌나요? 마이그레이션은 진공 속에서 돌지 않습니다.

Test knowledge. The Bulk API’s success results return each record’s new Salesforce ID with the data you sent, and its failure results return each error; both are kept for seven days. Capture them, write the IDs back to the source or staging data, and you can load in phases, retry only the failures, and reconcile what landed. ⟲

계정, 그다음 연락처, 그다음 주문을 각각 따로 테스트했다고 마이그레이션을 테스트한 것은 아닙니다. 저는 같은 순서, 같은 변환, 같은 정체성, 같은 자동화 전략, 같은 대사로, 대표성 있는 볼륨에서, 처음부터 끝까지 하는 전체 리허설을 원합니다. 단계 사이에서 의존성이 드러납니다. 10분 지연 때문에 다음 적재가 엉뚱한 때 시작되고, 예상 못한 실패율 때문에 몇 시간의 수정 작업이 들고, 공유 재계산이 일정을 밀어냅니다. 전체 실행은 각 명령이 아니라 마이그레이션이 작동하는지를 알려 줍니다.

If the rehearsal includes a performance or scale test, put Salesforce’s lead time in the plan. “All tests must be pre-approved. Unapproved testing is subject to throttling or blocking,” and for tests at 120 requests a second or below, the request goes in “at least one week before the testing date” (Salesforce Performance and Scale Test FAQs (새 탭에서 열림), Salesforce Help, checked 2026-10-27). A rehearsal that gets throttled proves nothing about cutover weekend. ⟲

AI가 스크립트를 쓸 수 있습니다. 실행은 여전히 여러분의 책임입니다

섹션 제목: “AI가 스크립트를 쓸 수 있습니다. 실행은 여전히 여러분의 책임입니다 ”

AI is very good at generating transformation scripts, load orchestration, error parsers, reconciliation queries and retry logic. Use it. But generated code can be wrong at enormous speed: a wrong transformation on ten records is ten fixes; on ten million, the Number of Zeros applies to the mistake too. The way I use it is step by step: clean, transform and migrate steps, with every generated command and action tracked. That’s deterministic, auditable automation, not unattended judgment. Test it at realistic scale before you trust it with production data. ⟲

시험 너머: AI가 바꾸는 것. AI는 마이그레이션 도구를 만드는 수고를 줄이고, 대량 작업을 빨리 만들고 돌리기 쉽게 합니다. 작업이 클수록 결정론적 변환, 추적되는 단계, 리허설, 통제, 대사의 필요는 더 커집니다.

아키텍트가 여기서 결정하는 것

섹션 제목: “아키텍트가 여기서 결정하는 것 ”
  • 마이그레이션을 트랜잭션 트래픽처럼 다루는 대신, 어떤 적재 메커니즘과 API 버전이 일의 볼륨과 모양에 맞는지.
  • 레코드와 관계를 따로 지나가야 하는 경우를 포함해, 의존성이 정하는 객체와 관계의 순서.
  • Which operation each set of records gets (insert, update or upsert), based on what the preparation data store knows already exists. ⟲
  • Where parallel processing is safe, and where concentration, locking or automation justify sorting by parent, a model change, or serial processing. ⟲
  • 어떤 안정적인 키, 마이그레이션 정체성, 레코드 소유권, 반복 가능한 도구가 리허설과 운영 적재를 결정론적으로 만드는지.
  • How results and new IDs come back, so loads can run in phases, retry only failures, and reload safely. ⟲
  • 운영 규모에서 프로세스를 믿기 전에 어떤 증거, 실패 분류, 전체 실행 리허설이 필요한지.

하역장에도 여전히 계획이 필요합니다

섹션 제목: “하역장에도 여전히 계획이 필요합니다 ”

The loading dock moves enormous amounts of data. It doesn’t decide which truck unloads first, where the pallets go, which shipment belongs to which customer, what happens to a damaged pallet, whether two forklifts are using the same aisle, or whether everything that should have arrived did. That’s architecture. Know the Number of Zeros and the shape behind them. Load in dependency order, use stable keys, control concurrency, bring the keys back, make the process repeatable, keep the evidence, and rehearse the whole thing. Then use the loading dock as fast as the architecture safely allows. ⟲