• 
      

    Search: Add DisjointFacetEngine, FilterBuilder, FacetCache, and search groups

    Review Request #15052 — Created May 13, 2026 and updated

    Information

    Review Board
    release-8.x

    Reviewers

    Adds the core faceted search engine, the reviewboard/search/facets/ package,
    along with benchmark tooling for performance validation.

    Key components:
    - DisjointFacetEngine (aggregations.py) builds and executes a single
    Elasticsearch msearch body per request containing: a paginated results
    query, one aggregation sub-query per faceted=True FilterSpec (each
    excluding its own filter clause so bucket counts remain accurate when the
    filter is active — the disjoint property), and one option-count query per
    compound filter choice.
    - FilterSpec / FilterBuilder (schema.py, builder.py) provide a
    declarative schema for filter dimensions and translate active filter values
    into Elasticsearch bool.filter clauses. Compound filters (ship-it, issues,
    reviews, file attachments) use custom build_fn callables.
    - FacetCache (cache.py) caches aggregation results per group, query, and
    filter state for the lifetime of a request.
    - FacetedSearchEngine (engine.py) orchestrates searches across
    ReviewRequestSearchGroup, UserSearchGroup, and ReviewGroupSearchGroup.
    For the active group it runs a full search with aggregations; for inactive
    groups it runs a count-only query so sidebar tab totals are accurate.
    - ReviewRequestSearchGroup additionally handles the Contents filter, which
    restricts the text query to specific ES fields and optionally runs a parallel
    query against comment indexes to surface review requests with matching
    comments.
    - Also adds populate_bench_data management command and bench_manager.py for
    populating and benchmarking persistent datasets at 1k, 10k, 100k, and 1M
    scale across Postgres, MySQL, and MariaDB.

    Verified that DisjointFacetEngine builds the correct msearch body structure,
    enforces the disjoint property on aggregation sub-queries, and produces exactly
    one msearch call per search regardless of how many filters are active. Verified
    that FilterBuilder generates correct Elasticsearch clauses for each filter
    type and that the permission filter is always injected. Verified that
    FacetedSearchEngine routes full searches to the active group and count-only
    queries to inactive groups.

    Performance benchmarks were run at 10k, 100k, and 1M review requests on
    Postgres, MySQL, and MariaDB. A 6-filter faceted search against a 1M-document
    index completes in 4.7ms with latency remaining flat across all scales. Full
    results are in the attached benchmark report.

    Summary ID Author
    Search: Add DisjointFacetEngine, FilterBuilder, FacetCache, and search groups
    9d6264ed7f45b8ba2d3f6c134eee21960a005a21 DanielCasaresIglesias
    Description From Last Updated

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    'django.core.management.base.CommandError' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    missing whitespace after ':' Column: 23 Error code: E231

    reviewbot reviewbot

    line too long (81 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    multiple statements on one line (def) Column: 5 Error code: E704

    reviewbot reviewbot

    multiple spaces before keyword Column: 35 Error code: E272

    reviewbot reviewbot

    multiple statements on one line (def) Column: 5 Error code: E704

    reviewbot reviewbot

    multiple statements on one line (def) Column: 5 Error code: E704

    reviewbot reviewbot

    multiple spaces before keyword Column: 33 Error code: E272

    reviewbot reviewbot

    multiple statements on one line (def) Column: 5 Error code: E704

    reviewbot reviewbot

    multiple spaces before keyword Column: 34 Error code: E272

    reviewbot reviewbot

    multiple statements on one line (def) Column: 5 Error code: E704

    reviewbot reviewbot

    multiple spaces before keyword Column: 33 Error code: E272

    reviewbot reviewbot

    multiple statements on one line (def) Column: 5 Error code: E704

    reviewbot reviewbot

    multiple spaces before keyword Column: 34 Error code: E272

    reviewbot reviewbot

    line too long (83 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (81 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (81 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    f-string is missing placeholders Column: 32 Error code: F541

    reviewbot reviewbot

    f-string is missing placeholders Column: 14 Error code: F541

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    f-string is missing placeholders Column: 10 Error code: F541

    reviewbot reviewbot

    multiple spaces before operator Column: 27 Error code: E221

    reviewbot reviewbot

    multiple spaces before operator Column: 28 Error code: E221

    reviewbot reviewbot

    multiple spaces before operator Column: 26 Error code: E221

    reviewbot reviewbot

    multiple spaces before operator Column: 28 Error code: E221

    reviewbot reviewbot

    f-string is missing placeholders Column: 13 Error code: F541

    reviewbot reviewbot

    no newline at end of file Column: 32 Error code: W292

    reviewbot reviewbot

    'time' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    expected 1 blank line, found 0 Column: 5 Error code: E301

    reviewbot reviewbot

    expected 1 blank line, found 0 Column: 5 Error code: E301

    reviewbot reviewbot

    expected 1 blank line, found 0 Column: 5 Error code: E301

    reviewbot reviewbot

    expected 1 blank line, found 0 Column: 5 Error code: E301

    reviewbot reviewbot

    expected 1 blank line, found 0 Column: 5 Error code: E301

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    f-string is missing placeholders Column: 16 Error code: F541

    reviewbot reviewbot

    'datetime.timezone' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    continuation line over-indented for visual indent Column: 47 Error code: E127

    reviewbot reviewbot

    continuation line over-indented for visual indent Column: 47 Error code: E127

    reviewbot reviewbot

    line too long (81 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (82 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    'django.db.models' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (82 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    'reviewboard.search.facets.cache.FacetCache' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    line too long (83 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (81 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (82 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    blank line contains whitespace Column: 1 Error code: W293

    reviewbot reviewbot

    blank line contains whitespace Column: 1 Error code: W293

    reviewbot reviewbot

    blank line contains whitespace Column: 1 Error code: W293

    reviewbot reviewbot

    blank line contains whitespace Column: 1 Error code: W293

    reviewbot reviewbot

    blank line contains whitespace Column: 1 Error code: W293

    reviewbot reviewbot

    line too long (81 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    dan.casares
    Review request changed
    Commits:
    Summary ID Author
    Search: Add DisjointFacetEngine, FilterBuilder, FacetCache, and search groups
    5374b963609df18752e686d913433b2e1f1d8505 DanielCasaresIglesias
    Search: Add DisjointFacetEngine, FilterBuilder, FacetCache, and search groups
    442abf12ff7610466b2e5f13584e7eaff19619cf DanielCasaresIglesias

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    dan.casares
    Review request changed
    Commits:
    Summary ID Author
    Search: Add DisjointFacetEngine, FilterBuilder, FacetCache, and search groups
    442abf12ff7610466b2e5f13584e7eaff19619cf DanielCasaresIglesias
    Search: Add DisjointFacetEngine, FilterBuilder, FacetCache, and search groups
    f889b02773719e8e15e86308f86bffe086c342c8 DanielCasaresIglesias

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    dan.casares
    Review request changed
    Commits:
    Summary ID Author
    Search: Add DisjointFacetEngine, FilterBuilder, FacetCache, and search groups
    f889b02773719e8e15e86308f86bffe086c342c8 DanielCasaresIglesias
    Search: Add DisjointFacetEngine, FilterBuilder, FacetCache, and search groups
    9d6264ed7f45b8ba2d3f6c134eee21960a005a21 DanielCasaresIglesias

    Checks run (2 succeeded)

    flake8 passed.
    JSHint passed.