Skip to content

perf: Optimize TermSet for very large sets of terms. - #75

Merged
stuhood merged 7 commits into
mainfrom
stuhood.term-set-opt
Oct 25, 2025
Merged

perf: Optimize TermSet for very large sets of terms.#75
stuhood merged 7 commits into
mainfrom
stuhood.term-set-opt

Conversation

@stuhood

@stuhood stuhood commented Oct 24, 2025

Copy link
Copy Markdown
Collaborator
  • Removes allocation in a bunch of places
  • Removes sorting of terms if we're going to use the fast field execution method
  • Adds back the (accidentally dropped) cardinality threshold
  • Removes bool support -- using the posting lists is always more efficient for a bool, since there are at most two of them
  • More eagerly constructs the term HashSet so that it happens once, rather than once per segment

@stuhood
stuhood merged commit e9cc0da into main Oct 25, 2025
5 checks passed
@stuhood
stuhood deleted the stuhood.term-set-opt branch October 25, 2025 21:40
stuhood added a commit that referenced this pull request Oct 25, 2025
* Removes allocation in a bunch of places
* Removes sorting of terms if we're going to use the fast field execution method
* Adds back the (accidentally dropped) cardinality threshold
* Removes `bool` support -- using the posting lists is always more efficient for a `bool`, since there are at most two of them
* More eagerly constructs the term `HashSet` so that it happens once, rather than once per segment
stuhood added a commit to paradedb/paradedb that referenced this pull request Oct 26, 2025
## What

Further optimizes `pdb.term_set` and `paradedb.term_set`, and deprecates
using `pdb.term_set` in aggregate position.

## Why

#3351 optimized `term_set` queries for very large input term sets by
switching to using fast fields when more than `1024` terms were used in
the set. But there was more that could be done.

For a `paradedb.aggregate` query using a `pdb.term_set` constructed from
an `array_agg` containing 10mm distinct inputs and 8 segments, this PR
further optimizes the fast field execution path:

| version | runtime |
| ------- | -------- |
| pre-#3351 - 0 workers | 35.851 s |
| pre-#3351 - 8 workers | swapping - did not complete |
| #3351 - 0 workers | 12.573 s |
| #3351 - 8 workers | 13.708 s |
| #3412 - 0 workers | 5.532 s |
| #3412 - 8 workers | 8.538 s |

Before #3351, the posting-list based execution mode for term sets was
not able to complete on my machine with multiple workers, because it
required enough memory to trigger swapping.

Critical to note: in the case of a massive `pdb.term_set` like this,
additional workers might be a pessimization. That's because the cost of
propagating and creating the query is expensive enough that it can dwarf
the actual aggregate time. For larger segment counts or larger
aggregates, the results might be different.

Additionally, this change deprecates using `pdb.term_set` in aggregate
position (as added in #3336): using `pdb.term_set` in function position
with an `array_agg` is equivalent, and sometimes slightly faster.

## How

* Incorporates paradedb/tantivy#75
* Removes some allocation from `pdb.term_set` and `paradedb.term_set`
creation.
* Skips allocation `FieldName::path` arrays if they contain a single
component.
stuhood added a commit to paradedb/paradedb that referenced this pull request Oct 26, 2025
Further optimizes `pdb.term_set` and `paradedb.term_set`, and deprecates
using `pdb.term_set` in aggregate position.

switching to using fast fields when more than `1024` terms were used in
the set. But there was more that could be done.

For a `paradedb.aggregate` query using a `pdb.term_set` constructed from
an `array_agg` containing 10mm distinct inputs and 8 segments, this PR
further optimizes the fast field execution path:

| version | runtime |
| ------- | -------- |
| pre-#3351 - 0 workers | 35.851 s |
| pre-#3351 - 8 workers | swapping - did not complete |
| #3351 - 0 workers | 12.573 s |
| #3351 - 8 workers | 13.708 s |
| #3412 - 0 workers | 5.532 s |
| #3412 - 8 workers | 8.538 s |

Before #3351, the posting-list based execution mode for term sets was
not able to complete on my machine with multiple workers, because it
required enough memory to trigger swapping.

Critical to note: in the case of a massive `pdb.term_set` like this,
additional workers might be a pessimization. That's because the cost of
propagating and creating the query is expensive enough that it can dwarf
the actual aggregate time. For larger segment counts or larger
aggregates, the results might be different.

Additionally, this change deprecates using `pdb.term_set` in aggregate
position (as added in #3336): using `pdb.term_set` in function position
with an `array_agg` is equivalent, and sometimes slightly faster.

* Incorporates paradedb/tantivy#75
* Removes some allocation from `pdb.term_set` and `paradedb.term_set`
creation.
* Skips allocation `FieldName::path` arrays if they contain a single
component.
stuhood added a commit to paradedb/paradedb that referenced this pull request Oct 26, 2025
## What

Further optimizes `pdb.term_set` and `paradedb.term_set`, and deprecates
using `pdb.term_set` in aggregate position.

## Why

#3351 optimized `term_set` queries for very large input term sets by
switching to using fast fields when more than `1024` terms were used in
the set. But there was more that could be done.

For a `paradedb.aggregate` query using a `pdb.term_set` constructed from
an `array_agg` containing 10mm distinct inputs and 8 segments, this PR
further optimizes the fast field execution path:

| version | runtime |
| ------- | -------- |
| pre-#3351 - 0 workers | 35.851 s |
| pre-#3351 - 8 workers | swapping - did not complete |
| #3351 - 0 workers | 12.573 s |
| #3351 - 8 workers | 13.708 s |
| #3412 - 0 workers | 5.532 s |
| #3412 - 8 workers | 8.538 s |

Before #3351, the posting-list based execution mode for term sets was
not able to complete on my machine with multiple workers, because it
required enough memory to trigger swapping.

Critical to note: in the case of a massive `pdb.term_set` like this,
additional workers might be a pessimization. That's because the cost of
propagating and creating the query is expensive enough that it can dwarf
the actual aggregate time. For larger segment counts or larger
aggregates, the results might be different.

Additionally, this change deprecates using `pdb.term_set` in aggregate
position (as added in #3336): using `pdb.term_set` in function position
with an `array_agg` is equivalent, and sometimes slightly faster.

## How

* Incorporates paradedb/tantivy#75
* Removes some allocation from `pdb.term_set` and `paradedb.term_set`
creation.
* Skips allocation `FieldName::path` arrays if they contain a single
component.
stuhood added a commit to paradedb/paradedb that referenced this pull request Oct 27, 2025
## What

Further optimizes `pdb.term_set` and `paradedb.term_set`, and deprecates
using `pdb.term_set` in aggregate position.

## Why

#3351 optimized `term_set` queries for very large input term sets by
switching to using fast fields when more than `1024` terms were used in
the set. But there was more that could be done.

For a `paradedb.aggregate` query using a `pdb.term_set` constructed from
an `array_agg` containing 10mm distinct inputs and 8 segments, this PR
further optimizes the fast field execution path:

| version | runtime |
| ------- | -------- |
| pre-#3351 - 0 workers | 35.851 s |
| pre-#3351 - 8 workers | swapping - did not complete |
| #3351 - 0 workers | 12.573 s |
| #3351 - 8 workers | 13.708 s |
| #3412 - 0 workers | 5.532 s |
| #3412 - 8 workers | 8.538 s |

Before #3351, the posting-list based execution mode for term sets was
not able to complete on my machine with multiple workers, because it
required enough memory to trigger swapping.

Critical to note: in the case of a massive `pdb.term_set` like this,
additional workers might be a pessimization. That's because the cost of
propagating and creating the query is expensive enough that it can dwarf
the actual aggregate time. For larger segment counts or larger
aggregates, the results might be different.

Additionally, this change deprecates using `pdb.term_set` in aggregate
position (as added in #3336): using `pdb.term_set` in function position
with an `array_agg` is equivalent, and sometimes slightly faster.

## How

* Incorporates paradedb/tantivy#75
* Removes some allocation from `pdb.term_set` and `paradedb.term_set`
creation.
* Skips allocation `FieldName::path` arrays if they contain a single
component.
stuhood added a commit to paradedb/paradedb that referenced this pull request Oct 27, 2025
## What

Further optimizes `pdb.term_set` and `paradedb.term_set`, and deprecates
using `pdb.term_set` in aggregate position.

## Why

#3351 optimized `term_set` queries for very large input term sets by
switching to using fast fields when more than `1024` terms were used in
the set. But there was more that could be done.

For a `paradedb.aggregate` query using a `pdb.term_set` constructed from
an `array_agg` containing 10mm distinct inputs and 8 segments, this PR
further optimizes the fast field execution path:

| version | runtime |
| ------- | -------- |
| pre-#3351 - 0 workers | 35.851 s |
| pre-#3351 - 8 workers | swapping - did not complete |
| #3351 - 0 workers | 12.573 s |
| #3351 - 8 workers | 13.708 s |
| #3412 - 0 workers | 5.532 s |
| #3412 - 8 workers | 8.538 s |

Before #3351, the posting-list based execution mode for term sets was
not able to complete on my machine with multiple workers, because it
required enough memory to trigger swapping.

Critical to note: in the case of a massive `pdb.term_set` like this,
additional workers might be a pessimization. That's because the cost of
propagating and creating the query is expensive enough that it can dwarf
the actual aggregate time. For larger segment counts or larger
aggregates, the results might be different.

Additionally, this change deprecates using `pdb.term_set` in aggregate
position (as added in #3336): using `pdb.term_set` in function position
with an `array_agg` is equivalent, and sometimes slightly faster.

## How

* Incorporates paradedb/tantivy#75
* Removes some allocation from `pdb.term_set` and `paradedb.term_set`
creation.
* Skips allocation `FieldName::path` arrays if they contain a single
component.
stuhood added a commit to paradedb/paradedb that referenced this pull request Oct 27, 2025
## What

Further optimizes `pdb.term_set` and `paradedb.term_set`, and deprecates
using `pdb.term_set` in aggregate position.

## Why

#3351 optimized `term_set` queries for very large input term sets by
switching to using fast fields when more than `1024` terms were used in
the set. But there was more that could be done.

For a `paradedb.aggregate` query using a `pdb.term_set` constructed from
an `array_agg` containing 10mm distinct inputs and 8 segments, this PR
further optimizes the fast field execution path:

| version | runtime |
| ------- | -------- |
| pre-#3351 - 0 workers | 35.851 s |
| pre-#3351 - 8 workers | swapping - did not complete |
| #3351 - 0 workers | 12.573 s |
| #3351 - 8 workers | 13.708 s |
| #3412 - 0 workers | 5.532 s |
| #3412 - 8 workers | 8.538 s |

Before #3351, the posting-list based execution mode for term sets was
not able to complete on my machine with multiple workers, because it
required enough memory to trigger swapping.

Critical to note: in the case of a massive `pdb.term_set` like this,
additional workers might be a pessimization. That's because the cost of
propagating and creating the query is expensive enough that it can dwarf
the actual aggregate time. For larger segment counts or larger
aggregates, the results might be different.

Additionally, this change deprecates using `pdb.term_set` in aggregate
position (as added in #3336): using `pdb.term_set` in function position
with an `array_agg` is equivalent, and sometimes slightly faster.

## How

* Incorporates paradedb/tantivy#75
* Removes some allocation from `pdb.term_set` and `paradedb.term_set`
creation.
* Skips allocation `FieldName::path` arrays if they contain a single
component.
mdashti pushed a commit that referenced this pull request Dec 3, 2025
* Removes allocation in a bunch of places
* Removes sorting of terms if we're going to use the fast field execution method
* Adds back the (accidentally dropped) cardinality threshold
* Removes `bool` support -- using the posting lists is always more efficient for a `bool`, since there are at most two of them
* More eagerly constructs the term `HashSet` so that it happens once, rather than once per segment
stuhood added a commit that referenced this pull request Dec 3, 2025
* Removes allocation in a bunch of places
* Removes sorting of terms if we're going to use the fast field execution method
* Adds back the (accidentally dropped) cardinality threshold
* Removes `bool` support -- using the posting lists is always more efficient for a `bool`, since there are at most two of them
* More eagerly constructs the term `HashSet` so that it happens once, rather than once per segment
stuhood added a commit that referenced this pull request Dec 10, 2025
* Removes allocation in a bunch of places
* Removes sorting of terms if we're going to use the fast field execution method
* Adds back the (accidentally dropped) cardinality threshold
* Removes `bool` support -- using the posting lists is always more efficient for a `bool`, since there are at most two of them
* More eagerly constructs the term `HashSet` so that it happens once, rather than once per segment
stuhood added a commit that referenced this pull request Dec 10, 2025
* Removes allocation in a bunch of places
* Removes sorting of terms if we're going to use the fast field execution method
* Adds back the (accidentally dropped) cardinality threshold
* Removes `bool` support -- using the posting lists is always more efficient for a `bool`, since there are at most two of them
* More eagerly constructs the term `HashSet` so that it happens once, rather than once per segment
stuhood added a commit that referenced this pull request Jan 13, 2026
* perf: Implement a TermSet variant which uses fast fields (#69)
* fix: Add support for `bool` to the fast field `TermSet` implementation (#70)
* perf: Optimize `TermSet` for very large sets of terms. (#75)
stuhood added a commit that referenced this pull request Jan 13, 2026
* perf: Implement a TermSet variant which uses fast fields (#69)
* fix: Add support for `bool` to the fast field `TermSet` implementation (#70)
* perf: Optimize `TermSet` for very large sets of terms. (#75)
stuhood added a commit that referenced this pull request Jan 13, 2026
* perf: Implement a TermSet variant which uses fast fields (#69)
* fix: Add support for `bool` to the fast field `TermSet` implementation (#70)
* perf: Optimize `TermSet` for very large sets of terms. (#75)
stuhood added a commit that referenced this pull request Jan 14, 2026
* perf: Implement a TermSet variant which uses fast fields (#69)
* fix: Add support for `bool` to the fast field `TermSet` implementation (#70)
* perf: Optimize `TermSet` for very large sets of terms. (#75)
rebasedming pushed a commit that referenced this pull request Feb 10, 2026
* perf: Implement a TermSet variant which uses fast fields (#69)
* fix: Add support for `bool` to the fast field `TermSet` implementation (#70)
* perf: Optimize `TermSet` for very large sets of terms. (#75)
rebasedming pushed a commit that referenced this pull request Feb 10, 2026
* perf: Implement a TermSet variant which uses fast fields (#69)
* fix: Add support for `bool` to the fast field `TermSet` implementation (#70)
* perf: Optimize `TermSet` for very large sets of terms. (#75)
rebasedming pushed a commit that referenced this pull request Jun 22, 2026
* perf: Implement a TermSet variant which uses fast fields (#69)
* fix: Add support for `bool` to the fast field `TermSet` implementation (#70)
* perf: Optimize `TermSet` for very large sets of terms. (#75)

(cherry picked from commit 129f5b0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants