Salesforce’s vast flexibility allows for the customization of data structures to fit almost any business need. However, one common issue that arises when building robust data models is handling too many child records related to a parent object. This problem can affect the performance of your Salesforce Org and limit user productivity. In this article, we’ll explore how to manage and resolve issues related to having too many child records in Salesforce, providing clear strategies and best practices to optimize your organization.

Too Many Child Records
Child Records in Salesforce
In Salesforce, relationships between objects are critical to defining how your data is connected. There are two primary types of relationships between objects: Lookup Relationships and Master-Detail Relationships. Both allow for the creation of parent-child associations between records.
Lookup Relationships
In a lookup relationship, a child record maintains a reference to its parent object, but the connection is relatively loose. Child records in this relationship can exist independently of their parent, and deletion of the parent record doesn’t automatically remove the child records.
Master-Detail Relationships
In contrast, master-detail relationships tightly link child records to their parent. Deleting a parent record in this relationship cascades the deletion to all associated child records, which means that managing these records can become complex when dealing with a high volume of child records.
Why Having Too Many Child Records Can Be Problematic
When you have too many child records in Salesforce, it can lead to several problems, including performance degradation, data management challenges, and user interface (UI) clutter. Here are a few specific concerns:
- Performance Degradation: Salesforce, like any relational database, has limits on how many records it can efficiently manage. Too many child records can slow down queries, make reporting sluggish, and degrade the performance of the UI.
- Sharing and Security Challenges: With large datasets, Salesforce’s sharing rules and security model can become difficult to maintain, resulting in slower sharing recalculations and increased complexity.
- Complex Data Management: Navigating through thousands or millions of child records can complicate data retrieval and reporting, especially when trying to visualize or segment information across various parent objects.
- Governance and Data Integrity: Without clear data governance, an excessive number of child records can lead to data duplication, incorrect data entry, and inconsistent data quality, hampering accurate decision-making.
Signs You Have Too Many Child Records
It’s essential to recognize when your organization may have a problem with excessive child records. Here are some key indicators:
- Reports and dashboards are taking longer than usual to run.
- Data skew is affecting reports and visibility.
- Users are encountering time-out errors or unusually slow page loads.
- Recalculating sharing rules takes more time than expected.
- Apex trigger limits are consistently hit when working with large sets of child records.
Strategies to Address Too Many Child Records in Salesforce
Use Roll-Up Summary Fields Efficiently
Roll-up summary fields allow you to perform calculations on related child records, such as counting the number of child records or summing up a value. However, the calculation can become sluggish if too many records are involved. Optimize your use of roll-up summary fields by:
- Filtering child records to reduce the dataset being summarized.
- Avoiding unnecessary recalculations by limiting roll-ups to only essential fields.
- Using asynchronous processing to offload roll-up operations from the user interface.
Archiving or Deleting Old Child Records
One of the most straightforward solutions is to archive or delete child records that are no longer needed. By reducing the volume of child records, you can free up resources and improve performance. Use tools such as Salesforce’s data export service or third-party data management apps to export and archive records before deletion.
Best Practices for Archiving:
- Create an archival policy that defines when records should be archived or deleted.
- Use custom objects to store archived data, making it easily retrievable if needed.
- Ensure that archiving complies with your organization’s data retention policies.
Avoid Data Skew
Data skew occurs when too many child records are associated with a single parent record. This can cause performance issues and data integrity problems, especially in Master-Detail relationships. To mitigate data skew:
- Distribute child records across multiple parent records where possible.
- Use record types and divisions to break up large datasets into smaller, more manageable units.
- Consider using a batch Apex process to evenly distribute the number of child records associated with each parent.
Use External Data Storage
For organizations with huge data volumes, Salesforce’s data storage limits can quickly become an issue. In such cases, consider using external storage solutions to house some of your child’s records, integrating them with Salesforce when needed.
Examples of External Storage Solutions:
- Heroku for handling large datasets and complex queries.
- Amazon S3 for simple and cost-effective storage.
- Salesforce Connect to access external data sources within the Salesforce UI.
Optimize SOQL Queries and Reports
When working with large datasets, SOQL (Salesforce Object Query Language) queries can become inefficient. Optimize your queries to avoid retrieving more child records than necessary:
- Use SELECTIVE queries by incorporating filters and WHERE clauses to limit the number of child records returned.
- Use indexed fields in your queries to speed up retrieval times.
- Split large queries into batch processes to handle data retrieval in manageable chunks.
Leverage Asynchronous Processing for Bulk Data Operations
If you frequently work with a large number of child records, using asynchronous processing methods such as Batch Apex, Queueable Apex, or Future Methods can help you avoid hitting governor limits. These methods allow you to process records in the background without affecting user performance.
Consider Data Sharding Techniques
For organizations that consistently run into performance issues due to a large number of child records, data sharding may be a viable option. This involves splitting your dataset into smaller, more manageable pieces, either by object type or geographic region.
- Use custom objects to split the data into different shards based on the parent object’s characteristics.
- Implement sharding at the SOQL query level, ensuring that queries only retrieve data from the appropriate shard.
Examples Of Too Many Child Records
Bulk Update Child Records Using Batch Apex
This example demonstrates how to update multiple child records in bulk using Batch Apex, allowing you to process records asynchronously.
global class BatchProcessChildRecords implements Database.Batchable<SObject> {
global Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator([SELECT Id, Name FROM ChildObject__c WHERE ParentId = :parentId]);
}
global void execute(Database.BatchableContext BC, List<ChildObject__c> scope) {
for(ChildObject__c childRecord : scope) {
// Perform operations on each child record
}
}
global void finish(Database.BatchableContext BC) {
// Optional: Post-processing after batch completes
}
}
Query Child Records with Selective Filters
Optimizing your SOQL queries by filtering data efficiently reduces query time and governor limit issues.
List<ChildObject__c> childRecords = [SELECT Id, Name FROM ChildObject__c WHERE ParentId = :parentId AND Status__c = 'Active' LIMIT 1000];
Delete Child Records in Bulk Using Batch Apex
If you need to delete a large number of child records, using Batch Apex is a good approach to prevent timeouts or hitting governor limits.
global class BulkDeleteChildRecords implements Database.Batchable<SObject> {
global Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator([SELECT Id FROM ChildObject__c WHERE ParentId = :parentId]);
}
global void execute(Database.BatchableContext BC, List<ChildObject__c> scope) {
delete scope;
}
global void finish(Database.BatchableContext BC) {
// Optionally send an email or log results
}
}
Asynchronous Processing Using Future Methods
Future methods allow you to process child records asynchronously in scenarios where batch jobs are unnecessary.
public class AsyncProcessChildRecords {
@future
public static void updateChildRecords(List<Id> childIds) {
List<ChildObject__c> children = [SELECT Id, Status__c FROM ChildObject__c WHERE Id IN :childIds];
for (ChildObject__c child : children) {
child.Status__c = 'Processed';
}
update children;
}
}
Parent Record Summary – Count Child Records Using Roll-Up Fields
Roll-up summary fields allow you to count related child records. Here’s how you create a roll-up field in Apex to avoid recalculating counts for every single child record change.
trigger UpdateChildCount on ChildObject__c (after insert, after delete) {
Map<Id, Integer> parentChildCount = new Map<Id, Integer>();
if (Trigger.isInsert) {
for (ChildObject__c child : Trigger.new) {
parentChildCount.put(child.ParentId, (parentChildCount.get(child.ParentId) != null ? parentChildCount.get(child.ParentId) : 0) + 1);
}
} else if (Trigger.isDelete) {
for (ChildObject__c child : Trigger.old) {
parentChildCount.put(child.ParentId, (parentChildCount.get(child.ParentId) != null ? parentChildCount.get(child.ParentId) : 0) - 1);
}
}
// Optionally update parent records with the new counts
}
Handle Large Data Volumes with Queueable Apex
Queueable Apex is a powerful tool for handling large volumes of data, allowing you to chain jobs for sequential processing.
public class QueueableProcessChildRecords implements Queueable {
public void execute(QueueableContext context) {
List<ChildObject__c> childRecords = [SELECT Id, Status__c FROM ChildObject__c WHERE Status__c = 'New'];
for (ChildObject__c child : childRecords) {
child.Status__c = 'Processed';
}
update childRecords;
}
}
Use Batchable Apex for Complex Parent-Child Relationship Operations
When operations are required across both parent and child objects, Batch Apex can handle complex processing tasks.
global class BatchParentChildOperations implements Database.Batchable<SObject> {
global Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator([SELECT Id, (SELECT Id FROM ChildObject__r) FROM ParentObject__c]);
}
global void execute(Database.BatchableContext BC, List<ParentObject__c> scope) {
for (ParentObject__c parent : scope) {
List<ChildObject__c> childRecords = parent.ChildObject__r;
for (ChildObject__c child : childRecords) {
child.Status__c = 'Updated';
}
update childRecords;
}
}
global void finish(Database.BatchableContext BC) {
// Finalize processing
}
}
Archive Child Records Using Schedulable Apex
Automatically archive child records that are older than a certain date using Scheduled Apex to reduce data volume.
global class ArchiveChildRecordsScheduler implements Schedulable {
global void execute(SchedulableContext sc) {
List<ChildObject__c> oldChildRecords = [SELECT Id FROM ChildObject__c WHERE CreatedDate < LAST_N_DAYS:365];
// Archive or delete logic here
delete oldChildRecords;
}
}
Using Platform Events to Manage Large Record Volumes
public class ChildRecordUpdater {
public void updateChildRecordsViaEvent(List<PlatformEvent__e> eventList) {
for (PlatformEvent__e eventInstance : eventList) {
List<ChildObject__c> children = [SELECT Id, Status__c FROM ChildObject__c WHERE ParentId = :eventInstance.ParentId__c];
for (ChildObject__c child : children) {
child.Status__c = 'Processed via Event';
}
update children;
}
}
}
Limit Child Records per Parent Using Validation Rules
AND(
ISPICKVAL(Status__c, 'Active'),
(SELECT COUNT() FROM ChildObject__r WHERE ParentId = ParentObject__c.Id) > 100
)