Cloud Spanner Errorcode Error: 9 FAILED_PRECONDITION

Cloud Spanner Errorcode Error: 9 FAILED_PRECONDITION

当我的云函数负载很重时开始收到此错误,触发次数超过 5 times/sec。

Error: 9 FAILED_PRECONDITION: This transaction has been invalidated by 
a later transaction in the same session. 

我是运行一个用nodejs客户端读写的事务。

文档说:

Operation was rejected because the system is not in a state required 
for the operation's execution.

交易完成,并将数据保存到Spanner,所以交易没有被拒绝。任何人都知道这意味着什么?

这是我的代码:

'use strict';

const { Spanner } = require('@google-cloud/spanner');

const constants = require('./constants');
const tableNames = constants.tableNames;


const spanner = new Spanner({ projectId: constants.projectId });

exports.spannerSync = async (data, context) => {

    const instance = spanner.instance(constants.instanceId);
    const database = instance.database(constants.databaseId);

    try {
        let profileExists;
        const [qOneRows] = await exists(attrA, attrB, database);

        if (qOneRows.length !== 0 && qOneRows !== undefined) {
            profileExists = true;
        }

        upsertNewProfile(attrA, attrB, profileExists, database);

    } catch (error) {
        console.log(error);
       
    }

};

function exists(attrA, attrB, database) {
    const query = {
        sql: {
            DML_PROFILE_EXISTS
        },
        params: {
            attr: attrA,
            attrB: attrB
        }
    };

    return database.run(query);
}

function deleteProfile(attrA, attrB, transaction) {
    const query = {
        sql: DML_DELETE_PROFILE,
        params: {
            attr: attrA,
            attrB: attrB
        }
    };
    return transaction.runUpdate(query);
}

function existsInTableB(attrB, attrA, database) {
    const query = {
      sql: EXISTS_DML_QUERY,
      params: {
        attrA: attrA,
        attrB: attrB
      }
    }
    return database.run(query);

}

async function upsertNewProfile(
    attrA, attrB,
    profileExists,
    database
) {
    let inActive = attrA.status;

    database.runTransaction(async (err, transaction) => {
        if (err) {
            console.error('Error running transaction:  ' + err);
            return;
        }
        if (profileExists) {
         try {
            const deleted = await deleteProfile(attrA, attrB, 
            transaction);
          } catch (error) {
            console.log("ERROR   " + error);
           
        }

        }

        transaction.upsert(tableNames.A, attrA);

        transaction.upsert(tableNames.B, attrB);

        transaction.upsert(tableNames.C, attrB.attrA);

        transaction.upsert(tableNames.D, attrB.attrB);

        transaction.upsert(tableNames.E, attrB.attrC);

        transaction.upsert(tableNames.F, attrB.attrD);

        transaction.upsert(tableNames.G, attrB.attrE);

        transaction.upsert(tableNames.H, attrB.attrF);

        transaction.upsert(tableNames.I, attrB.attrG);

        transaction.upsert(tableNames.J, attrB.attrH);

        transaction.upsert(tableNames.F, attrB.attrI);
        
        if (inActive) {
            let [pidExistInBL] = await existsInTableB(attrA, attrB, database);
            if (pidExistInBL.length == 0) {
                transaction.upsert(tableNames.B, getObject(attrA, attrB));
            }
        }

        try {

            transaction.commit((error) => {

                if (!error) {
                    console.log("Tables where successfully updated");
                 
                } else {
                    console.log("ERROR   " + error);
                }
            });

        } catch (error) {
            console.log("ERROR   " + error);
           
        } finally {

            transaction.end();
        }
    });

}

抱歉,try/catch 在将代码写入 SO 时丢失了。 它在代码中,我发现了错误,但如果 deleteProfile 失败,我不会回滚事务。

还有一些其他细节我没有提到。 还出现了另一个错误,说我已经达到了云函数的 cpu 配额限制,并且该函数设置为重试,(我已经扩展了配额限制)并且问题消失了.

所以我怀疑正在发生的事情是,当云函数达到配额限制并且 deleteProfile 失败时,这些函数会一遍又一遍地重试。因为当达到配额限制时,执行次数和 Spanner 会话数量会猛增。仍然有点困惑,如果它正在重试并打开更多会话,然后每个云功能中的事务正在重新查询,这是否是同时看到 Spanner CPU-利用率峰值的合理解释?

那么我也应该在 deleteProfile 周围的 catch 子句中应用事务回滚,对吗?

Cloud Spanner 一次只能在 session 上执行一个事务。您收到的错误消息表明您已经在已经有活动事务的会话上启动了第二个事务。

当您尝试对第一个不再有效的事务执行 statement/query 时返回错误消息。第二笔交易通常会成功。

听起来您 运行 遇到了某种竞争条件/会话泄漏问题。您是否有代码示例或产生此错误的代码的更多详细信息?


在代码示例后编辑

我想知道如果

是否会出现这个问题
const deleted = await deleteProfile(attrA, attrB, transaction)

失败,因为这会在没有明确结束交易的情况下终止交易。如果是可重试错误,新事务将自动在同一会话上重试。由于初始交易未明确结束,该交易可能会生成您描述的错误消息,而重试交易会成功。

您可以尝试将该语句放入与您的提交类似的 try-catch-block 中,看看是否可以解决问题。