Repository navigation
perf: convert large Arrow results to rows in slices - #1039
Open
vuanhphung wants to merge 1 commit into
Open
vuanhphung wants to merge 1 commit into
vuanhphung wants to merge 1 commit into
Conversation
fetchall() and large fetchmany() converted the whole Arrow table with one to_pandas() call and one object ndarray, so a DataFrame, the object array and the final Row list were all alive at the peak. Convert 100,000 rows at a time instead. Columns whose pandas conversion depends on the column's other values (list/map/struct: pyarrow picks the element dtype from the whole column) are still converted over the whole table, so every value and type is unchanged. Signed-off-by: Vu Anh Phung <vu.phung@databricks.com> Co-authored-by: Isaac <no-reply@databricks.com>
vuanhphung
had a problem deploying
to
azure-prod
November 12, 2026 11:25 — with
GitHub Actions
Failure
vuanhphung
temporarily deployed
to
azure-prod
November 12, 2026 11:25 — with
GitHub Actions
Inactive
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What type of PR is this?
Description
fetchall()and largefetchmany()calls convert the Arrow result with oneto_pandas()call and oneDataFrame.to_numpy(dtype=object)over the whole table, so a DataFrame, a full object array and the finalRowlist are all alive at the peak. On a 5M-row mixed-type result (INT/BIGINT with NULLs, DOUBLE, STRING, DECIMAL, DATE, TIMESTAMP, BOOLEAN, ARRAY), conversion peaked at about +5.4 GB RSS, while the returned rows alone hold +3.85 GB. The process also kept about 0.9 GB more afterfetchall()returned.This converts 100,000 rows per
to_pandas()call. That is the defaultarraysize, sofetchone()and default-sizefetchmany()take the same single-call path as before. Columns whose pandas conversion depends on the column's other values are still converted once over the whole table. pyarrow picks the element dtype of a list or map from the whole column (one NULL element turns everyARRAY<BIGINT>value into a float64 array) and of a struct per Arrow chunk. Their values are written into each slice's object array over null placeholders. The placeholders keep the slice's DataFrame block layout the same as before, which matters because pandas < 2.2 returns durations differently from a single-block DataFrame. Returned values and Python types are unchanged.Related open PRs: #982 and #1007 call
_convert_arrow_tableunchanged and merge cleanly with this (#982 conflicts only in CHANGELOG, as it already does with main). #979 (NaN vs NULL) and #960 (nested conversion) edit the same function body. Whichever lands second needs a small rebase: #979's NaN restore moves intoto_object_array, since float columns are converted per slice, and #960's Arrow-native nested conversion replaces the whole-columnto_object_array(table_renamed.select(whole))call. This PR does not change either behavior.How is this tested?
New unit tests check that sliced conversion matches whole-table conversion value by value and type by type (nested, struct, duration, tz timestamps, decimals, duplicate column names). They also check that slices are bounded and that small or nested-only tables are not sliced. The test file passes on pandas 1.5.3/pyarrow 14, 2.2.3/18, 3.1.1/23 and 3.1.1/26. A separate script compared main and this branch on about 2,900 table layouts and slice sizes under the same four version pairs, and on live Thrift inline, CloudFetch and kernel results. Every value and type was identical. Slicing the nested columns as well is caught as a difference.
Performance was measured on the mixed-type query above (
FROM range(N), 9 columns), with a fresh process per run, main and this branch interleaved, median of 5. "Local" times_convert_arrow_tablealone on the same table read from an Arrow file. "Live" isexecute()+fetchall()against a SQL warehouse, with peak RSS measured above the pre-query baseline. p95 values are within 2% of the medians.fetchall()At 1M rows the peak drops 4–7% on every backend, with no change in time.
_disable_pandas=Trueis unaffected. Slice sizes from 50k to 1M rows gave the same wall time. With this change the peak is within 1% of what the returned rows hold, so what remains is theRowlist itself.This pull request and its description were written by Isaac.