Optimization is important when working with large amounts of data. Optimization is especially important when building an app in a multi-tenant platform where you have governor limits. Join us for lessons learned while developing a finance system on Force.com. We'll cover topics like executing aggregate queries with limits and offsets, handing updates to arbitrary numbers of records, and sorting complex objects.
Boost PC performance: How more available memory can improve productivity
Data Design Tips for Developing Robust Apps on Force.com
1. Data Design Tips for Developing
Robust Apps on Force.com
Lessons learned while developing a finance system on
Force.com
Steven Lawrance, Salesforce.com, Lead Member of the Technical Staff
2. Safe harbor
Safe harbor statement under the Private Securities Litigation Reform Act of 1995:
This presentation may contain forward-looking statements that involve risks, uncertainties, and assumptions. If any such uncertainties
materialize or if any of the assumptions proves incorrect, the results of salesforce.com, inc. could differ materially from the results
expressed or implied by the forward-looking statements we make. All statements other than statements of historical fact could be
deemed forward-looking, including any projections of product or service availability, subscriber growth, earnings, revenues, or other
financial items and any statements regarding strategies or plans of management for future operations, statements of belief, any
statements concerning new, planned, or upgraded services or technology developments and customer contracts or use of our services.
The risks and uncertainties referred to above include – but are not limited to – risks associated with developing and delivering new
functionality for our service, new products and services, our new business model, our past operating losses, possible fluctuations in our
operating results and rate of growth, interruptions or delays in our Web hosting, breach of our security measures, the outcome of any
litigation, risks associated with completed and any possible mergers and acquisitions, the immature market in which we operate, our
relatively limited operating history, our ability to expand, retain, and motivate our employees and manage our growth, new releases of our
service and successful customer deployment, our limited history reselling non-salesforce.com products, and utilization and selling to
larger enterprise customers. Further information on potential factors that could affect the financial results of salesforce.com, inc. is
included in our annual report on Form 10-K for the most recent fiscal year and in our quarterly report on Form 10-Q for the most recent
fiscal quarter. These documents and others containing important disclosures are available on the SEC Filings section of the Investor
Information section of our Web site.
Any unreleased services or features referenced in this or other presentations, press releases or public statements are not currently
available and may not be delivered on time or at all. Customers who purchase our services should make the purchase decisions
based upon features that are currently available. Salesforce.com, inc. assumes no obligation and does not intend to update these
forward-looking statements.
7. Designing within limits
▪ Scarce
• Record storage
▪ Plentiful
• Custom objects
• Tabs
• Apex code lines
▪ Selection of limits to optimize
• Record storage limits were selected for optimization in this project.
• Runtime computation was favored over temporary storage in a custom object.
9. Handing updates to arbitrary numbers of records
@future, Maps, and Visualforce are your friends.
▪ @future method: Asynchronously update batches of records.
• Update only up to the DML execution and record limits.
▪ Maps: Identify batches in the keys and changes in the values.
• @future private static void runLater(Map<String,String> batchUpdateValues)
▪ Visualforce: Use to re-invoke the next @future method call, if needed.
• @future methods cannot be called from a @future method.
10. Handing updates to arbitrary numbers of records
Visualforce page sends groups of batches to the @future method
▪ Page checks the donation update status during the page load.
• Updates required: Call the @future method and reload the page every 4 seconds.
• Updates in progress: Reload the page every 4 seconds.
• Complete: Display the regular user interface.
▪ The @future method updates donations until it gets to the DML limits.
• Set the donation update status at the end of @future method.
11. Handing updates to arbitrary numbers of records
Processing within @future
Input from Visualforce
Key
Value
For each key,
update each donation
to the target value
Credit Card
AmEx
2013-11-02
Finance Batch
a0nK0000000FAY
8
Credit Card
AmEx
2013-11-02
Fred Smith
AmEx
2013-11-02
$5.00
Finance Batch
a0nK0000000FAY8
Alex Jones
AmEx
2013-11-02
$10.00
Finance Batch
a0nK0000000FAY8
Check
2013-11-02
B123456
.
.
.
Finance Batch
a0nK0000000FAY
9
.
.
.
.
.
.
Catherine Katz
AmEx
2013-11-02
$10.00
.
.
.
13. Executing aggregate queries with limits and offsets
▪ Aggregations can return at most 500 rows per query.
▪ By using LIMIT and OFFSET, it’s possible to query more than 500
aggregated rows.
▪ In an aggregate query, the OFFSET can’t be 2000 or higher.
14. Executing aggregate queries with limits and offsets
for (Integer batchOffset = 0; batchOffset < 2000;) {
Integer aggregateBatchCount = 0, aggregateBatchSize = 500;
for (AggregateResult donationBatch : [SELECT … FROM … WHERE …
GROUP BY … ORDER BY … NULLS FIRST
LIMIT :aggregateBatchSize OFFSET :batchOffset]) {
aggregateBatchCount++;
…
}
if (aggregateBatchCount < aggregateBatchSize) { break; }
else { batchOffset += aggregateBatchCount; }
}
15. Executing aggregate queries with limits and offsets
Break apart disjunctions into separate queries if it’s faster.
▪ If your query takes too long to execute and makes use of ‘OR’, then try
splitting it into multiple queries that don’t use ‘OR’
SELECT … FROM Donation_Designation__c
WHERE Donation__r.CloseDate >= :startDate
AND Donation__r.CloseDate < :endDatePlusOne
AND (Donation__r.Fin_Batch__r.Rec__c = :recId
OR Donation__r.Batch__c = NULL
OR Donation__r.Batch__r.Status__c = 'Committed')
GROUP BY …
ORDER BY …
LIMIT … OFFSET …
16. Executing aggregate queries with limits and offsets
▪ The query plan in the underlying database is much more efficient when
all filters are conjunctive (joined with ‘AND’).
SELECT … FROM Donation_Designation__c
WHERE Donation__r.CloseDate >= :startDate
AND Donation__r.CloseDate < :endDatePlusOne
AND Donation__r.Fin_Batch__r.Rec__c = :recId
GROUP BY …
ORDER BY …
LIMIT … OFFSET …
17. Executing aggregate queries with limits and offsets
▪ Index range scans are faster than table scans.
▪ Use of ‘OR’ can lead to table scans.
▪ Salesforce.com can create an index for frequently filtered fields.
18. Sorting complex objects
▪ Before Winter ‘14, using a Map<String,ComplexObject> and a
List<String> to call List.sort() was a way to sort without running out of
script statements. The properties exist in the String key in the order to
sort by.
▪ In Winter ’14 and beyond, the recommended approach is to implement
the Comparable interface and call List.sort() on a List of your object.
• public class ComplexObject implements Comparable {
…
public Integer compareTo(Object compareTo) { /* implementation here */ }
}