Page MenuHomePhabricator

Wish index results may not contain results in the requested language when they should
Closed, ResolvedPublic5 Estimated Story PointsBUG REPORT

Description

Problem

The main wish-index table (with all the wishes) is failing to get rows in the requested language. If you browse to Community Wishlist/Wishes and sort by vote count descending, you should see several rows at the bottom in Japanese. Click on them and you'll see there are English translations, yet we aren't showing them.

A simplified example production query:

SELECT cr_page, crt_title, cr_base_lang, crt_lang
FROM `communityrequests_entities`
JOIN `communityrequests_translations` ON ((crt_entity = cr_page))
WHERE cr_entity_type = 0
  AND (
    crt_lang = 'en' OR
    crt_lang = cr_base_lang
  )
ORDER BY
  crt_title DESC
LIMIT
  22;

This contains a result for W22 (in English, Make it easier for newcomers to create their first article), but instead we only got the Japanese translation 新規利用者が最初の記事を作成しやすくなるようにしてほしい – Japanese in this case being the base language of that wish.

What we're trying to do here is fetch all rows for the requested language along with those in its fallback chain, OR where the base language = requested language (i.e. there are no suitable translations). In this case, we are requesting English, which has no fallback languages. We want 10 rows of results, +1 more for pagination. So going by our logic, the LIMIT should be 22 – that is ( 10+1 ) * 2 (the 2 being there to account for the base lang rows). This unfortunately doesn't work.

Working theory

This perhaps has something to do with the order of the rows in the DB. We know a lot of those FuzzyBot jobs failed during the migration, so the translations didn't get saved until a day or so later when they were re-marked for translation. Still, we should write our queries to be resilient enough to handle this scenario.

Possible solutions

I was originally thinking we could just do two queries. First for the requested language and its fallbacks, and the other for crt_lang = cr_base_lang. It's a performance hit but it'd at least mean we know for certain we'll have the translations for the requested language. We could also do our processing after the first query, and if there are enough results, we don't even need the second one. The problem with that is we might lose a reliable order when we want to sort by title.

Taking that idea a step further, maybe the first one could only be against communtiyrequests_entities (where all the filters are applied), so that would dictate the definitive order, so long as the title isn't part of the sorting criteria. So then the second query only looks for translations in the requested language.

Other ideas are doing some sort of subquery or other more complicated means to get it all in one query and in a consistent order. That probably should involve verifying the EXPLAIN results don't look terrible.

QA Results - Meta Beta

ACStatusDetails
1T406680#11303897

Event Timeline

MusikAnimal changed the task status from Open to In Progress.
MusikAnimal triaged this task as High priority.

I'm not really sure what to do here and could use some help.

MusikAnimal changed the task status from In Progress to Open.Oct 8 2025, 5:48 AM

Ooh, maybe LEFT JOIN the entities table with translations, such that we get rows for all entities even if there are no translations for the requested lang. Then, we fill in the gaps with a second query for the base language translations. That should maybe work?

Change #1194364 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/extensions/CommunityRequests@master] ApiQueryWishesTest: Add test case that should pass

https://gerrit.wikimedia.org/r/1194364

I believe the above patch demonstrates the problem.

the translations didn't get saved until a day or so later when they were re-marked for translation.

Is the issue here that the translation rows aren't getting saved, or aren't being queried correctly? It sounds like the latter.

From your example above, you're saying that we're not getting the following two rows in the results?

cr_pagecrt_titlecr_base_langcrt_lang
12754604新規利用者が最初の記事を作成しやすくなるようにしてほしいjaja
12754604Make it easier for newcomers to create their first articlejaen

i.e. due to being ordered by title and then limited, the English row is not being included in the results.

Anyway, should we perhaps be doing one query per language in the fallback chain, e.g. something like this?:

( SELECT cr_page, cr_base_lang, crt_lang, crt_title
    FROM `communityrequests_entities`
    JOIN `communityrequests_translations` transbase ON crt_entity = cr_page AND transbase.crt_lang = cr_base_lang
    WHERE cr_entity_type = 0 )
UNION
( SELECT cr_page, cr_base_lang, crt_lang, crt_title
    FROM `communityrequests_entities`
    JOIN `communityrequests_translations` transbase ON crt_entity = cr_page AND transbase.crt_lang = 'pt'
    WHERE cr_entity_type = 0 )
UNION
( SELECT cr_page, cr_base_lang, crt_lang, crt_title
    FROM `communityrequests_entities`
    JOIN `communityrequests_translations` transbase ON crt_entity = cr_page AND transbase.crt_lang = 'pt-br'
    WHERE cr_entity_type = 0 )

ORDER BY crt_title desc
LIMIT 22;

Indeed, the data is there, it's just our query that's broken. A UNION'd query like that might work, yes! It didn't work in my testing. Running it on production, I got the same Japanese translation for W22 instead of the English one.

I've got a few other ideas, but I'm not claiming this task until I find something that works. Anyone else should feel free to grab it if they've got a solution in mind.

I'm thinking this might work:

SELECT cr_page, cr_base_lang, crt_lang, crt_title
FROM `communityrequests_entities`
JOIN `communityrequests_translations` ON crt_entity = cr_page
WHERE cr_entity_type = 0
  AND (
    crt_lang = 'en'
    OR (
      crt_lang = cr_base_lang
      AND NOT EXISTS (
        SELECT 1
        FROM `communityrequests_translations` transinner
        WHERE transinner.crt_lang = 'en'
          AND transinner.crt_entity = cr_page
      )
    )
  )
ORDER BY crt_title DESC
LIMIT 22;

It could probably be simplified, but initial tests look promising. Definitively, any wishes we get back that are not already in pt or pt-br do not exist in pt or pt-br. I find that reassuring.

EXPLAIN results:

stdClass Object
(
    [id] => 1
    [select_type] => PRIMARY
    [table] => communityrequests_translations
    [type] => index
    [possible_keys] => PRIMARY,crt_lang_title
    [key] => crt_lang_title
    [key_len] => 294
    [ref] => 
    [rows] => 1164
    [Extra] => Using where; Using index; Using filesort
)
stdClass Object
(
    [id] => 1
    [select_type] => PRIMARY
    [table] => communityrequests_entities
    [type] => eq_ref
    [possible_keys] => PRIMARY
    [key] => PRIMARY
    [key_len] => 4
    [ref] => metawiki.communityrequests_translations.crt_entity
    [rows] => 1
    [Extra] => Using where
)
stdClass Object
(
    [id] => 2
    [select_type] => MATERIALIZED
    [table] => transinner
    [type] => ref
    [possible_keys] => PRIMARY,crt_lang_title
    [key] => PRIMARY
    [key_len] => 37
    [ref] => const
    [rows] => 403
    [Extra] => Using where; Using index
)

versus the EXPLAIN of the original query in the task description:

stdClass Object
(
    [id] => 1
    [select_type] => SIMPLE
    [table] => communityrequests_translations
    [type] => index
    [possible_keys] => PRIMARY,crt_lang_title
    [key] => crt_lang_title
    [key_len] => 294
    [ref] => 
    [rows] => 1164
    [Extra] => Using index; Using filesort
)
stdClass Object
(
    [id] => 1
    [select_type] => SIMPLE
    [table] => communityrequests_entities
    [type] => eq_ref
    [possible_keys] => PRIMARY
    [key] => PRIMARY
    [key_len] => 4
    [ref] => metawiki.communityrequests_translations.crt_entity
    [rows] => 1
    [Extra] => Using where
)

So it looks like it wants to use the where condition before the crt_lang_title index, which is maybe not good. However we still scan the same number of rows in the first query plan. The new query also has the MATERIALIZED query plan which scans an additional 403 rows. That number will be smaller for languages with fewer translations. I.e. for 'pt', 'pt-br', 'en' it is only 37 rows.

Either way, it will be quite a while before these queries are ever slow, so maybe if nothing else this is acceptable as a stopgap measure?

What do we think?

MusikAnimal changed the subtype of this task from "Task" to "Bug Report".

I'm going to give T406680#11257469 a try.

I'm not 100% understanding how that will fetch the rows for intermediate fallback languages. But go for it!

I'm not 100% understanding how that will fetch the rows for intermediate fallback languages. But go for it!

It would be the same as above but instead of crt_lang = 'en' it'd be i.e. crt_lang IN ('pt', 'pt-br', 'en'), and with the LIMIT adjusted. We get the list of language codes before running the query.

Change #1194364 merged by jenkins-bot:

[mediawiki/extensions/CommunityRequests@master] AbstractWishlistStore: revise main query to ensure we get translations

https://gerrit.wikimedia.org/r/1194364

MusikAnimal changed the task status from Open to In Progress.Oct 14 2025, 5:56 AM
HMonroy changed the task status from In Progress to Open.Oct 14 2025, 4:25 PM
MusikAnimal set the point value for this task to 5.Oct 15 2025, 5:51 AM
GMikesell-WMF updated the task description. (Show Details)