The State of Vacuum in Postgres
rhaas.blogspot.com
rhaas.blogspot.com
What version of postgres vacuum implementation are they derived from? Have they implemented or incorporated any of these improvements since their fork? (Obviously Redshift doesn't need the btree index ones since it uses distribution keys and zone maps.)
I ask because my experience is that Redshift vacuum implementation is awful on large heavily used data warehouses, and I wonder if there is any hope for improvement.
So the vacuuming of tables to recover disk space from deleted rows needs to be done manually.
There are 3 types of vacuuming in Redshift (DELETE ONLY, SORT, REINDEX). The vacuum SORT operation is done on tables that have a sort key. To the extent that a vacuum SORT is an expensive (high IO) operation, we recommend when possible, to avoid the need to vacuum by loading the rows in sort order. If you do that, you will not need to vacuum the table, and this is the optimal solution for very long tables.
When dealing with a very long table (>10b rows) that has a sort key, it’s useful to partition the table into multiple tables where the table names are modified to indicate the partition. This is useful when pruning the size of the table, you don’t have to run VACUUM DELETE ONLY.
The key is to vacuum on a regular basis, and keep your stats off below 10%. If you wait too long, you might end up in a place where you have to resize your cluster or do a deep copy.
One more thing: Run your vacuum jobs in the queue with the most memory.
Same wikipedia article you are quoting ;)
In these systems, a function that is very careful with resource allocation will almost never experience a significant slowdown due to the background processing, because they either pay no cost or an amortized cost. For instance, one collector I knew of would garbage collect up to 10 objects on every allocation. If large allocations are front loaded then a full cycle almost never happens. In Swift or C++ or Rust you pay for your allocations as soon as they go out of scope.
In the context of an MVCC system, running a long query causes considerably more versioned data to pile up. If part of the cost of retiring that data was payed at the end of the transaction, several things happen. One, other transactions are less impacted by the overhead. Two, you end up throttling the bad actors, which encourages the problem to be fixed, and in the meantime the high water mark for vacuuming is reduced.
A lot of the tools we have for dealing with down services also can deal with degraded ones. If for instance throttling votes per second keeps the system usable for everyone else then that has been done before and can be done again. But you have to do it before the database has a seizure, not after.
[edit: I’m talking about back pressure. This is not a new or crazy concept. Your incredulity and $5 will buy me a cup of coffee]
There's certainly room for improvement — The Fine Article wouldn't exist otherwise — and backpressure might be worth exploring as a means of improving things. If that backpressure causes the system to slow down in a way that adversely affects customer or end-user interactions, however, it will often just be turned off, mooting it — or making things worse.
I guess you could argue the tool is somehow "flawed" for needing maintenance. I'd counter with the invitation to show me tools that don't.
What's slow are stuff like java GC Eden management where deletes are an afterthought to what is a time-stamped LRU reference management server, and a lot more.
So, no. I don't think you can compare C++ with systems that run full fledged GCs and threat them in the same bullpark.
Isn't it possible to just use a data structure to index rows by transaction ids, and on each transaction commit efficiently find all rows that aren't visible to any transaction, and add them to a free list?
Seems kind of a bad design to rely on periodic full data scans.
This is not to say vacuum-like mechanisms are necessarily the best method but it was a common architectural idiom for database engines from the 1990s because it works well with sequential storage devices. Newer techniques tend to put resource recovery inline with the workload rather than outside of it but that has other disadvantages.
Indexing all rows in a database by transaction id would cause an extreme loss of throughput. That just creates a continuously mutated (and therefore locking) structure every thread constantly uses that is also paged to disk. And deletion of individual records is a problem for indexes generally.
For example, as I recall, Oracle actually writes new row data to the page the row lives in, overwriting the old data in place on commit. It also writes the modifying transaction ID to the row header. If a different transaction finds the row, it will see that the transaction ID doesn't match (it's newer than itself), which forces the database to go to the undo log to look for the older data.
I'm not privy to the technical details of how the undo log is implemented or how it avoids the vacuum problem. I suspect it's related to the fact that the undo log is separate from the tuple data, so undo bloat doesn't affect table performance to the same extent as with Postgres.
In this case, I'd want to think carefully about the data volume: many places may have hundreds of transactions per second but also have queries which run for much longer periods of time and the visible state has to be accurate for all of them. That sounds a lot of contention for that shared data structure and “efficiently find all rows that aren't visible to any transaction” sounds decidedly non-trivial for busy servers with lots of data, which are generally the only ones where this matters.
Vacuum actually works by looking for tuples where no running transactions can see them anymore. Postgres, in effect, maintains a minimum and maximum transaction ID that any tuple is visible for, and vacuum scans over all of those that have a max visible transaction ID (suggesting it's available to be reclaimed) and which is less than the minimum active transaction ID.
Assuming active transactions {T}_i ordered by id, then if you are committing T_i, any row whose [Tmin, Tmax] visibility interval is fully contained in (T_(i-1), T_(i+1)) (i.e. for which Tmin > T_(i-1) && T_(i+1) < Tmax) is now dead (taking -inf and a large value for the previous/next transaction ids if there are none).
I believe this sort of query can be efficiently handled in time O(k polylog n) with several data structures, like an interval tree such as a B-tree augmented with maximum values or several kinds of 2D search trees.
It's also possible to use a simple index and just reclaim those rows whose Tmax is lower than the oldest active transaction, although this means that a single never-closed transaction blocks all row reclamation forever, which seems a bad design for a production-quality database.
Indexes in postgres point to the page, not the tuple. The VM keeps two bits per page: "does this page have any 'dead' tuples?", and "does this page need to be touched for xid wraparound?"
Vacuuming doesn't touch a page unless the VM indicates it's necessary.
Except for BRIN.
Note that it's not full scans - only pages that have been modified since the last vacuum, or were in a state last vacuum that they couldn't be processed, are vacuumed again. So there essentially is a block-level index for this.
https://www.postgresql.org/docs/current/static/storage-vm.ht...
It has potential pitfalls too though. On a system with very high mutation rate, purging could fall behind. This mainly would happen if the history list overflows available buffer pool space and starts getting paged to disk (e.g. from one very long lived transection). I don't know if newer MySQL versions make purge running off disk more efficient, but 5.5 added a config for multiple purge threads.
Many things can seem this way from the armchair, but you can lend some benefit of the doubt. Postgres is worked on by very smart people and it's not the only system that works this way
Everything has tradeoffs.
Postgres already has the notion of creating and destroying transaction ids for each row version attached to that tuple (row version). That distributes your "structure" across the rows involved in it. Just because you delete a row version (whether by deleting that row, or by updating it — which in MVCC is synchronously deleting the old version and inserting a new one) doesn't mean that row version isn't still visible to other concurrent activity, specifically including read-only activity. You'd therefore have to write to this proposed structure on completing every read or write.
These weights and heuristics are either going to punish small databases of sabotage large ones, unless the factor in a predictor or how much there is to vacuum.
It is determined by the rate of churn.
Edit: This is above my technical paygrade so I'll let others be the judge of whether these are in fact related! I believed so, from my earlier readings about the issue. Here's an article from the same blog in the OP about the Uber migration: http://rhaas.blogspot.in/2016/08/ubers-move-away-from-postgr...
Edit, re: the parent's edit: Quoting that article, "Perhaps the thorniest problem which Uber raises is that of write amplification caused by secondary index updates." The word "vacuum" is mentioned exactly once, in the context of keeping up with index insertions (writes).
Writes in MySQL (using InnoDB) just touch the pkey index, to which all other indexes point, or something like that. (Can someone who knows MySQL innards better — I avoid it like it's contagious — please confirm or clarify?)
It’d have been more accurate to say they abandoned traditional relational databases entirely.
The parenthetical comment is not true. If no indexed columns are updated, and there's space on the same page, a so called "HOT" update is performed. Basically a redirection is inserted triggering index scans to follow the update chain to the newest version:
https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f...
It's explained in the blog the great-grandparent linked:
PostgreSQL has a write-amplification issue — especially when you index every column on a table. My point is: if you don't do that, it doesn't hurt.