4

We're loading thousands of records into a custom object (Address__c) over the Bulk API, in parallel mode. It has a Master-Detail relationship with Contact

There's a checkbox field on the object (Primary__c) and some trigger logic on insert which marks the field as true for the first record that is created for a particular contact. The data sent to the API does not set any value and the field level default value is false. The trigger logic is bulkified and is verified by several pairs of eyes to be correct. Theoretically, I'd expect each contact to have exactly one Primary address or no addresses at all. This is true for SOAP API data loads or records created via the UI.

When a day's load completes, I find that sometimes multiple (I've seen only 2 in the records I checked) addresses are set as Primary.

The only explanation and my question is in the title - when 2 batches are running in parallel, do they not see records created by each other through SOQL? I understand, each batch would run its own transaction so the trigger.new is different but I'd expect one of them to commit changes to the DB for the other batches to see.

Edit: Below static method is called from the trigger with trigger.new as parameter.

public static void setPrimaryAddress(List<Address__c> addressList)
{
    Map<Address__c,String> addressContactMap = new Map<Address__c,String>();
    Map<String,Address__c> contactAddressMap = new Map<String,Address__c>();
    Map<String,Address__c> contactPrimaryAddressMap = new Map<String,Address__c>();


    for(Address__c eachAddress : addressList)
    {
        addressContactMap.put(eachAddress,eachAddress.Contact__c);
        //Holds the Address & the Associated Contact
    }

    List<Address__c> addrList = [SELECT Id, Address_Type__c, Primary__c, Contact__c FROM Address__c WHERE Contact__c IN: addressContactMap.values() 
                                 AND Primary__c = true]; 
    //List of Address Having Primary as true

    for(Address__c eachAddr : addrList) //Loop through the List of Address & put the contact, Address information in a Map
    {
        contactAddressMap.put(eachAddr.Contact__c,eachAddr);
    }


    for(Address__c anAddr : addressContactMap.keySet()) //Loop through the list of Address that needs to be made as primary
    {
            if(!contactAddressMap.containsKey(anAddr.Contact__c) && !contactPrimaryAddressMap.containsKey(anAddr.Contact__c)) //If the Address is not listed in the PrimaryAddressMap
            {
               anAddr.Primary__c = true;
               contactPrimaryAddressMap.put(anAddr.Contact__c,anAddr); //Add the Primary Address & associated contact in a Map
            }
    }
}
  • 2
    If they're done in parallel, then one batch won't see the details of another batch that's in process. post your trigger code, sounds like it has some concurrency bugs. – superfell Dec 29 '13 at 18:03
  • Before I paste my code, if one batch can't see the records inserted by another, then how can my trigger handle it? – Vid L Dec 30 '13 at 15:19
  • 1
    you have to serialize access by locking the relevant records (the parent in this case) – superfell Dec 30 '13 at 15:55
  • Agree with @superfell, that you're facing rather typical concurrency issue. It would be perfect if you post your code, so as everyone can learn from it! Of course the simplest solution would be changing the Bulk API mode to Serial. – endrju Dec 31 '13 at 14:23
  • Appreciate the responses! Will post it by tomorrow.. – Vid L Dec 31 '13 at 17:32
1

I think the problem is in the query for primary addresses. New address records only become visible in SOQL queries in the After-context. If you then do multiple inserts/updates at once they don't see each other's new entries yet. So either you have to lock the parent, as suggested by superfell, or move the logic to the After-context and add an additional Address-update statement for the addresses that should actually be marked as Primary.

I actually prefer the latter since, although it costs an extra update statement, does not impact the user experience and does not contain the risk of locking issues.

  • I'd prefer the latter method too. And I can always contains recursive updates. But how does the 'lock the parent' technique work? I'm interested in knowing – Vid L Jan 2 '14 at 13:00
  • 1
    Just lock the parent record, without doing anything with it: salesforce.com/us/developer/docs/apexcode/Content/… While there is a lock on the parent record, other parallel processes processing childs of that parent will be halted. Then when the transaction ends, the lock is also relieved. – Guy Clairbois Jan 3 '14 at 14:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.