Read a result down its columns - #18
Merged
Merged
Conversation
`query` builds an object a row and a JavaScript value a cell, which is what a program reading a hundred rows wants and the wrong shape for a million. `conn.columnar(...)` runs the same statement and hands back the buffers instead. The buffers are the engine's own, moved rather than read: the pointer V8 is given is the pointer `zu::query::column` filled, and the allocation is freed when the typed array is collected. So a column of a million integers crosses the boundary as a pointer and a length. Over a million rows that is 38 ms against 243 for one integer column, 50 against 262 for strings, and 76 against 632 for three columns at once. Walking what came back costs the same either way, at about 14 ns a row, which is worth saying because it is where the win is not: V8 reads a property of a small object about as fast as an element of a typed array, and what it cannot do is make a million of those objects for nothing. Every column says what it is, so reading one is a switch on `type` rather than a series of tests for what is there. `values` carries everything of a fixed width, a string column has its bytes and its offsets, `validity` is one bit a row and is absent when nothing is null, `unit` says whether a cell counts days, nanoseconds or months, and `zone` is the offset a column of zoned times was written with. The layout is Arrow's, so `apache-arrow` wraps it without copying it, and the README prints the eleven lines that do it rather than shipping them: a client that hands out an Arrow object has to agree with one version of Arrow forever, and a client that hands out the bytes agrees with all of them. The recipe is run in the test suite, including the assertion that the array Arrow reads is the array the engine filled. Two things are not buffers and both are named by the type. A column of nodes, rels, paths, lists or records arrives as `items`, holding the values `query` would have made, and a column of nothing but nulls has a length and nothing else. A column that mixes two types is refused, naming the column and the row that did it. 26 tests in `test/columnar.test.mjs`, a bench in `bench/columnar.mjs`, and the last piece of the milestone item that has the TypeScript client reaching the Python one.
33 tasks
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.
querybuilds an object a row and a JavaScript value a cell, which is what a program reading a hundred rows wants and the wrong shape for a million.conn.columnar(...)runs the same statement and hands back the buffers instead.The buffers are the engine's own, moved rather than read: the pointer V8 is given is the pointer
zu::query::columnfilled, and the allocation is freed when the typed array is collected. So a column of a million integers crosses the boundary as a pointer and a length. Fromnpm run bench:columnarover a million rows:Walking what came back costs the same either way, at about 14 ns a row for a sum over the buffer and the same over the rows, which is in the README because it is where the win is not. V8 reads a property of a small object about as fast as an element of a typed array. What it cannot do is make a million of those objects for nothing, and that is the whole of the six to eight times above.
Every column says what it is, so reading one is a switch on
typerather than a series of tests for what is there.valuescarries everything of a fixed width, a string column hasdataandoffsets,validityis one bit a row and is null when nothing in the column is,unitsays whether a cell counts days, nanoseconds or months, andzoneis the minutes east of UTC a column of zoned times was written with.That layout is Arrow's, which is the point of it.
apache-arrowwraps a buffer of this shape without copying it, so a table is eleven lines and no dependency of this package, and the README prints them rather than shipping them: a client that hands out an Arrow object has to agree with one version of Arrow forever, and a client that hands out the bytes agrees with all of them. The recipe is run in the test suite, including the assertion thattable.getChild("id").data[0].valuesis the same array the engine filled.Two things are not buffers and both are named by the type rather than found out by looking. A column of nodes, rels, paths, lists or records has no fixed width cell, so it arrives as
items, holding the same valuesquerywould have made, and a column of nothing but nulls has a length and nothing else. A column that mixes two types is refused, naming the column and the row that did it.bigIntModesays nothing here, since a columnar read has one physical layout per type. It still decides what is insideitems, where this client is making objects anyway.26 tests in
test/columnar.test.mjs: every type and its buffer, the bit order of a boolean column and of a validity bitmap, a null row keeping its cell, a column of nulls, a result of no rows, a statement that projects nothing, the mixed column refusal, parameters, a signal, a closed connection, the Arrow recipe with and without nulls, the columns going straight back in as a registered frame, and a million rows read while the event loop keeps ticking.This is the last piece of the milestone item that has the TypeScript client reaching the Python one.