When LIMIT 9 works but LIMIT 10 hangs
neon.tech
neon.tech
Once upon a time I encountered some small issue (not a bug) with Postgres. I was able to get to the very source of it in the C code, understand what's happening, and adjust my code accordingly. It took maybe an hour.
Upon doing that, I shuddered, as I remembered troubleshooting an issue (a real confirmed bug) inside Oracle 9. It was by poking in the dark, trying to trigger a condition that made a server process crash, then trying to help Oracle engineers reproduce that. It took a week.
The ability to do this has saved my bacon on more than one occasion. It's why (outside of work I do for an employer) I won't develop using libraries/frameworks that I don't have the source code to.
I’m a little surprised at the statement that ~10%+ performance on a transport protocol isn’t worth it. 1 million frames doesn’t seem like that much, and some workloads see these things scaled out as front end servers mostly managing stuff like websockets / JSON / utf nonsense. I’d argue if it’s deep in the kernel of execution and well abstracted in the higher level logic a cryptic bit fiddle with a comment, even perhaps with it’s more common equivalent in the comment, might be worth while.
That leads me to the ultimate question: what was the Pokémon table used for?
Well, depending on which contrast we're making, the performance difference on that one line is either 10ms (by which bit-twiddling is faster than Uint8Array.writeUint16BE) or 100ms (by which Uint8Array.writeUInt16BE is faster than DataView.setUint16) per million frames. I don't think that's going to be anything like 10% of the total CPU time per frame. Unless I've misunderstood your point here?
> That leads me to the ultimate question: what was the Pokémon table used for?
You know what, I just assumed it was a test data table, but I never asked!
90ns/100ns is a lot for this kind of operation, but that comes from JS' handling of numbers and not a badly optimised routine.
In a declarative language, random() would get executed once, so it'd be like "order by 4" (nod to xkcd)
Instead of Uint8Array take an ArrayBuffer and an offset and a length. There should instead be something like ArrayBufferView to take the offset and length. And Uint8Array just new with the view, and uint8Array.buffer just return the view. Nothing like missing the offset or length would happen again.
Besides this, missing a intermediate level in between is also a security issue. It means there isn't a way to pass a partial view to a ArrayBuffer safely to other function. Because typedArray.buffer always give you the full ArrayBuffer.
order by random()
Does that even make sense? Why would anyone ever need this? Better to order by a stored value and randomize the presentation order, IMO.Seems completely dependent on when random() gets evaluated. If it gets evaluated at every inspection of the row during the sort, I'm little amazed that it hangs some times.
Of course, this is a bad idea if you have a table with lots of rows, and you'd be better off rolling an N-sided dice M times, where N is the number of rows in the table.
This is the plan I get:
Limit (cost=56.39..56.41 rows=10 width=40)
-> Sort (cost=56.39..59.79 rows=1360 width=40)
Sort Key: (random())
-> Seq Scan on test (cost=0.00..27.00 rows=1360 width=40)
One should use "FROM table TABLESAMPLE SYSTEM (size)", this is the plan: Sample Scan on test (cost=0.00..5.36 rows=136 width=32)
Sampling: system ('10'::real)
You can also use different distribution method than "SYSTEM", and you can make it reproducible with a seed.https://www.postgresql.org/docs/current/sql-select.html#SQL-...
ORDER BY random() is a really un-optimized bad practice to get a random sample on PostgreSQL. If you use another SQL database, the best thing to do is to use UUIDv4 as primary key, and use "ORDER BY pk LIMIT size".
> The SYSTEM method does block-level sampling with each block having the specified chance of being selected; all rows in each selected block are returned.
What if I want a different random order tomorrow?
> What if I want a different random order tomorrow?
If your data is large, and you want different samples. You can always always "select * from pk > gen_random_uuid() order by pk limit 10"
You have a very small chance to get ffffffff-... and get zero elements.
This is not perfect, I admit, but ORDER BY random() is one of the most wasteful thing.
If getting random samples is very important, the best you can you is migrate to postgres and use TABLESAMPLE.
Er, no, it will assign values to the result set, which may not involve a full table scan.
Also, any case of "WHERE filter ORDER BY random() LIMIT n" means "I want a tiny sample from a broad filter". So it might not be a full table scan, but it will still assign value to a lot of rows.
And that has nothing to do with the ORDER BY random().
Just a side note: TABLESAMPLE is part of the SQL standard, and e.g. Oracle supports this as well.
I guess some projects need to randomly select a few rows from a table? I was thinking of the wikipedia random topic, but that's only one-at-a-time, not a set of records.
You can even use a repeatable seed with this method.
It is probably not very efficient, but semantically it is fairly straightforward. In fact I thought that "hang" was going to mean "took so long I thought it was hung" but that turned out not be the issue here.
Queries of this form are easilly parallelized. Most systems that support distributed query processing, for example, optimize ORDER/LIMIT n queries such that each worker returns just the top n rows from its partition. These top rows are then merged in a central worker and limited one last time to produce the final result.
Even so, if your system supports the TABLESAMPLE clause and offers a formulation that lets you specify your actual distribution of interest (TABLESAMPLE SYSTEM often does not), you're probably better off using it.
Algorithm A
Input: A population V of weighted items
Output: A WRS of size n
1: For each v[i] in V, u[i] = random(0, 1) and k[i] = u^(1/w[i])
2: Select the n items with the largest keys k[i] as a WRS
Instead of the more direct tanslation of `ORDER BY POW(RANDOM(), (1/weight)) DESC`, we use the convenient fact that logarithm is a monotone transform over the positive reals to take the log of the ORDER BY argument without changing the sort order: ORDER BY POW(RANDOM(), (1/weight)) DESC
=> ORDER BY LN(POW(RANDOM(), (1/weight))) DESC -- LN preserves order.
=> ORDER BY LN(RANDOM()) / weight DESC -- LN(x^a) = LN(x) * a.
=> ORDER BY -LN(RANDOM()) / weight -- Order by `x DESC` is same as by `-x`.
[1] Pavlos S Efraimidis and Paul G Spirakis. Weighted random sampling with a reservoir. Information Processing Letters, 97(5):181–185, 2006The world of Pokémon can be crazily complicated if you let it ;-).
> the use of DataView here was an optimization that, though intended to speed things up, actually slowed things down … and broke them utterly in the process.
That’s so clear dangerous from both a bug and security perspective.
No kidding.
The test I contributed is very specific to the frame fix I made, but I should probably go back and contribute more tests in send.js that test other lengths too.
Due to off by one error, the last byte of a message is never sent if the message is larger than the buffer or the packet size (or not sent until the next message arrives, for stream protocols)
Only happened with very large database reads so that the 1440 byte segments hit that spot. New manager and he started mocking me during team meetings on why I was so obsessed with these load tests succeeding and then was amazed at the 2 char fix (adding +1 to the read till length call).
I think the misunderstanding comes from the fact calling .write() on sockets is pretty expensive. Specifically, if you write a byte or two, then the OS might send a whole packet out onto the network with just those two bytes.
If you did that for every tiny websocket message, then you would be sending double the number of packets necessary, with matching CPU overhead. Double the chance of network losses causing head of line blocking too.
So... Calling .write() should be done for large buffers and as rarely as possible.
However, in this case, .writeUInt16BE() doesn't send anything onto the network, but presumably the original author has been burned by .write() being expensive before...
If they're gonna optimize further I think they may as well go all the way through. This'll be pretty readable in the context of a binary serializer codebase
b[2] = value >>> 8;
b[3] = value & 0xff;Some software which converts some TCP connections to WebSocket didn't work because it didn't do the WebSocket framing (which is just prepending the size of each message) correctly.
The bug in question wasn't even well elaborated on as most of the article is spent on looking at what might be going wrong, even though basic unit testing of the framing for its various cases (WebSocket has different framing for different payload sizes) would have triggered the bug immediately.
Then it's just a matter of tracing the response back from the call to the database backwards until I find the break.
To me, the root cause is “optional arguments are evil”. I don’t blame the undici people, I blame the API design of DataFrame that makes it easier for the dev to not think about these critical parameters that to do it.
Optional arguments are much more often than not the wrong API.
> Node 17 and below worked, Node 18 and above didn’t.
Given that the same queries worked fine with a different client, that would point to a client issue rather than a server issue.
> SELECT * FROM pokemon LIMIT [copy code button]
How does that work? Is a "chunk" in timescaledb split into "pages" and just streamed from S3?
So all extensions including Timescale work. Postgres does the heavy lifting
Is such a mode being considered for Neon? If not, will there be future adjustments in pricing?