OptiStore:2025 华为软挑赛——从标签感知到高性能读写的系统设计

在 2025 年的华为软件精英挑战赛中,我们面对的是一个分布式对象存储系统的性能优化问题。表面上,我们需要处理读、写、删三种操作,但赛题的核心很快就清晰了:最终得分几乎完全取决于读取(Read)性能

写入操作本身不直接计分,但它是我们唯一的优化手段。写入操作如何将逻辑对象映射到物理磁盘块,直接决定了读取时磁头的移动效率。删除操作则是最大的干扰,它会不断制造磁盘碎片,破坏精心设计的布局。

因此,我们团队的 OptiStore,其核心设计思想就是:一切写入策略,都为了最终的读取效率服务。我们的所有优化,都围绕着这一理念展开,即利用题目给出的标签信息,预测并布局数据,将随机IO转变为顺序IO。

OptiStore 硬盘标签图

写入优化

我们的写入策略是整个系统的核心,主要分为两大步:全局预处理和动态写入。

全局预处理 :划分冷热标签

在正式开始操作前,我们会收到所有时间片(1800个)的标签读写删总统计数据。preprocess_tag 函数的职责就是解析标签数据,对整个磁盘空间进行宏观的战略划分。

  1. 分析热度:我们首先读取标签的读写删数组,通过总写入 – 总删除估算出每个标签的净数据量,并通过总读取判断其热度。(后面发现这个判断是极其不准确的)。
  2. 排序与识别:我们根据总读取量对所有标签进行降序排序,识别出最关键的 hot_tag_cnt 个核心热标签。
  3. 划分热区:我们为这些核心热标签的前两个副本在所有N个磁盘上,以轮询的方式分配专属的、连续的热区。这些热区在磁盘上从头开始,每个标签占据最后剩余大小(总写 – 总删)的空间。
  4. 划分冷区与次热区:在热区分配完毕后,我们使用一个优先队列来管理所有磁盘的剩余空间。我们接着为非热标签的所有副本,以及热标签的最后一个副本分配空间。分配策略是总是从 current_space 中取出剩余空间最大的磁盘进行分配。

通过这一步,我们将每个磁盘都划分成了两部分:前半部分是高度连续、专用于高频读取的热区,后半部分是用于存放普通数据和副本的通用区。

实际上最后的标签分布(朋友队伍做的):

OptiStore 标签分布图

动态写入:多级优先级的空间分配

当收到一个写入请求时,我们面临两个问题:选哪个磁盘?放哪个位置? 我们设计了分离式的多级优先级策略来解决。

选择磁盘

此函数的目标是为对象的 3 个副本选出最佳的 3 个不同磁盘。

  • P1:写入热区。我们首先检查该对象的标签是否是热标签。如果是,我们直接查询 hot_tag_alloc,找到为其预留的磁盘。
  • P2:写入通用区尾部。如果 P1 未命中(例如非热标签,或热区已满),我们遍历所有磁盘,检查它们的通用区尾部是否有足够的连续空间。
  • P3:写入全局碎片区。如果尾部空间也不足,我们才作为最后手段,对磁盘进行全局扫描,寻找任何位置的可用碎片空间。

分配空间

当磁盘选定后,此函数负责在盘上找到具体的物理块。

  • P1:使用热区空间。如果当前磁盘是为该标签预留的热区,我们就在该热区内寻找连续空间。
  • P2:使用通用区尾部空间。如果 P1 未命中,我们尝试在通用区尾部(从 di[disk_id].end_point 开始)进行正向分配
  • P3:使用磁盘末尾空间反向分配。我们从磁盘的物理末尾开始,向前寻找连续空间。这就在通用区内创造了两个写入头:一个从 end_point 向后增长,一个从 V 向前增长。这使得磁盘两端都是高顺序性的数据,而碎片被挤压到磁盘的中间地带,极大地保护了顺序性。

副本反转

write_action 中调用 allocate_contiguous_blocks 时,我们传入了一个参数 j & 1。在 save_block 逻辑中,如果 reverse_blocks 为 true,我们会将块编号(disk_block_id)反向存入(size - j),并在最后 std::reverse(blocks.begin(), blocks.end())

这为读取操作提供了双向扫描的能力。如果磁头在对象 A 的前面,它可以正向读取副本 2 或 3。如果磁头在对象 A 的后面,它可以选择正向(连续)读取副本 1(因为副本 1 的物理块序是反的),而无需执行一次昂贵的跨对象 jump 寻道。

读取优化

读取的核心是dp_planjump_decision

动态规划路径

读取操作的代价规则是:连续读代价递减(预处理好的 cost[] = {0, 64, 52, 42, ...})。我们的写入策略创造了大量连续块,dp_plan 根据代价计算能够得到的最大化分数,也就是 dp[i][j] 表示消耗 i 个 Token、且最后一操作是连续第 j 次读时,所能达到的最小化 Token 数量。可以在 G 个 Token 的预算内,计算出一个由 r (Read) 和 p (Pass) 组成的最佳操作序列。

磁头调度

DP 来规划当前的最大价值, jump_decision 可以规划下一个周期内磁头的位置。

  • 就近移动:一个块有价值,当且仅当它属于一个当前有活跃读请求的对象。从磁头当前位置向前搜索 G * 2 / 3 的范围内,查找最近的有价值的块。这是磁头的首选目标。
  • 热点跳转:如果附近没有可读的块(ptr.first == -1),我们不能让磁头空转。此时扫描整个磁盘,找出当前磁盘上请求数最多的对象,然后 jump 到那个对象的位置。

删除操作

删除

删除能操作的空间并不大,逻辑很简单:

  1. 中止该对象所有未完成的读请求。
  2. 将对象在磁盘上占据的区域都清空。

垃圾回收

复赛新加的规则,当时摆烂没有特别优化好。

思路是将热区中的空洞与通用区中的热数据进行交换。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇