Deterministic tree ordering — the actual fix for the 'flaky ancestry' failures
Reproduced the reported ~50% intermittent ancestry failure (3-4 of 8 runs) and captured it: NamespacedDirectory.roots returned [dir4, dir1] vs [dir1, dir4]. The session report blamed 'several dependent saves that occasionally race' — wrong. Both records share position 0 and run_tree ordered by position alone, so the tie order was whatever the index scan produced that run. No race, no CAS involvement: an ORDER BY tie. - run_tree now tie-breaks by created_at then META(c).id, exactly like run_docs already did for finder queries. - The two client-side tree sorts (descendants, build_tree) get the same tie-break: Ruby's sort_by is not stable, so [depth, position] alone could reorder same-position siblings between runs. ancestry_spec re-run repeatedly post-fix: no failures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9ce5aea23d
commit
37288db487
2 files changed
+11
-3
No files matched your search
@@ -68,8 +68,11 @@ module SimplyBased
|
|||||||
return @descendants if @descendants
|
return @descendants if @descendants
|
||||||
return @descendants = [] if id.blank?
|
return @descendants = [] if id.blank?
|
||||||
prop = self.class.ancestry_by_property
|
prop = self.class.ancestry_by_property
|
||||||
|
# created_at/id tie-breaks: records habitually share position 0 and
|
||||||
|
# Ruby's sort_by is not stable, so [depth, position] alone reorders
|
||||||
|
# siblings between runs.
|
||||||
@descendants = self.class.database.tree_descendants(self.class, id,
|
@descendants = self.class.database.tree_descendants(self.class, id,
|
||||||
by_property: prop, by_value: (prop && send(prop))).sort_by { |d| [d.path_ids.size, d.position] }
|
by_property: prop, by_value: (prop && send(prop))).sort_by { |d| [d.path_ids.size, d.position, d.created_at.to_s, d.id.to_s] }
|
||||||
@children = self.class.build_tree(@descendants)
|
@children = self.class.build_tree(@descendants)
|
||||||
@descendants
|
@descendants
|
||||||
end
|
end
|
||||||
@@ -254,7 +257,7 @@ module SimplyBased
|
|||||||
tree_wrapper = Struct.new(:children).new([]) # Dummy container as traversing begin, contains roots as children
|
tree_wrapper = Struct.new(:children).new([]) # Dummy container as traversing begin, contains roots as children
|
||||||
old_tree_slice = tree_wrapper.children
|
old_tree_slice = tree_wrapper.children
|
||||||
new_tree_slice = []
|
new_tree_slice = []
|
||||||
pages.sort_by!{|p| [p.path_ids.size, p.position]}
|
pages.sort_by!{|p| [p.path_ids.size, p.position, p.created_at.to_s, p.id.to_s]} # deterministic tie order (sort_by is unstable)
|
||||||
root_depth = pages.first.path_ids.size
|
root_depth = pages.first.path_ids.size
|
||||||
current_depth = root_depth + 1 # Start counting/swapping from one depth deeper than first one
|
current_depth = root_depth + 1 # Start counting/swapping from one depth deeper than first one
|
||||||
for page in pages
|
for page in pages
|
||||||
|
|||||||
@@ -428,7 +428,12 @@ module SimplyBased
|
|||||||
|
|
||||||
def run_tree(where, params, order_col)
|
def run_tree(where, params, order_col)
|
||||||
sql = +"SELECT c.*, META(c).id AS `__id`, META(c).cas AS `__cas` FROM #{keyspace} AS c WHERE #{where.join(' AND ')}"
|
sql = +"SELECT c.*, META(c).id AS `__id`, META(c).cas AS `__cas` FROM #{keyspace} AS c WHERE #{where.join(' AND ')}"
|
||||||
sql << " ORDER BY c.#{quote_ident(order_col)} ASC" if order_col
|
# Tie-break equal positions by creation time then id, like run_docs.
|
||||||
|
# Records habitually share position 0; ordering by position alone left
|
||||||
|
# the tie order to the index scan, which flips between runs — this was
|
||||||
|
# the actual cause of the "intermittently flaky ancestry" failures
|
||||||
|
# (misdiagnosed as a save/CAS race in the original session report).
|
||||||
|
sql << " ORDER BY c.#{quote_ident(order_col)} ASC, c.`created_at` ASC, META(c).id ASC" if order_col
|
||||||
query(sql, params).rows.to_a.map { |row| row_to_doc(row) }
|
query(sql, params).rows.to_a.map { |row| row_to_doc(row) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user