Firebase 云功能与 Firestore 返回“已超过截止日期”

Posted

技术标签:

【中文标题】Firebase 云功能与 Firestore 返回“已超过截止日期”【英文标题】:Firebase Cloud Function with Firestore returning "Deadline Exceeded" 【发布时间】:2018-03-21 04:31:02 【问题描述】:

我从 Firestore 文档中获取了一个示例函数,并能够在我的本地 firebase 环境中成功运行它。但是,一旦我部署到我的 firebase 服务器,该功能就完成了,但在 firestore 数据库中没有任何条目。 firebase 功能日志显示“已超过截止日期”。我有点困惑。有谁知道为什么会发生这种情况以及如何解决这个问题?

这里是示例函数:

exports.testingFunction = functions.https.onRequest((request, response) => 
var data = 
    name: 'Los Angeles',
    state: 'CA',
    country: 'USA'
;

// Add a new document in collection "cities" with ID 'DC'
var db = admin.firestore();
var setDoc = db.collection('cities').doc('LA').set(data);

response.status(200).send();
);

【问题讨论】:

不确定它是否与您看到的错误有关,但您可能希望通过使用 return db.collection('cities').doc('LA').set(data).then(result => response.status(200)) 来等待 doc(...).set(data) 返回的承诺解决 @Ramon 将其更改为 Promise 确实从日志中删除了错误,但不幸的是没有成功地将数据插入到集合中。 【参考方案1】:

Firestore 有限制。

可能会因为其限制而发生“超过最后期限”。

看到这个。 https://firebase.google.com/docs/firestore/quotas

每秒对文档的最大写入速率 1

https://groups.google.com/forum/#!msg/google-cloud-firestore-discuss/tGaZpTWQ7tQ/NdaDGRAzBgAJ

【讨论】:

对此有任何解决方法建议吗?我想在使用节点处理它的同时对 1500000 个数据进行初始数据导入。任何建议表示赞赏。 @KD。每秒 1 个限制用于文档。对于数据库,限制为“每个数据库每秒的最大写入次数 | 2,500(每秒最多 2.5 MiB)”。你可以使用 setTimeout 来避免这个限制,尽管这需要时间。 谢谢。添加超时将是我尝试的最后一个选择,因为我有太多的初始输入数据。我真的希望有一种方法可以像实时数据库一样导入 JSON。现在我继续使用实时数据库,因为同样的方法适用于它。 我已经通过 2 个步骤“解决”了它。 1)我使用批量写入。并将批次设置为 490(最大值显然是 500)2)我等待每个批次完成,然后再发送下一个。【参考方案2】:

我编写了这个小脚本,它使用批量写入(最多 500 次)并且只在另一个之后写入一个批次。

首先创建一个batchWorkerlet batch: any = new FbBatchWorker(db);来使用它 然后向工作人员batch.set(ref.doc(docId), MyObject); 添加任何内容。并通过batch.commit() 完成它。 该 api 与普通 Firestore Batch (https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes) 相同,但目前仅支持 set

import  firestore  from "firebase-admin";

class FBWorker 
    callback: Function;

    constructor(callback: Function) 
        this.callback = callback;
    

    work(data: 
        type: "SET" | "DELETE";
        ref: FirebaseFirestore.DocumentReference;
        data?: any;
        options?: FirebaseFirestore.SetOptions;
    ) 
        if (data.type === "SET") 
            // tslint:disable-next-line: no-floating-promises
            data.ref.set(data.data, data.options).then(() => 
                this.callback();
            );
         else if (data.type === "DELETE") 
            // tslint:disable-next-line: no-floating-promises
            data.ref.delete().then(() => 
                this.callback();
            );
         else 
            this.callback();
        
    


export class FbBatchWorker 
    db: firestore.Firestore;
    batchList2: 
        type: "SET" | "DELETE";
        ref: FirebaseFirestore.DocumentReference;
        data?: any;
        options?: FirebaseFirestore.SetOptions;
    [] = [];
    elemCount: number = 0;
    private _maxBatchSize: number = 490;

    public get maxBatchSize(): number 
        return this._maxBatchSize;
    
    public set maxBatchSize(size: number) 
        if (size < 1) 
            throw new Error("Size must be positive");
        

        if (size > 490) 
            throw new Error("Size must not be larger then 490");
        

        this._maxBatchSize = size;
    

    constructor(db: firestore.Firestore) 
        this.db = db;
    

    async commit(): Promise<any> 
        const workerProms: Promise<any>[] = [];
        const maxWorker = this.batchList2.length > this.maxBatchSize ? this.maxBatchSize : this.batchList2.length;
        for (let w = 0; w < maxWorker; w++) 
            workerProms.push(
                new Promise((resolve) => 
                    const A = new FBWorker(() => 
                        if (this.batchList2.length > 0) 
                            A.work(this.batchList2.pop());
                         else 
                            resolve();
                        
                    );

                    // tslint:disable-next-line: no-floating-promises
                    A.work(this.batchList2.pop());
                ),
            );
        

        return Promise.all(workerProms);
    

    set(dbref: FirebaseFirestore.DocumentReference, data: any, options?: FirebaseFirestore.SetOptions): void 
        this.batchList2.push(
            type: "SET",
            ref: dbref,
            data,
            options,
        );
    

    delete(dbref: FirebaseFirestore.DocumentReference) 
        this.batchList2.push(
            type: "DELETE",
            ref: dbref,
        );
    

【讨论】:

即使只设置了 20 个项目,我也会遇到同样的错误,这是为什么呢?文档密钥在所有文档中都是唯一的(时间戳)可能有什么问题? 因为体积大,写入文档需要一秒多时间 感谢您的脚本。只有一件事——这个脚本和从firebase批量写入有什么区别? 主要区别在于,它不使用事务。 Firebase 批量写入的问题是,它会发送例如 100 次更新。等待所有完成写入数据库,然后您可以写入下一批。我有机会亲自与 Firebase 团队交谈,他们说,最好不批量直接编写。除非您确实需要确保所有写入都同时发生。 好的,知道了。我尝试使用您的脚本进行删除操作,这很奇怪,但即使使用脚本,我也遇到了超出期限的错误。不知道为什么,再去看看。它只删除了 100 个文档,然后我得到了错误。然后我也可以删除 100 个文档,然后我会收到错误消息。看起来这里有 100 次操作的限制。有什么想法吗?【参考方案3】:

根据我自己的经验,当您尝试使用糟糕的互联网连接编写文档时也会发生此问题。

我使用类似于 Jurgen 的建议的解决方案,一次批量插入小于 500 个的文档,如果我使用不太稳定的 wifi 连接,则会出现此错误。当我插入电缆时,具有相同数据的相同脚本运行没有错误。

【讨论】:

【参考方案4】:

如果大约 10 秒后出现错误,可能不是您的互联网连接,可能是您的函数没有返回任何承诺。以我的经验,我得到错误只是因为我将一个 firebase 设置操作(它返回一个承诺)包装在另一个承诺中。 你可以这样做

return db.collection("COL_NAME").doc("DOC_NAME").set(attribs).then(ref => 
        var SuccessResponse = 
            "code": "200"
        

        var resp = JSON.stringify(SuccessResponse);
        return resp;
    ).catch(err => 
        console.log('Quiz Error OCCURED ', err);
        var FailureResponse = 
            "code": "400",
        

        var resp = JSON.stringify(FailureResponse);
        return resp;
    );

而不是

return new Promise((resolve,reject)=> 
    db.collection("COL_NAME").doc("DOC_NAME").set(attribs).then(ref => 
        var SuccessResponse = 
            "code": "200"
        

        var resp = JSON.stringify(SuccessResponse);
        return resp;
    ).catch(err => 
        console.log('Quiz Error OCCURED ', err);
        var FailureResponse = 
            "code": "400",
        

        var resp = JSON.stringify(FailureResponse);
        return resp;
    );

);

【讨论】:

【参考方案5】:

我通过让 15 个并发 AWS Lambda 函数将 10,000 个请求写入数据库到不同的集合/文档毫秒部分来测试这一点。我没有收到DEADLINE_EXCEEDED 错误。

请参阅firebase 上的文档。

'deadline-exceeded':截止日期在操作完成之前已过期。对于改变系统状态的操作,即使操作成功完成,也可能会返回此错误。例如,来自服务器的成功响应可能已经延迟了足够长的时间以至截止日期到期。

在我们的例子中,我们正在写入少量数据,并且大部分时间都可以正常工作,但丢失数据是不可接受的。我还没有得出结论,为什么 Firestore 无法写入简单的小数据。

解决方案:

我正在使用一个使用 SQS 事件触发器的 AWS Lambda 函数。

  # This function receives requests from the queue and handles them
  # by persisting the survey answers for the respective users.
  QuizAnswerQueueReceiver:
    handler: app/lambdas/quizAnswerQueueReceiver.handler
    timeout: 180 # The SQS visibility timeout should always be greater than the Lambda function’s timeout.
    reservedConcurrency: 1 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit    
    events:
      - sqs:
          batchSize: 10 # Wait for 10 messages before processing.
          maximumBatchingWindow: 60 # The maximum amount of time in seconds to gather records before invoking the function
          arn:
            Fn::GetAtt:
              - SurveyAnswerReceiverQueue
              - Arn
    environment:
      NODE_ENV: $self:custom.myStage

我正在使用连接到我的主队列的死信队列来处理失败的事件。

  Resources:
    QuizAnswerReceiverQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: $self:provider.environment.QUIZ_ANSWER_RECEIVER_QUEUE
        # VisibilityTimeout MUST be greater than the lambda functions timeout https://lumigo.io/blog/sqs-and-lambda-the-missing-guide-on-failure-modes/

        # The length of time during which a message will be unavailable after a message is delivered from the queue.
        # This blocks other components from receiving the same message and gives the initial component time to process and delete the message from the queue.
        VisibilityTimeout: 900 # The SQS visibility timeout should always be greater than the Lambda function’s timeout.

        # The number of seconds that Amazon SQS retains a message. You can specify an integer value from 60 seconds (1 minute) to 1,209,600 seconds (14 days).
        MessageRetentionPeriod: 345600  # The number of seconds that Amazon SQS retains a message. 
        RedrivePolicy:
          deadLetterTargetArn:
            "Fn::GetAtt":
              - QuizAnswerReceiverQueueDLQ
              - Arn
          maxReceiveCount: 5 # The number of times a message is delivered to the source queue before being moved to the dead-letter queue.
    QuizAnswerReceiverQueueDLQ:
      Type: "AWS::SQS::Queue"
      Properties:
        QueueName: "$self:provider.environment.QUIZ_ANSWER_RECEIVER_QUEUEDLQ"
        MessageRetentionPeriod: 1209600 # 14 days in seconds

【讨论】:

以上是关于Firebase 云功能与 Firestore 返回“已超过截止日期”的主要内容,如果未能解决你的问题,请参考以下文章

导出 Firestore 备份数据的云功能。使用 firebase-admin 或 @google-cloud/firestore?

使用 Firebase 云功能和 Firestore 在 android 上验证购买 [关闭]

Firebase Firestore +云功能服务器端验证应用内购买收据(Swift + NodeJS)

Firebase firestore云功能显示错误:无效使用“undefined”类型作为Firestore参数

Firebase云功能Firestore触发onWrite在本地测试时未按预期运行

如何使用用于 JS 开发的 firebase Emulator 设置 firebase firestore 和云功能测试套件