Symptom
spec/models/food_spec.rb fails intermittently in full-suite runs:
Failure/Error: expect(Food.send(:fts, query[:name], MAX_ROWS, user_food.user_id)).to eq [global_food, personal_food]
Measured rates on master (Ruby 3.2.3, Postgres 15):
- full suite: 1 failure in 20 runs
spec/models/food_spec.rb alone: 0 failures in 25 runs
Root cause
Food.fts orders by relevance with no tie-breaker:
https://github.com/rubyforgood/Flaredown/blob/master/backend/app/models/food.rb#L49
ORDER BY ts_rank_cd(f.searchable, to_tsquery(:lang, :query), 1) DESC
LIMIT :limit
The fixtures produce ties. Actual ranks for the query TestFood:* under the english configuration:
long_desc |
ts_rank_cd |
TestFood |
0.14426951 |
TestFood <word> |
0.09102392 |
TestFood <word> |
0.09102392 |
With LIMIT 2 and no secondary sort key, Postgres is free to return equally ranked rows in any order, and both examples in the fts block depend on that order:
"retrun local and global foods for author" (line 35) — the candidate set is global_food ("TestFood"), personal_food ("TestFood"), and the two "TestFood <word>" rows. global_food and personal_food have identical long_desc, so they tie at 0.14426951. The spec asserts the exact order [global_food, personal_food]. This is the example that has been failing.
"return same global foods" (line 26) — the candidate set is the three global rows. same_food_1 and same_food_2 tie at 0.09102392, and LIMIT 2 picks one of them arbitrarily, but the spec asserts same_food_1 specifically. Same latent problem, just not observed failing yet.
Running the file alone is stable because the tables are freshly truncated and the physical row order matches insertion order. In a full-suite run the earlier specs have left the heap in a different state, so tied rows can come back in a different order.
Not just a test problem
The missing tie-breaker also affects the deployed food search: for any query where several foods rank equally, the same search can return a different set (because of LIMIT) and a different order on each request. Adding a deterministic secondary sort fixes both the flake and the user-visible instability.
Suggested fix
Add a deterministic tie-breaker to the ORDER BY in fts_sql, e.g.
ORDER BY ts_rank_cd(f.searchable, to_tsquery(:lang, :query), 1) DESC, f.id ASC
and, if the fixtures are meant to exercise ranking rather than ordering, give the food records distinct long_desc values so the expectations do not depend on tie-break behaviour at all.
Notes
Found while verifying #875; unrelated to that change (0/20 full-suite failures on that branch, 1/20 on master). Filing separately so it is not fixed silently inside an unrelated PR.
Symptom
spec/models/food_spec.rbfails intermittently in full-suite runs:Measured rates on
master(Ruby 3.2.3, Postgres 15):spec/models/food_spec.rbalone: 0 failures in 25 runsRoot cause
Food.ftsorders by relevance with no tie-breaker:https://github.com/rubyforgood/Flaredown/blob/master/backend/app/models/food.rb#L49
The fixtures produce ties. Actual ranks for the query
TestFood:*under theenglishconfiguration:long_descts_rank_cdTestFoodTestFood <word>TestFood <word>With
LIMIT 2and no secondary sort key, Postgres is free to return equally ranked rows in any order, and both examples in theftsblock depend on that order:"retrun local and global foods for author"(line 35) — the candidate set isglobal_food("TestFood"),personal_food("TestFood"), and the two"TestFood <word>"rows.global_foodandpersonal_foodhave identicallong_desc, so they tie at 0.14426951. The spec asserts the exact order[global_food, personal_food]. This is the example that has been failing."return same global foods"(line 26) — the candidate set is the three global rows.same_food_1andsame_food_2tie at 0.09102392, andLIMIT 2picks one of them arbitrarily, but the spec assertssame_food_1specifically. Same latent problem, just not observed failing yet.Running the file alone is stable because the tables are freshly truncated and the physical row order matches insertion order. In a full-suite run the earlier specs have left the heap in a different state, so tied rows can come back in a different order.
Not just a test problem
The missing tie-breaker also affects the deployed food search: for any query where several foods rank equally, the same search can return a different set (because of
LIMIT) and a different order on each request. Adding a deterministic secondary sort fixes both the flake and the user-visible instability.Suggested fix
Add a deterministic tie-breaker to the
ORDER BYinfts_sql, e.g.and, if the fixtures are meant to exercise ranking rather than ordering, give the food records distinct
long_descvalues so the expectations do not depend on tie-break behaviour at all.Notes
Found while verifying #875; unrelated to that change (0/20 full-suite failures on that branch, 1/20 on
master). Filing separately so it is not fixed silently inside an unrelated PR.