边缘情况下的IndexedDb事务自动提交行为

时间:2021-12-09 22:51:32

Tx is committed when :

Tx致力于:

  • request success callback returns - that means that multiple requests can be executed within transaction boundaries only when next request is executed from success callback of the previous one
  • 请求成功回调返回 - 这意味着只有当从前一个请求的成功回调执行下一个请求时,才能在事务边界内执行多个请求
  • when your task returns to event loop
  • 当您的任务返回到事件循环时

It means that if no requests are submitted to it, it is not committed until it returns to event loop. These facts pose 2 problematic states :

这意味着如果没有提交请求,则在返回事件循环之前不会提交。这些事实构成了两个有问题的状态:

  • placing a new IDB request by enqueuing a new task to event loop queue from within the success callback of previous request instead of submitting new request synchronously
    • in that case the first success callback immediately returns but another IDB request has been scheduled
      • are all the asynchronous requests executed within the single initial transaction? This is quite essential in case you want to implement result pulling with back-pressure where consumer gives you a feedback in form of a Future that it is ready to consume another response
      • 是在单个初始事务中执行的所有异步请求吗?这是非常重要的,如果你想用背压实现结果拉动,消费者会以Future的形式给你一个反馈,它准备消耗另一个响应
    • 在这种情况下,第一次成功回调会立即返回,但是已经安排了另一个IDB请求是在单个初始事务中执行的所有异步请求吗?这是非常重要的,如果你想用背压实现结果拉动,消费者会以Future的形式给你一个反馈,它准备消耗另一个响应
  • 通过将新任务从前一个请求的成功回调中排入事件循环队列而不是同步提交新请求来放置新的IDB请求,在这种情况下,第一个成功回调会立即返回但是已经调度了另一个IDB请求是所有执行的异步请求在单一的初始交易中?这是非常重要的,如果你想用背压实现结果拉动,消费者会以Future的形式给你一个反馈,它准备消耗另一个响应
  • creating a ReadWrite tx, not placing any requests against it and creating another one before returning to event loop
    • does creating a new one implicitly commits the previous tx ? If not, serious write lock starvations might occur, because :
    • 创建一个新的隐式提交前一个tx?如果不是,可能会发生严重的写锁定,因为:
  • 创建一个ReadWrite tx,没有对它发出任何请求并在返回事件循环之前创建另一个请求创建一个新的隐式提交前一个tx吗?如果不是,可能会发生严重的写锁定,因为:

If multiple "readwrite" transactions are attempting to access the same object store (i.e. if they have overlapping scope), the transaction that was created first must be the transaction which gets access to the object store first. Due to the requirements in the previous paragraph, this also means that it is the only transaction which has access to the object store until the transaction is finished.

如果多个“readwrite”事务试图访问同一个对象存储(即,如果它们具有重叠的范围),则首先创建的事务必须是首先访问对象存储的事务。由于前一段中的要求,这也意味着它是唯一可以在事务完成之前访问对象存储的事务。

The example of enqueuing a new task to event loop queue from within the success callback by recursive request submission with back-pressure :

通过带有反压的递归请求提交从成功回调中将新任务排入事件循环队列的示例:

    function recursiveFn(key) {
      val req = store.get(key)
      req.onsuccess = function() {
        observer.onNext(req.result).onsuccess { recursiveFn(nextKey) } 
      }
    }

Observer#onNext // returns Future[Ack] Ack is either Continue or Cancel

Now can onsuccess or onNext do a setTimeout(0) or not to make the whole thing be part of one transaction?

现在可以onsuccess或onNext执行setTimeout(0)或不使整个事件成为一个事务的一部分吗?

Bonus question :

奖金问题:

I think that ReadOnly transactions are exposed to the consumer/user just because it would be hard to detect the end of a batch read if you recursively submit new requests from the success callback of the previous one right? Otherwise I don't see any other reason for them to be exposed to a user, right ?

我认为ReadOnly事务是暴露给消费者/用户的,因为如果你递归地提交来自前一个成功回调的新请求,那么很难检测到批量读取的结束吗?否则我没有看到任何其他原因让他们接触到用户,对吧?

1 个解决方案

#1


2  

I'm not sure I understand your question completely but I'll offer an answer on whether you can safely use IDB transaction events to move a state machine.

我不确定我是否完全理解你的问题,但我会提供一个答案,说明你是否可以安全地使用IDB事务事件来移动状态机。

Yes and no. Yes in theory, no in practice.

是的,不是。在理论上是的,在实践中没有。

I think you understand the transaction lifetime. But to rehash:

我想你了解交易的生命周期。但重新改变:

The lifetime of a transactions lasts as long as it's referenced: it's "active" so long as it's being referenced, after which it is said to be "finished" and the transaction is committed.

事务的生命周期持续时间与引用一样长:只要它被引用,它就是“活动的”,之后它被称为“已完成”并且事务已被提交。

In theory, oncomplete should fire whenever a transaction successfully commits. There's a useful tip in the spec on this that suggests how you could loop:

理论上,只要事务成功提交,就会触发oncomplete。在这个规范中有一个有用的提示,它建议你如何循环:

To determine if a transaction has completed successfully, listen to the transaction’s complete event rather than the IDBObjectStore.add request’s success event, because the transaction may still fail after the success event fires.

要确定事务是否已成功完成,请侦听事务的complete事件而不是IDBObjectStore.add请求的成功事件,因为在成功事件触发后事务仍可能失败。

To safely use this mechanism be sure to watch for non-success events including onblocked and onabort as well.

要安全地使用此机制,请务必注意非成功事件,包括onblocked和onabort。

Practically speaking, I've found transactions to be flakey when long-lived or done consecutively in batches (as you've noted in another IDB comment). I'm generally not engineering my apps to require tricky behavior because, no matter how the spec says it should behavior, I'm seeing wonky transactions in both Firefox and Chromium (but mostly Blink, interestingly) especially when multiple tabs are open.

实际上,我发现交易在长期存在或连续分批完成时是非常好的(正如你在另一个IDB评论中所指出的那样)。我通常不会设计我的应用程序以要求棘手的行为,因为无论规范如何说它应该行为,我在Firefox和Chromium中都看到了不稳定的交易(但主要是Blink,有趣的是),尤其是当多个标签打开时。

I spent many weeks rewriting dash to reuse transactions for supposed performance gains. In the end it could not pass even my basic write tests and I was forced to abandon simultaneous/queued/consecutive transactions and rewrite once again. This time I picked a one-transaction-at-a-time model which is slower but, for me, more reliable (and suggest to avoid my lib and use something like ydn for bulk inserts).

我花了很多周时间重写破折号以重用交易以获得所谓的性能提升。最后它甚至无法通过我的基本写测试,我*放弃同步/排队/连续交易并再次重写。这次我选择了一次一次交易的模型,这个模型速度较慢,但​​对我来说,更可靠(建议避免使用lib并使用ydn进行批量插入)。

I'm not sure on your application requirements, but in my humble opinion tying in your I/O into your event loop seems like a disastrous idea. If I needed an event loop as what I understand to be the term I would definitely use requestAnimationFrame() and throttle that callback if I needed fewer ticks than one per ~33 milliseconds.

我不确定您的应用程序要求,但我认为将您的I / O绑定到事件循环中似乎是一个灾难性的想法。如果我需要一个事件循环,就像我所理解的那样,我肯定会使用requestAnimationFrame()并且如果我需要更少的滴答而不是每33毫秒一个,那就可以限制回调。

#1


2  

I'm not sure I understand your question completely but I'll offer an answer on whether you can safely use IDB transaction events to move a state machine.

我不确定我是否完全理解你的问题,但我会提供一个答案,说明你是否可以安全地使用IDB事务事件来移动状态机。

Yes and no. Yes in theory, no in practice.

是的,不是。在理论上是的,在实践中没有。

I think you understand the transaction lifetime. But to rehash:

我想你了解交易的生命周期。但重新改变:

The lifetime of a transactions lasts as long as it's referenced: it's "active" so long as it's being referenced, after which it is said to be "finished" and the transaction is committed.

事务的生命周期持续时间与引用一样长:只要它被引用,它就是“活动的”,之后它被称为“已完成”并且事务已被提交。

In theory, oncomplete should fire whenever a transaction successfully commits. There's a useful tip in the spec on this that suggests how you could loop:

理论上,只要事务成功提交,就会触发oncomplete。在这个规范中有一个有用的提示,它建议你如何循环:

To determine if a transaction has completed successfully, listen to the transaction’s complete event rather than the IDBObjectStore.add request’s success event, because the transaction may still fail after the success event fires.

要确定事务是否已成功完成,请侦听事务的complete事件而不是IDBObjectStore.add请求的成功事件,因为在成功事件触发后事务仍可能失败。

To safely use this mechanism be sure to watch for non-success events including onblocked and onabort as well.

要安全地使用此机制,请务必注意非成功事件,包括onblocked和onabort。

Practically speaking, I've found transactions to be flakey when long-lived or done consecutively in batches (as you've noted in another IDB comment). I'm generally not engineering my apps to require tricky behavior because, no matter how the spec says it should behavior, I'm seeing wonky transactions in both Firefox and Chromium (but mostly Blink, interestingly) especially when multiple tabs are open.

实际上,我发现交易在长期存在或连续分批完成时是非常好的(正如你在另一个IDB评论中所指出的那样)。我通常不会设计我的应用程序以要求棘手的行为,因为无论规范如何说它应该行为,我在Firefox和Chromium中都看到了不稳定的交易(但主要是Blink,有趣的是),尤其是当多个标签打开时。

I spent many weeks rewriting dash to reuse transactions for supposed performance gains. In the end it could not pass even my basic write tests and I was forced to abandon simultaneous/queued/consecutive transactions and rewrite once again. This time I picked a one-transaction-at-a-time model which is slower but, for me, more reliable (and suggest to avoid my lib and use something like ydn for bulk inserts).

我花了很多周时间重写破折号以重用交易以获得所谓的性能提升。最后它甚至无法通过我的基本写测试,我*放弃同步/排队/连续交易并再次重写。这次我选择了一次一次交易的模型,这个模型速度较慢,但​​对我来说,更可靠(建议避免使用lib并使用ydn进行批量插入)。

I'm not sure on your application requirements, but in my humble opinion tying in your I/O into your event loop seems like a disastrous idea. If I needed an event loop as what I understand to be the term I would definitely use requestAnimationFrame() and throttle that callback if I needed fewer ticks than one per ~33 milliseconds.

我不确定您的应用程序要求,但我认为将您的I / O绑定到事件循环中似乎是一个灾难性的想法。如果我需要一个事件循环,就像我所理解的那样,我肯定会使用requestAnimationFrame()并且如果我需要更少的滴答而不是每33毫秒一个,那就可以限制回调。