Nine Years of Data, Fourteen Million Tasks
A nine-year-old org holds 2.1M Accounts, 5.8M Contacts and 14M Tasks; data storage is over its limit. A migration team is loading 900K net-new Accounts with a Bulk API 2.0 job and repeatedly hits UNABLE_TO_LOCK_ROW on the parent Territory__c records, which carry a roll-up summary from Account. Account also has three Apex triggers and a record-triggered flow, and Contact has its own record-triggered flow. Separately, a compliance requirement says 6M Task records older than five years must leave the org but stay queryable for three years, and leadership wants a monthly full backup after an incident last year where a bad load overwrote 40,000 phone values.
1. Explain why the Bulk API 2.0 job is failing on Territory__c and lay out the options, including their trade-offs.
Read the worked answer
Every Account save recalculates the roll-up summary on its parent Territory__c, which means each Account write takes a row lock on that parent. Bulk API 2.0 runs batches in parallel by default, so several batches touching Accounts that roll up to the same Territory contend for the same parent row and time out.
The options, best first:
- Remove the contention instead of retrying. Sort or partition the input so parallel batches touch disjoint Territories, or run the load in territory-sized chunks during a quiet window. This keeps Bulk API 2.0 and its throughput.
- Serialize the load. Bulk API 2.0 has no serial mode; Bulk API 1.0 does. If you must have guaranteed single-threaded processing, use Bulk API 1.0 serial mode or a single-threaded client, accepting a slower load.
- Reduce batch size (smaller batches mean shorter lock holds and fewer collisions) and add exponential-backoff retry on the failed records only, rather than restarting the whole job.
- Temporarily decouple the roll-up - load into a staging field, or have the load skip trigger/flow work via a bypass flag, then recalculate the roll-up in one pass. This is a metadata change in production and needs a change record and a rollback plan; do not do it casually.
Also confirm the three Account triggers and the record-triggered flow are not themselves doing parent DML, which compounds the lock. And set the load to defer sharing recalculation, or the sharing engine will add its own recomputation cost on top.
2. How would you get 6M old Tasks out of the org while keeping them queryable for three years?
Read the worked answer
Two requirements are being conflated: reclaiming storage and retaining evidence. Design for both explicitly.
- Extract first, delete second. Pull the six million records out through the Bulk API with every field you would need to answer a future audit question (WhoId, WhatId, OwnerId, ActivityDate, Subject, Description, Created/LastModified). Verify row counts and checksums against a SOQL COUNT() before any deletion. Deleting before you have a verified extract is unrecoverable.
- Decide the retention home. Either an external store owned by compliance, or a Big Object in Salesforce if they need to query it inside the platform. Big Objects are designed for exactly this, but they must be modelled and indexed before the load, and they are append-only - plan the index keys around the questions people will actually ask.
- Delete in bulk, then empty the recycle bin. A Bulk API hard delete bypasses the recycle bin; a standard delete does not, and storage is only reclaimed once the recycle bin is emptied. Either way, script it in batches well under the batch size limits and run it off-peak.
- Check the collateral damage. Tasks linked to open opportunities or cases may drive reports, list views and activity components. Confirm with the business which records are safe, and test the deletion against a full sandbox copy first.
Document the extract location, the deletion date, and the restore procedure - compliance will ask for that, not for the job ID.
3. Leadership wants a monthly full backup. What would you actually recommend, and what would you tell them a backup cannot do?
Read the worked answer
Start with what the incident actually was: field-level overwrites, not record loss. A backup restores records; it does not tell you what a field contained before the bad load unless you also have history. So the recommendation is two layers:
- Data backup: Salesforce's built-in scheduled export is weekly, not monthly, and its files are not a long-term archive - they expire. For a genuine monthly full copy with point-in-time restore, use a third-party backup/replication tool or drive the SOAP/Partner API export yourself on a schedule and store it outside the org. Test a restore, not just a backup; an untested backup is a hope.
- Field history: enable tracking on the fields that matter (Amount, Phone, Owner, and anything a bulk load can clobber). Standard field history is capped per object and has limited retention; Field Audit Trail extends retention out to years if the regulatory window demands it.
Tell leadership plainly: a backup restores to a point in time and loses everything after it; it will not answer 'who changed this phone number and when' for a single record. That question needs field history, enabled before the incident. Also confirm the storage math - a monthly full export of a 14M-Task org is large, and the export itself may hit API limits, so schedule it in the off-hours window.