Skip to content

[packages] Add repo tooling support for custom sharding #189278

Open

Description

@stuartmorgan-g

Per discussion in flutter/packages#12150, the material_ui tests are quite slow on (non-WASM) web, such that just material_ui + material_ui/example are too long to be a reasonable shard together (~40 mins).

Currently the way sharding works in flutter/packages is that the tool finds every package that needs to be run in a given PR—the problematic case is anything that runs all packages, such as a flutter/flutter -> flutter/packages roll—then takes the alphabetically ordered list and chops it into N equal count pieces. This has always been a suboptimal way to shard, but we've always gotten away with it because we've never had any single package that was this different in timing from everything else (it's about 10x other packages).

We'll need to decide how we want to implement shard control. Ideally I'd like to keep the property that things can rebalance on the fly (although that has been an issue for flake detection in the past, since tests run in completely different shards for PRs that only touch a small number of packages vs those that run all packages), and ideally it should be low maintenance (i.e., not needing to do a bunch of work when adding one new small package).

Something I'm considering is a file that we could feed in (so we'd have one for each test type that needed shard control) that would just be a yaml map of package name to a "test unit" cost, where the default value is 1, which would correspond to a typical test of that type. For instance, for the web Dart unit tests in question, it looks like the baseline fast unit test time is about 30 seconds, so we'd call that a unit. material_ui takes about 26 minutes, so we'd call that 52 units. material_ui/example/ is about 16 minutes, so we'd call it 32 units. We'd only need to list the slow packages in the file, and everything else would be assumed one (although a bunch are zero because they are skipped after sharding; we would need to see if that ends up being an issue or not).

Then we add up all the units, divide that to get a target number of units per shard, and then implement a simple greed algorithm that would probably be good enough since we don't have to balance perfectly. Probably one of:

  • Iteratively take the largest-cost package and put in in the currently-emptiest shard.
    • likely gets the best balance, but will make it very hard to guess which shard a test is in compared to the current alphabetical approach.
  • Take the next package alphabetically that doesn't go over the target cost (probably with a tolerance of, say 10%).
    • likely less smooth, but would keep things mostly alphabetical.

If we go this route, we can try some experiments once we set up cost files, and see how the options handle our current needs in practice.

Activity

  1. added
    packageflutter/packages repository. See also p: labels.
    fyi-frameworkFor the attention of Framework team
    p: toolingAffects the flutter_plugin_tools package
    P1High-priority issues at the top of the work list
    on Jul 10, 2026
  2. stuartmorgan-g commented on Jul 13, 2026

    @stuartmorgan-g
    ContributorAuthor

    Hm, there's a deeper problem here which is that we shard at the top-level package level, and then expand to sub-packages later for the tests that need that. But in this case, the two things we most need to separate are the same top-level package. This is going to require more thought.

  3. stuartmorgan-g commented on Jul 13, 2026

    @stuartmorgan-g
    ContributorAuthor

    I just remembered the other problem, which is that as our CI is currently set up we need sharding to be consistent across different kinds of tests. That's because for things like https://github.com/flutter/packages/blob/ad2eab1b9fa3901d43b2383272ffd43ddb0b55cb/.ci/targets/ios_platform_tests.yaml we want build examples, native analyze, and native test to all run on the same packages so we can benefit from build caches. If we size packages differently in different tests, each step will run on different packages, and it'll be a mess.

    So if we do something with weighting, we would need to guarantee that:

    • every call in the same task target used the same weighting file, and the weights would need to be a sort of average of overall weights for all the related tests, and
    • every command would need to expand all subpackages and then skip those that don't apply, rather than conditionally inflating subpackages by command, so all commands in a shard agree about what the set of things to shard over.
  4. removed their assignment
    on Aug 24, 2026
  5. self-assigned this
    on Aug 28, 2026
  6. elliette commented on Sep 4, 2026

    @elliette
    Member

    Status update here:

    Adding a new shardCost argument to package_sharding (as attempted in flutter/packages#12715, see specifically here) was not sufficient - the CI targets with the new shardCost argument all failed to run in CI with the following error:

    {
      "error": {
        "code": 3,
        "message": "generating build from request: builder \"flutter/try/Linux_web web_dart_unit_test_shard_1 master\": property \"package_sharding\": invalid property override"
      }
    }

    Getting this to work requires:

    1. updating the LUCI recipe to accept shardCost, which was done in https://flutter-review.googlesource.com/c/recipes/+/82680
    2. add package_sharding to the allowed overridable properties in the LUCI service config here: https://flutter.googlesource.com/infra/+/refs/heads/main/config/lib/common.star#13

    Chatted with @eyebrowsoffire about this: it is much preferred to find a solution that does not require 2 above (adding package_sharding to the overridable properties). Ideally we need a solution that doesn't involve updating the LUCI config every time we need to change how our CI targets behave.

    [NOTE] Why is modifying the overridable properties even necessary?

    From my understanding, the LUCI service configs are generated from the ci.yaml on the main branch. It's a bit of a chicken-and-egg problem - if we landed flutter/packages#12715 on main, then the LUCI service config would get updated to recognize the new shardCost argument. But obviously we shouldn't land it until we can confirm it works, but we can't confirm it works because the LUCI service config doesn't know about the shardCost argument. Adding package_sharding to the overrides list basically tells LUCI "it's okay if the properties here don't match what is in the config", and therefore would let us run the modified CI targets in flutter/packages#12715 before merging. But as stated above, we should avoid touching flutter/infra if possible.

    For now, I'm going to revert https://flutter-review.googlesource.com/c/recipes/+/82680. Chatted with @Piinks about this, and we're going to workaround this by reducing our web testing suite for material_ui and cupertino_ui (issue links to come).

    However, I would like to keep this open because I do think it would be a good to have a longterm solution here for how we can better shard our tests. One idea from @eyebrowsoffire :

    "Having something like package_sharding be an actual LUCI property isn't actually necessary for how we're using it. LUCI itself doesn't care, the recipe ends up putting into an environment variable or something. But maybe it would be useful to have some sort of generic mechanism for "test configuration properties" that you can specify in the .ci.yaml. They wouldn't actually be LUCI properties, but our recipes (or maybe the scripts that run the tests?) could actually just parse the .ci.yaml and grab this set of properties and make them available to whatever test harnesses we have.It just seems like people will probably want a way to be able to add some specific data to their tests that they specify in the .ci.yaml, and I think we can probably accommodate that, we just need to use a different mechanism than LUCI properties that actually get passed when the buildbucket/led call happens"

  7. added
    P2Important issues not at the top of the work list
    and removed
    P1High-priority issues at the top of the work list
    on Sep 4, 2026
  8. removed their assignment
    on Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listp: toolingAffects the flutter_plugin_tools packagepackageflutter/packages repository. See also p: labels.team-ecosystemOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions