)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"569ba778442e2147d2752014e54b7abcdaba115b","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"721dc538_8aa6f282","updated":"2026-06-03 05:47:26.000000000","message":"recheck\n\nopenstack.exceptions.ConflictException 409 The Stack (test_stack) already exists.","commit_id":"f9a15e3ac1c99e90b352259945c8b431bfd184f8"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"dcdd1222bbf4dcca41619601ff4a6f60f1cff01b","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"47091288_db4f2d34","updated":"2026-07-02 00:30:25.000000000","message":"OK so in this version we\u0027re just adding the tables, intialising and resizing as approriate. That\u0027s a nice small chuck to review.\n\nQuestion, wan\u0027t the last_parts_moved support to grow into the new ring like moves table?","commit_id":"1fca45f3a163b469a6987b2c55f9a178f4bd7ee3"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"97493a4af0f274891fa2709bb3dfa387b6e8a66f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"064a60ce_92f10cd0","in_reply_to":"47091288_db4f2d34","updated":"2026-09-03 09:51:05.000000000","message":"No, the two tables stay separate. `_last_part_moves` holds one value for each\npartition, in hours. It only applies `min_part_hours`. `_replica2part2last_moved`\nholds one value for each cell, and it counts rebalance cycles.","commit_id":"1fca45f3a163b469a6987b2c55f9a178f4bd7ee3"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"3185ef5e3884e703340b4b6e75a06c7555fd5162","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"741084ae_9aa4b954","updated":"2026-07-28 14:03:19.000000000","message":"Data structures, knobs, serialization, and normalization are all correct. The 255 vs 254 upper bound is fixed in 991133 and is a non-issue for atomic deployment. Looks good to me!","commit_id":"e09510e0a38365d2269db9bc4f5f3e9784650a17"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"1b30b432d51c25546705a81d8503be6e2fdc54e9","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"67d10865_71ceceb3","updated":"2026-07-27 07:50:27.000000000","message":"Went back to the design doc, and re-loaded the plan into my head. got last_parts_moved and can_part_moved swapped in my memory, Yes cen_part_moved and history moved tables is number of min_part_hours (or rebalances) ago.\n\nNow that\u0027s it loaded up, let\u0027s work through them again!","commit_id":"e09510e0a38365d2269db9bc4f5f3e9784650a17"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"97493a4af0f274891fa2709bb3dfa387b6e8a66f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"38bf960d_305a5de6","in_reply_to":"67d10865_71ceceb3","updated":"2026-09-03 09:51:05.000000000","message":"Thanks - next patchset will squash the first four commits into one, so there is one single commit with the architecture.","commit_id":"e09510e0a38365d2269db9bc4f5f3e9784650a17"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"0a096c7c2465f470c14c819a7cc22f9ad467948e","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"47eb27d9_f16dbfa8","updated":"2026-09-18 07:19:44.000000000","message":"Pointing GPT 6 at this is did find a bug. Basically a fresh move can look much older when you reduce min_part_hours.\n\nHistory age uses two pieces of state:\n\n  - A per-assignment age, measured in whole min_part_hours cycles.\n  - A shared _history_epoch, from which the builder counts additional cycles.\n\nMoving an assignment resets its age to zero, but leaves that shared clock alone. Consequently, the clock can start counting from before the assignment moved. With an unchanged cycle length, the code accommodates this uncertainty by retaining history for an extra boundary.\n\nFor example:\n```\n\n   Time since initial placement    Action                                      History state\n  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   0 hours                         Initial rebalance with min_part_hours\u003d24    Shared epoch starts at 0\n  ──────────────────────────────  ──────────────────────────────────────────  ───────────────────────────────────────────────\n   47 hours                        Drain a device; 48 assignments move         Their ages reset to 0; epoch advances only to\n                                                                               hour 24\n  ──────────────────────────────  ──────────────────────────────────────────  ───────────────────────────────────────────────\n   47 hours + 1 minute             Change min_part_hours to 1 and rebalance    Epoch is still hour 24\n\n```\n\nThe age conversion initially leaves those fresh ages at zero:\n```\n  converted age \u003d 0 × 24 / 1 \u003d 0\n```\n\nThen _age_history_cells() uses the old shared epoch with the new one-hour cycle:\n```\n  additional cycles \u003d floor((47h 1m − 24h) / 1h) \u003d 23\n```\n\nSo a one-minute-old move becomes age 23, exceeds the seven-cycle retention limit, and loses its history.\n\nWhen min_part_hours changes we probably need to use the old min_part_hours to interpret the existing ages first. Maybe something like:\n\n```\n  old_period \u003d old_min_part_hours * 3600\n  new_period \u003d new_min_part_hours * 3600\n\n  minimum_elapsed \u003d max(\n      0,\n      (age - 1) * old_period + (now - old_epoch),\n  )\n  new_age \u003d min(255, minimum_elapsed // new_period)\n```","commit_id":"816a34b7da0942b9a1961ba0eb4c3d4503964f4e"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"b1ef9202d682fb0f5892510c7e3c58be27781790","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":4,"id":"5f29fc1b_907545a4","in_reply_to":"47eb27d9_f16dbfa8","updated":"2026-09-18 17:25:22.000000000","message":"@matt@oliver.net.au You\u0027re right - but this was fixed on PS5, and PS6 did not change it? Can you please recheck?","commit_id":"816a34b7da0942b9a1961ba0eb4c3d4503964f4e"}],"swift/common/ring/builder.py":[{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"dcdd1222bbf4dcca41619601ff4a6f60f1cff01b","unresolved":true,"context_lines":[{"line_number":282,"context_line":"            self._replica2part2dev \u003d builder._replica2part2dev"},{"line_number":283,"context_line":"            self._history_replica2part2dev \u003d getattr("},{"line_number":284,"context_line":"                builder, \u0027_history_replica2part2dev\u0027, None)"},{"line_number":285,"context_line":"            self._replica2part2last_moved \u003d getattr("},{"line_number":286,"context_line":"                builder, \u0027_replica2part2last_moved\u0027, None)"},{"line_number":287,"context_line":"            self.max_history_cycles \u003d getattr("},{"line_number":288,"context_line":"                builder, \u0027max_history_cycles\u0027, 7)"}],"source_content_type":"text/x-python","patch_set":2,"id":"434d7486_06f1047a","line":285,"range":{"start_line":285,"start_character":12,"end_line":285,"end_character":41},"updated":"2026-07-02 00:30:25.000000000","message":"Was this going to be replacing the _last_parts_moved? I thought it was basically that but per replica rather then per partition?","commit_id":"1fca45f3a163b469a6987b2c55f9a178f4bd7ee3"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"97493a4af0f274891fa2709bb3dfa387b6e8a66f","unresolved":false,"context_lines":[{"line_number":282,"context_line":"            self._replica2part2dev \u003d builder._replica2part2dev"},{"line_number":283,"context_line":"            self._history_replica2part2dev \u003d getattr("},{"line_number":284,"context_line":"                builder, \u0027_history_replica2part2dev\u0027, None)"},{"line_number":285,"context_line":"            self._replica2part2last_moved \u003d getattr("},{"line_number":286,"context_line":"                builder, \u0027_replica2part2last_moved\u0027, None)"},{"line_number":287,"context_line":"            self.max_history_cycles \u003d getattr("},{"line_number":288,"context_line":"                builder, \u0027max_history_cycles\u0027, 7)"}],"source_content_type":"text/x-python","patch_set":2,"id":"b2ed0352_52231733","line":285,"range":{"start_line":285,"start_character":12,"end_line":285,"end_character":41},"in_reply_to":"11813844_38177b33","updated":"2026-09-03 09:51:05.000000000","message":"Done","commit_id":"1fca45f3a163b469a6987b2c55f9a178f4bd7ee3"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"548f24e75ed36d2810e70d227e99a315136610a9","unresolved":true,"context_lines":[{"line_number":282,"context_line":"            self._replica2part2dev \u003d builder._replica2part2dev"},{"line_number":283,"context_line":"            self._history_replica2part2dev \u003d getattr("},{"line_number":284,"context_line":"                builder, \u0027_history_replica2part2dev\u0027, None)"},{"line_number":285,"context_line":"            self._replica2part2last_moved \u003d getattr("},{"line_number":286,"context_line":"                builder, \u0027_replica2part2last_moved\u0027, None)"},{"line_number":287,"context_line":"            self.max_history_cycles \u003d getattr("},{"line_number":288,"context_line":"                builder, \u0027max_history_cycles\u0027, 7)"}],"source_content_type":"text/x-python","patch_set":2,"id":"8abd9500_00f4d245","line":285,"range":{"start_line":285,"start_character":12,"end_line":285,"end_character":41},"in_reply_to":"434d7486_06f1047a","updated":"2026-07-24 09:24:47.000000000","message":"No, the two coexist. _last_part_moves is unchanged: per partition, in hours, only used to enforce min_part_hours.\n\n_replica2part2last_moved has the same shape as _replica2part2dev (per cell - replica x partition), not the flat per-partition shape of _last_part_moves. It  counts rebalance cycles, not hours, and is used for max_history_cycles / failure_grace_cycles in 991131 and 991132. So it\u0027s an addition, not a replacement.","commit_id":"1fca45f3a163b469a6987b2c55f9a178f4bd7ee3"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"d9a4910dc11f4ddb6cfc9d6d41f59f7483c70afb","unresolved":true,"context_lines":[{"line_number":282,"context_line":"            self._replica2part2dev \u003d builder._replica2part2dev"},{"line_number":283,"context_line":"            self._history_replica2part2dev \u003d getattr("},{"line_number":284,"context_line":"                builder, \u0027_history_replica2part2dev\u0027, None)"},{"line_number":285,"context_line":"            self._replica2part2last_moved \u003d getattr("},{"line_number":286,"context_line":"                builder, \u0027_replica2part2last_moved\u0027, None)"},{"line_number":287,"context_line":"            self.max_history_cycles \u003d getattr("},{"line_number":288,"context_line":"                builder, \u0027max_history_cycles\u0027, 7)"}],"source_content_type":"text/x-python","patch_set":2,"id":"11813844_38177b33","line":285,"range":{"start_line":285,"start_character":12,"end_line":285,"end_character":41},"in_reply_to":"8abd9500_00f4d245","updated":"2026-07-27 05:03:05.000000000","message":"oh ok, so \"how many rebalances ago did this particular replica of this partition\" move, not that it moved x many hours ago. Fair enough, I thouhght it was going to be the latter, kk, I guess I should work down (or up) the chain.","commit_id":"1fca45f3a163b469a6987b2c55f9a178f4bd7ee3"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"1b30b432d51c25546705a81d8503be6e2fdc54e9","unresolved":true,"context_lines":[{"line_number":125,"context_line":"        # Retention knobs. The age table is uint8, so a cap above 255 is"},{"line_number":126,"context_line":"        # unreachable."},{"line_number":127,"context_line":"        self.max_history_cycles \u003d 7"},{"line_number":128,"context_line":"        self.failure_grace_cycles \u003d 1"},{"line_number":129,"context_line":""},{"line_number":130,"context_line":"        # _last_part_moves is an array of unsigned bytes representing"},{"line_number":131,"context_line":"        # the number of hours since a given partition was last moved."}],"source_content_type":"text/x-python","patch_set":3,"id":"60ca6029_6f4685a9","line":128,"updated":"2026-07-27 07:50:27.000000000","message":"I assume `replica_history_overload` is something we\u0027ll deal with later, once we get a better feel for the initial version with these knobs?","commit_id":"e09510e0a38365d2269db9bc4f5f3e9784650a17"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"97493a4af0f274891fa2709bb3dfa387b6e8a66f","unresolved":false,"context_lines":[{"line_number":125,"context_line":"        # Retention knobs. The age table is uint8, so a cap above 255 is"},{"line_number":126,"context_line":"        # unreachable."},{"line_number":127,"context_line":"        self.max_history_cycles \u003d 7"},{"line_number":128,"context_line":"        self.failure_grace_cycles \u003d 1"},{"line_number":129,"context_line":""},{"line_number":130,"context_line":"        # _last_part_moves is an array of unsigned bytes representing"},{"line_number":131,"context_line":"        # the number of hours since a given partition was last moved."}],"source_content_type":"text/x-python","patch_set":3,"id":"09443d9f_53924066","line":128,"in_reply_to":"60ca6029_6f4685a9","updated":"2026-09-03 09:51:05.000000000","message":"Yes, not part of this series yet.","commit_id":"e09510e0a38365d2269db9bc4f5f3e9784650a17"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"0a096c7c2465f470c14c819a7cc22f9ad467948e","unresolved":true,"context_lines":[{"line_number":475,"context_line":"    def set_failure_grace_cycles(self, value):"},{"line_number":476,"context_line":"        self._validate_history_cycles("},{"line_number":477,"context_line":"            \u0027failure_grace_cycles\u0027, value, self.max_history_cycles)"},{"line_number":478,"context_line":"        self.failure_grace_cycles \u003d value"},{"line_number":479,"context_line":""},{"line_number":480,"context_line":"    def _normalize_history_state(self):"},{"line_number":481,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":4,"id":"d025dc8c_4c7cf202","line":478,"updated":"2026-09-18 07:19:44.000000000","message":"If these 2 set methods are suppose to be the only way to set these values (because of the validcation checks) I wonder if they should be properties rather then get methods?\n\nJust thinking out loud.","commit_id":"816a34b7da0942b9a1961ba0eb4c3d4503964f4e"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"b1ef9202d682fb0f5892510c7e3c58be27781790","unresolved":true,"context_lines":[{"line_number":475,"context_line":"    def set_failure_grace_cycles(self, value):"},{"line_number":476,"context_line":"        self._validate_history_cycles("},{"line_number":477,"context_line":"            \u0027failure_grace_cycles\u0027, value, self.max_history_cycles)"},{"line_number":478,"context_line":"        self.failure_grace_cycles \u003d value"},{"line_number":479,"context_line":""},{"line_number":480,"context_line":"    def _normalize_history_state(self):"},{"line_number":481,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":4,"id":"36c300c3_f0e49063","line":478,"in_reply_to":"d025dc8c_4c7cf202","updated":"2026-09-18 17:25:22.000000000","message":"That would be different to other CLI commands like `set_overload`, `set_replicas` and `change_min_part_hours`. I think it makes sense to keep this style?","commit_id":"816a34b7da0942b9a1961ba0eb4c3d4503964f4e"}]}
