Handling Postgres Serialization errors in Sequelize

When using the REPEATABLE READ or SERIALIZABLE isolation levels, you should be prepared to handle serialization (error code 40001) and deadlock failures (error code 40P01) in your code. Well this is how you do it:

let retry = true;
        while (retry) {
            const t = await sequelize.transaction({
                isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE
            });
            try {
                let id = await the_function_that_actually_does_the_work(t);
                await t.commit();
                retry = false;
            } catch (err) {
                retry = is_serialization_failure(err); // serialization failures should be retried
                if (!is_rollback_failure(err)) {
                    await t.rollback();
                }   // else transaction has already been rolled back and you should NOT roll it back again!
                if (!retry) {
                    throw err;  // re-throw so caller knows we failed
                }   
            }
        }

where

export const is_serialization_failure = (e): boolean => {
    // https://www.postgresql.org/docs/current/mvcc-serialization-failure-handling.html
    // The optional chaining (?.) operator accesses an object's property or calls a function.
    // If the object accessed or function called using this operator is undefined or null, 
    // the expression short circuits and evaluates to undefined instead of throwing an error.
    // more accurately only 40001 is serialization failure. 40P01 is a deadlock failure.
    return (e.original?.code === '40001' || e.original?.code === '40P01');
}

export const is_rollback_failure = (e): boolean => {
    // https://www.postgresql.org/docs/current/errcodes-appendix.html
    return e.original?.code?.startsWith('40');    
}

That’s it for this post! Let me know what you think.

This entry was posted in Computers, programming, Software and tagged , . Bookmark the permalink.

Leave a comment