HTTP/3 QPACK 中 Duplicate encoder 指令的目的是什么?

What is the purpose of the Duplicate encoder instruction in HTTP/3 QPACK?

在 HTTP/3 QPACK 中存在一条指令,用于复制动态 table 中的现有条目;据说它是用来避免添加对旧条目的引用,这可能会阻止插入新条目。

但是,我看不出这有什么用。

If the dynamic table does not contain enough room for a new entry without evicting other entries, and the entries that would be evicted are not evictable, the encoder MUST NOT insert that entry into the dynamic table (including duplicates of existing entries).

To ensure that the encoder is not prevented from adding new entries, the encoder can avoid referencing entries that are close to eviction. Rather than reference such an entry, the encoder can emit a Duplicate instruction (Section 4.3.4), and reference the duplicate instead.

   +--------+---------------------------------+----------+
   | Unused |          Referenceable          | Draining |
   | Space  |             Entries             | Entries  |
   +--------+---------------------------------+----------+
            ^                                 ^          ^
            |                                 |          |
      Insertion Point                 Draining Index  Dropping
                                                       Point

(摘自 IETF-QUIC-QPACK-DRAFT16)

这里的想法是,一旦一个字段通过了排空索引,我们想让它以较低的优先级存在,所以我们会添加一个对它的引用,让任何新的请求都引用那个引用,这样旧字段变得未被引用并且将被删除?在它被删除和添加新引用之间,这些中间请求可以填充到动态 table 中,否则会被阻止?

谢谢。

旧条目的复制生成 该 table 条目的新副本;它不引用旧条目。这允许旧条目老化(被逐出)。

复制指令实现了三个目标:

  1. 它允许旧条目被逐出(上面已介绍)。
  2. 它允许连续使用动态 table 而不是再次插入新条目(旧条目的副本)并可能冒阻塞听众的风险;和
  3. 复制旧条目比插入新条目更便宜——从需要通过网络发送的字节数的角度来看;这提高了压缩性能。

发出Duplicate指令时的QPACK Internet Draft touches upon可能是profitable。选择何时以及是否发出 Duplicate 指令是一个重要的决定,它可能会极大地影响压缩性能。每个编码器都会 select 自己的策略。

例如,ls-qpack contains this code:

    candidate = NULL;
    STAILQ_FOREACH(entry, &enc->qpe_all_entries, ete_next_all)
    {
        if (!qenc_entry_is_draining(enc, entry))
            break;
        if (candidate && ETE_SIZE(entry) < ETE_SIZE(candidate))
            continue;
        for (next = STAILQ_NEXT(entry, ete_next_nameval); next;
                                    next = STAILQ_NEXT(next, ete_next_nameval))
            if (next->ete_nameval_hash == entry->ete_nameval_hash
                    && next->ete_name_len == entry->ete_name_len
                    && next->ete_val_len == entry->ete_val_len
                    && 0 == memcmp(ETE_NAME(next), ETE_NAME(entry),
                                                        next->ete_name_len)
                    && 0 == memcmp(ETE_VALUE(next), ETE_VALUE(entry),
                                                        next->ete_val_len))
                break;
        if (!next
                && qenc_hist_seen(enc, HE_NAMEVAL, entry->ete_nameval_hash)
                        && qenc_has_or_can_evict_at_least(enc, ETE_SIZE(entry)))
            candidate = entry;
    }

代码说:找到一个还没有被复制,名称和值最近被使用(qenc_hist_seen()),并且可以被复制(qenc_has_or_can_evict_at_least())的引流条目。