Swift/FastPost
总结
- 使用单独的“content-type”特定时间戳更新容器,而不更改 row['created_at'] 值,似乎可以解决快速 POST 的容器更新问题。
- 这将修复容器列表的不一致性,并允许容器同步“找到”更新的对象。
- 容器同步在实际同步更新的对象时会引发其他一些棘手的问题:content-type 的时间戳必须单独传递,POST 的用户元数据的时间戳也必须单独传递。接收端可能需要重建 .data 和 .meta 文件。
- 两向量时间戳 *可能* 提供一种合适的机制来表示“meta”时间戳,但编码 (元数据,时间戳) 元组可能更合适,并且将从快速 POST 的可行性中分离出关于时间戳偏移数字的待定决策。
动机
本次讨论的目的是探讨一种潜在的解决方案,以使快速 POST 修改的 content-type 能够准确地反映在容器列表中(认识到其他人已经研究过这个问题,并且可能立即发现此建议中的缺陷……)。
本次讨论的时机是由存储策略引入的新两向量时间戳功能促发的,该功能 *可能* 提供一种支持快速 POST content-type 更新的机制。但是,快速 POST 用例将需要比存储策略调解器所需的更多数量的时间戳偏移数字。因此,在确定时间戳格式之前,值得考虑快速 POST 用例是否有希望(尽管,如稍后讨论,可能无需重用两向量时间戳即可实现此建议)。
背景
快速 POST 之后容器 content-type 的更新目前无法发生,因为只有在给出较新的时间戳时,对象行才会更新,并且对快速 POST 执行此操作会错误地冒着将“陈旧”etag 和大小信息标记为容器数据库中的“新鲜”信息。我的理解是,这就是为什么 post-as-copy 是处理对象 POST 的默认模式。
建议的修改(抽象描述)
在处理修改对象 content-type 的快速 POST 时,修改对象服务器行为以发出容器更新,该更新传递现有对象 .data 文件的的时间戳以及指示对象 content-type 修改时间的第二个时间戳 t_meta(t_meta 将是 .meta 文件的的时间戳)。
修改容器服务器以在对象的数据库行中记录 t_meta(注意:不假设更改对象表以添加 t_meta 的新列 - 稍后讨论确切的机制)。
修改容器代理的 merge_items() 方法,以便使用指示对象 .data 文件的时间戳来持久化最新的 content-type(如 t_meta 所示) - 详细信息如下所述。
示例场景
考虑一个在时间 t1 PUT 的对象的初始状态。
Obj server 1,2,3: …/t1.data {etag=ETAG, size=s1, c_type=OLD_TYPE}
Container server 1,2,3: {o, etag=ETAG, size=s1, c_type=OLD_TYPE, ts=t1 }
场景 1:所有服务器初始一致,成功快速 POST 修改对象的 content-type。
当一切顺利时,我们的对象服务器将最终处于一致的状态
Obj server 1,2,3: …/t1.data {etag=ETAG, size=s1, c_type=OLD_TYPE}
…/t2.meta {c_type=NEW_TYPE}
该建议是快速 POST 触发容器更新,这需要在容器后端 merge_items() 方法中进行略有不同的处理
如果存在 name=o 且 created_at>=t1 的行,则当前不会进行任何数据库更改。现在,如果存在这样的行 AND 它的 t_meta
这使我们得到
Container server 1,2,3: {o, etag=ETAG, size=s1, c_type=NEW_TYPE, ts=t1, t_meta=t2}
(再次,这是数据库行内容的抽象表示 - 不建议更改数据库以添加 t_meta 的列 - 参见后面的讨论)。请注意,容器服务器的时间戳仍然是 t1,即 .data 文件的的时间戳。
现在考虑一些失败场景
场景 2:快速 POST 失败到容器服务器子集之后的容器更新
例如:
Container server 1,2 : {o, etag=ETAG, size=s1, c_type= NEW_TYPE, ts=t1, t_meta=t2}
Container server 3 : {o, etag=ETAG, size=s1, c_type= OLD_TYPE, ts=t1}
由于我们对 merge_items() 逻辑的修改,数据库不一致将在复制期间修复,即服务器 3 中的行将使用新的 content_type 和 t_meta 更新。
场景 3:对象快速 POST 失败到对象服务器子集。
例如:
Obj server 1,3 : …/t1.data {etag=ETAG, size=s1, c_type=OLD_TYPE}
…/t2.meta {c_type=NEW_TYPE}
Obj server 2 : …/t1.data {etag=ETAG, size=s1, c_type=OLD_TYPE}
对象复制器最终会将 t2.meta 复制到 obj 服务器 2。与现有行为没有变化。
场景 4:快速 POST 发生时,对象服务器子集上的对象数据陈旧。
例如:
Obj server 1,3 : …/t1.data {etag=ETAG, size=s1, c_type=OLD_TYPE}
…/t2.meta {c_type=NEW_TYPE}
Obj server 2 : …/t0.data {etag=OLD_ETAG, size=s0, c_type=OLD_TYPE}
…/t2.meta {c_type=NEW_TYPE}
对象复制器最终会将 t2.meta 复制到 obj 服务器 2。与现有行为没有变化。
对象服务器 2 将发送容器更新 {o, etag=OLD_ETAG, size=s0, c_type= NEW_TYPE, ts=t0, t_meta=t2}。假设容器服务器是最新的,它们的对象的新行将具有 created_at=t1(> t0)和 t_meta=t2,因此将忽略更新。
场景 5:对象服务器子集上的对象数据陈旧,快速 POST 仅在陈旧对象服务器上成功
Obj server 1,3 : …/t1.data {etag=ETAG, size=s1, c_type=OLD_TYPE}
Obj server 2 : …/t0.data {etag=OLD_ETAG, size=s0, c_type=OLD_TYPE}
…/t2.meta {c_type=NEW_TYPE}
对象复制器最终会将 t2.meta 复制到 obj 服务器 2。与现有行为没有变化。
对象服务器 2 将发送容器更新 {o, etag=OLD_ETAG, size=s0, c_type= NEW_TYPE, ts=t0, t_meta=t2}。假设容器服务器是最新的,它们的对象的新行将具有 created_at=t1(> t0),但我们对 merge_items() 的修改意味着这些行将使用 content_type=NEW_TYPE 和 t_meta=t2 更新。
场景 6:对象服务器子集上的对象数据陈旧,快速 POST 仅在陈旧对象服务器上成功,来自新鲜对象服务器的异步更新。
与上一个场景类似,但容器服务器在从陈旧对象服务器收到快速 POST 诱发的更新时未更新。容器服务器的初始状态是
Container server 1,2,3: {o, etag=OLD_ETAG, size=s0, c_type= OLD_TYPE, ts=t0}
然后更新为
Container server 1,2,3: {o, etag=OLD_ETAG, size=s0, c_type= NEW_TYPE, ts=t0, t_meta=t2}
然后异步更新到达,内容为 {etag=ETAG, size=s1, c_type=OLD_TYPE, ts=t1}。目前,merge_items() 将删除 created_at=t0 的行并插入新行。我们需要对 merge_items() 进行进一步的修改
在删除“旧”行之前,从当前最新的行中选择 t_meta,如果该值大于更新的数据时间戳和 t_meta,则将现有的 t_meta 保留在新插入的行中。
结果是
Container server 1,2,3: {o, etag=ETAG, size=s1, c_type= NEW_TYPE, ts=t1, t_meta=t2}
场景 7:容器服务器不同步
各种故障的组合可能会使容器服务器处于不一致的状态
Container server 1,2 : {o, etag=OLD_ETAG, size=s0, c_type= NEW_TYPE, ts=t0, t_meta=t2}
Container server 3 : {o, etag=ETAG, size=s1, c_type= OLD_TYPE, ts=t1}
对 merge_items() 方法的建议修改将在复制后使容器服务器达到一致性。
容器同步
关于容器同步,至少有两个考虑因素
同步点
容器同步守护程序通过维护同步点(即 ROW_IDs)来跟踪已同步的对象。因此,如果我们在对象表中修改一行以反映对象 content-type 的更改,则我们需要删除/插入新行以确保同步守护程序选择该更改。
选择“最新对象”
当将对象推送到其同步对等体时,容器同步守护程序会向所有本地对象服务器发出 GET 请求,并选择响应中具有最新 x-timestamp 的响应。如果对象已使用快速 POST 更新,则随后续 GET 请求返回的 x-timestamp 是 POST 的时间戳,而不是 .data 文件的时间戳。因此,容器同步守护程序选择来自具有陈旧 .data 文件但最新的 .meta 的服务器的响应的风险(坏事)。
附注:这也是使用 X-Newest 处理已进行快速 POST 的对象时发生的行为,即 X-Newest 返回具有最新 .meta 时间的对象,而不是具有最新 .data 时间的对象。
这需要一些调整,但可以通过让对象服务器在 GET 响应中返回单独的数据文件和 content-type 时间戳来解决(例如,作为 X-Backend-Data-Timestamp 和 X-Backend-Content-Type-Timestamp,或作为两个向量的 X-Backend-Composite-Timestamp)。将结合这些时间戳来选择最“最新”的响应作为同步的来源。“最新”的定义是具有数据时间戳 >= 行的 created_at 值 AND content-type 时间戳 >= 行的 t_meta 值。(同步守护程序已经检查 GET 响应时间戳 >= row['created_at'])。
问题:同步守护程序使用行['created_at'] 值来设置发送到对等容器对象的 PUT 的 x-timestamp,即使该值 < 对象服务器 GET 的 x-timestamp。为什么不使用对象服务器返回的“最新”时间戳?
接下来,content-type 时间戳需要与对象 PUT 一起发送,以便同步对等体可以重建相同的对象视图(例如,发送 X-Content-Type-Timestamp=t_meta*),通过使用新样式的两向量时间戳对数据文件进行时间戳,或将 content-type 时间戳嵌入到数据文件元数据中。
(实际上,更普遍地说,如果发送端有一个 .meta 文件,则应发送通用的 .meta 时间戳,以便接收端可以重建一致的对象状态 - 这意味着如果元数据时间戳 != 数据时间戳,则创建 .meta 文件。content-type 时间戳是一个特殊情况,因为 - 借助此建议 - content-type 可以存在于 .data 文件或 .meta 文件中,因此需要显式地进行时间戳,而其他元数据可以视为一个整体)。
如果对象服务器已经有一个 t1.data 文件并收到数据时间戳 = t0 和 content-type(或元数据)时间戳=t2 (t0 附注 - 如果我们曾经支持可更新的对象 sysmeta(单独时间戳的 sysmeta),以及在 ssync 中进行复制期间,也可能会出现类似的问题(因此需要解决)。 我们可以通过将 t_meta 编码到现有的文本字段中来避免更改现有的对象表以添加 t_meta 的新列。 (a) 以 [type, t_meta] 的形式编码到 content-type 字段中。RFC 2616 [1] 规定 content-type 值不应包含方括号,从而可以轻松区分 JSON 编码的列表 [type, t_meta] 和简单的 content-type 值。这种方法将此快速 POST 建议的实现与存储策略的两向量时间戳分离。 或者 (b) 存储策略功能引入了两向量时间戳,以实现放置在不正确的策略中的对象的调解。时间戳现在采用 normal_offset 的形式,其中“normal”是原始时间戳的形式,而“offset”是“normal”时间戳的可选时间延迟。由于 t_meta 将始终大于数据时间戳,因此我们可以将其值存储在数据时间戳偏移量中(即 offset = t_meta - t_data)。这将需要比存储策略所需的更大的偏移量字段 - 当前偏移量为 16 位数字,以预期此目的,但如果此快速 POST 建议不需要,则可以减少位数。 时间戳偏移部分需要多少位数?
此建议正在推动增加时间戳偏移量的位数(偏移量 < 16 可能足以用于放置不正确的存储策略调解)。如果快速 POST 用例的时间戳偏移量无效或不需要,则应使用更紧凑的时间戳偏移量。 假设:1. 偏移量分辨率与正常时间戳相同,即 10usecs(这合理吗?) 2. 偏移量是 10usec 单位的十六进制编码整数 12 位数字将允许对象 PUT 和 content-type 更新之间大约 89 年的最大时间。13 位数字将允许对象 PUT 和 content-type 更新之间大约 1424 年的最大时间,这远远超过正常时间戳溢出的 272 年。16 位数字将允许对象 PUT 和 content-type 更新之间数千年。 [1] http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 和 http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2如何在容器数据库中存储 t_meta