Using Heroku Postgres from local development setup

When developing locally against a Heroku Postgres database (hosted on Heroku) you quickly realize you cannot connect due to a self-signed certificate (or rather Heroku signed certificate). The solution is to ensure that you use the non-validating SSL factory for the connection pool. This is easily done by appending “ssl=true” and “sslfactory=org.postgresql.ssl.NonValidatingFactory” to the database URL i.e. something like the following with the addition in bold:

DATABASE_URL=postgres://foo:bar@ec2-55-222-96-152.eu-west-1.compute.amazonaws.com:5432/baz?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory

Yet another reason for using Platform as a Service (PaaS)

I’m using a Platform as a Service (PaaS) for all my application development — because why wouldn’t I?! Heroku is the platform of choice with the full disclaimer that Heroku is a Salesforce company and I work for Salesforce.

However the driving reason of my usage of Heroku is that being a PaaS Heroku provides me with a high level of abstraction of the underlying compute infrastructure. On Heroku I just worry about the app and not anything else. No load balancing. No compute instances. No gluing and regluing stuff together. Just the app.

As part of the applications I have on Heroku I have a wide variety of databases including Heroku Postgres (a fully managed and elastic Postgres instance). A nice aspect of this is that for me Postgres is just another data store but I do not have to worry about the operations of it. This is great as I’m much better at writing apps than managing an always-on, fully load balanced and highly available database cluster.

Yesterday I received a notice about upcoming maintenance and one section in the email made me take notice so I thought I would share:

Your database must undergo maintenance.

At that time, we will create a hidden follower, wait for it to catch up, push the hidden follower’s creds to your app, unfollow it from your former leader and repoint any followers you may have to properly follow your new leader.

Let’s break that out really quick… When they — Heroku — do the maintenance for me and I won’t have to lift a finger they will:

  • Create a hidden follower (database)
  • Wait for it to catch up (to allow uninterrupted operations)
  • Push the hidden follower’s creds to your app (so the app uses the new follower database)
  • Unfollow it from your former leader (to ensure smooth operations)
  • Repoint any followers you may have to properly follow your new leader (again to ensure smooth operations)

All this without me doing anything. Again I just worry about my app that will continue running without any change by the way. How cool is that?! I just worry about the app.

Just another reason for using a PaaS…

Simultaneous INSERTs with Heroku Connect in the same transaction

So had some fun working with Heroku Connect the other day writing some data from Heroku to Salesforce using Postgres. On Heroku that is easily done using Heroku Connect which is a turn-key solution we have for that. Easy to use and easy to setup. The way it works is that you configure a connection from Heroku to Salesforce, map it to a Postgres database on Heroku and selects the objects and fields from Salesforce to synchronize and Heroku Connect does the rest. Powerful stuff!

So what did I do and what did I learn…

In Salesforce I have two custom objects – a parent custom object (Parent__c) and a child custom object (Child__c) with a master-detail relation to the parent (Parent__r / Parent__c). Both the Parent and Child custom objects have an external ID (External_ID__c) to make writing back from Heroku easy.

Once the objects are mapped using Heroku Connect I get 2 new tables in the “salesforce”-schema in Postgres (“salesforce.parent__c” and “salesforce.child__c”) with the selected fields.

What I wanted to do was – in a transaction – write a new parent record and the associated child records. I’m using node.js but since I’m using raw SQL that shouldn’t be too different to any other language. What I did do however is use promises which is important for this post.

Below is some code to illustrate what I did. Basically the code does the following:

  1. Starts a transaction (BEGIN)
  2. Inserts the parent record (INSERT INTO salesforce.parent__c…)
  3. Creates a promise to insert the 3 child records (INSERT INTO salesforce.child__c…) and resolves them all at once (i.e. the inserts will happen simultaneously)
  4. Commits the transaction (COMMIT) if all is good or rolls it back (ROLLBACK)
// data to work with
const objData = {
   "id": "ffe16a92-0f22-4a16-947a-461d307b4905", 
   "text": "Some Parent Text...", 
   "children": [
      {"id": "ffe16a92-0f22-4a16-947a-461d307b5906", "text": "Child1"},
      {"id": "ffe16a92-0f22-4a16-947a-461d307c4907", "text": "Child2"},
      {"id": "ffe16a92-0f22-4a16-947a-461d307b3904", "text": "Child3"}
   ]
};

// start a new tx
pool.query("BEGIN").then(rs => {
   // insert parent record
   return pool.query(`INSERT INTO salesforce.parent__c 
      (External_ID__c, Text__c) 
      VALUES 
      ('${objData.id}', '${objData.text}');`);
}).then(rs => {
   // map each child to a promise to insert child record
   const promises = objData.children.map(child => {
      return pool.query(`INSERT INTO salesforce.Child__c 
         (parent__r__external_id__c, "External_ID__c", "Text__c") 
         VALUES 
         ('${objData.id}', '${child.id}', '${child.text}')`);
   return Promise.all(promises);
}).then(rs => {
   // commit tx
   console.log("Commiting tx");
   return pool.query("COMMIT");
}).catch(err => {
   // roll tx back
   console.log(`Rolling back tx: ${err.message}`);
   return pool.query("ROLLBACK");
})

The issue was that I kept seeing 2 errors in the log when the code ran as shown below. Funny thing is that I didn’t see any information about the transaction being rolled back.

2019-03-16T14:32:20-07:00 event="record 8 ERROR ↑SALESFORCE [SOAP] Foreign key external ID: ffe16a92-0f22-4a16-947a-461d307b4905 not found for field External_ID__c in entity Parent__c" addon_id=ba3d4995-092d-4095-aa5e-7ac41981bd93 object_type=sync object_id=ba3d4995-092d-4095-aa5e-7ac41981bd93 state=POLLING_DB_CHANGES level=debug  
22019-03-16T14:32:20-07:00 event="record 9 ERROR ↑SALESFORCE [SOAP] Foreign key external ID: ffe16a92-0f22-4a16-947a-461d307b4905 not found for field External_ID__c in entity Parent__c" addon_id=ba3d4995-092d-4095-aa5e-7ac41981bd93 object_type=sync object_id=ba3d4995-092d-4095-aa5e-7ac41981bd93 state=POLLING_DB_CHANGES level=debug  

Okay so clearly the insert fails because the foreign key of the child records cannot be resolved i.e. the ID pointing from salesforce.child__c to salesforce.parent__c. But why only 2 errors and not 3 errors? (I was attempting to insert 3 child records) I added an intermediate COMMIT before inserting the child records and started a new transaction and then all was well. So it had to do with the transaction… In Postgres the solution would be to defer the referential constraint check but I couldn’t do it. Why? Because Heroku Connect doesn’t use constraints as can be seen if inspecting the child table (psql: “\d salesforce.child__c”) but seems to do the referential integrity check using a trigger on the tables created by Heroku Connect. I’m guessing this is part of the magic that it Heroku Connect.

I played a bit more around with the code and finally solved the issue by doing the inserts in turn by resolving the promises sequentially. So instead of using Promise.all (that resolves the promises simultaneously) I resolved each promise in turn and then it worked.

My understanding of this is must be as follows. The triggers created by Heroku Connect that handles the underlying integration to Salesforce uses some log-tables (as described in the documentation) but the triggers must also employ some locking of the salesforce.parent__c table and hence the other inserts to salesforce.child__c running at the same time fails. Also this doesn’t cause my transaction to roll back which from my point of view is unfortunate as I really want the inserts to be atomic hence the use of a transaction. In this case there was a solution in doing the inserts sequentially. I’ll follow up internally in Salesforce and see if I can provide a better answer that the above.