7 ms·
The only scalable delete in Postgres is DROP TABLE
- flossly 3mo agoYears ago I heard Oracle's db had an edge on PG when it comes to DELETEs. I guess that's still the case...
- mike_hearn 3mo agoOracle has ALTER TABLE [name] MOVE ONLINE INCLUDING ROWS [predicates]. This is basically what you want - it keeps only the data matching the predicate, it's efficient and it works without disrupting the app. That might be the edge you heard about.
- asgeirn 3mo agoAlso ALTER TABLE DROP PARTITION
- smithcheck4 3mo agoI have been using TRUNCATE allm the way, and is fast as very good.
- xenator 3mo agoDeleting whole file is faster then deleting rows in file.
- foreigner 3mo agoSurprisingly to remove small numbers of rows in multiple tables (e.g. cleanup between automated tests), DELETE is often faster than TRUNCATE! It's counterintuitive but just measure it for yourself and see. Note you can DELETE from multiple tables in one statement using CTEs, and that way you don't need to think about foreign key dependency order.
- twotwotwo 3mo agoYears ago work was bit by the analogous thing in MySQL. Like it usually does, it took a chain of events: - We wrote a cronjob to periodically DELETE for a retention policy on a table we'd just created. Most senior person on the team reviewed it, looked fine. - Unusually for us, we prioritize QA'ing a different feature for release, delaying the release of this cronjob and a bunch of other code. - During that delay, the new table accumulated many times more rows to be deleted than we'd expected during review. - Release happens. All looks well since the initial delete wasn't a migration and cronjob hasn't run yet; engineer doing the release signs off. - Cronjob runs, deleting hundreds of millions of rows quickly. - Next day, replica lag's high and MySQL's transaction history is very high. MySQL keeps transaction history around until purge threads have visited all the affected pages on disk. - The bad cluster conditions last for days and lead to other problems. This omits detail and the 'noise' of everything else we were watching. But it gets across how the code and MySQL behaved. Like most exciting events, it led to multiple changes to avoid a repeat. For retention policies, our new approach was one at the end of PlanetScale's post, to partition and drop old partitions. Transitioning to this from a huge unpartitioned table can be fun! If a table is append-only and already huge, with lots of rows already past the retention threshold, you might only copy the rows to be kept to the new partitioned table: copy what you can, lock tables, do a last catch-up copy and swap tables. (Roughly the blog's 'performant one-off delete'.) If the table's merely kind of big, gh-ost or such could allow you to ALTER without causing lag, locking, etc. At a scale below that, you could run a slow incremental 'nibble' delete while watching server stats, and a step below that, plain ALTERs or DELETEs are fine. Using partitioning has fun bits, too. In MySQL, the partition key has to be part of any unique index, understandably. But you have to keep that in mind when you're using INSERT..ON DUPLICATE KEY UPDATE and relying on uniqueness to trigger the update. Things stay interesting! I hear Vitess shops like PlanetScale usually don't run multi-terabyte myqsld instances in the first place: even when physical nodes are big, they run many smaller mysqlds on them. That wouldn't make all this fully irrelevant--huge deletes would still sometimes be worse than copy-swap-drop--but it does seem real handy for taming issues that tend to worsen with mysqld size, like replication lag. All to say, little bit jelly of their setup over there!
- DarkUranium 3mo agoOne thing I did a while ago was to make deletes part of inserts, to amortize the cost. The main reason was to avoid a separate cron job, but it had other benefits (and downsides) too. Something like: DELETE FROM foo WHERE expires_at < now() LIMIT 10; INSERT INTO foo .....; Note the LIMIT: it ensures the latency stays under control even if we've suddenly hit 50k rows that need deleting. And by deleting (up to) 10 each time we insert one, it ensures obsolete things will eventually get deleted. Obviously, this isn't viable when the deletions must happen due to strict policy (e.g. legal compliance) since it can't ensure when things get deleted, just that they eventually do. IIRC, in my case, I used it for a password reset tokens table. There's no legal issue there and keeping expired ones around is fine as long as the code also checks `expires_at` to make sure it's still valid (which would be a good practice regardless, for defense in depth).
- silvestrov 3mo agoProblem is that PostgreSQL does not support LIMIT on the DELETE command. I have no idea why, it seems such an obvious feature for supporting large databases.
- DarkUranium 3mo agoRight, I must've forgotten. I know I had it working, but I don't have code at hand right now. I probably worked around it with a CTE or subquery then? Something like: WITH ids AS ( SELECT id FROM foo WHERE ... LIMIT 10 ) DELETE FROM foo USING ids WHERE foo.id=ids.id; Or: DELETE FROM foo WHERE id IN (SELECT id FROM foo WHERE ... LIMIT 10);
- inigyou 3mo agoThis website appears to be hard blocking my IP address (drops all packets( so I can't read the article.
- cowthulhu 3mo agoIMO, needing to clear out an entire table is an indicator that something has gone wrong with your design. Don't get me wrong, I've definitely done it before, but it's in the same bucket as VACUUM for me... high impact interventions used to fix a mistake I made, not "course of business" actions.
- baq 3mo agoYou should run vacuum as often as possible in Postgres if you’re doing anything other than INSERTs, this is a design tradeoff in Postgres itself. It’s the reason autovacuum exists and why tuning it is so important for performance; nothing wrong with doing a VACUUM ANALYZE after finishing a large DML batch job.
- TurdF3rguson 3mo agowhat does as often as possible mean? It will auto-vacuum when it's idle, right? Why not just let it do that?
- baq 3mo agoWhat do you mean by ‘idle’? If you mean your database is seeing extended periods of no updates to a table, you still want to vacuum, maybe even vacuum full if you know when traffic stopped and for how long to get the best possible read performance. If you have quiet periods in both reads and writes, enjoy the luxury of having an unused database to operate.
- deleted 3mo ago[deleted]
- saltcured 3mo agoDROP DATABASE, for when a bunch of calls to DROP TABLE seems like too much overhead...
- mike_hock 3mo agopg_dropcluster for when a bunch of calls to DROP DATABASE seems like too much overhead.
- ankitml 3mo agoThis is directionally correct approach. Deleting a large chunk of rows, in a large table does lead to unpredictable-bad behaviour for a while until those dead tuples are handled. I have used a very similar strategy by forking repack client https://github.com/reorg/pg_repack/pull/326 https://github.com/reorg/pg_repack/pull/326 This works out of the box with rds/cloudsql etc.
- buremba 3mo agoMaterialized tables are useful for time-series or sharding-like use-cases. You essentially offload the work to INSERT time to locate the data into relevant buckets/sub-tables that you can DROP later. We use materialized views for append-only timeseries data for https://lobu.ai https://lobu.ai and the retention policies define how we DROP the tables so we don't DELETE/UPDATE any rows in the tables. The long term storage is Iceberg on S3 that's ingested via Postgresql replication, suitable for OLAP use-cases. Postgresql only stores the dimensional OLTP data the users can update and the hot append-only event data.
- Syzygies 3mo agoI can't believe I'm the first to Rick roll this thread with the most famous XKCD comic of all time: https://xkcd.com/327/ https://xkcd.com/327/
- TurdF3rguson 3mo agoI can because this thread is not about sql injection.
- missingdays 3mo agoBut there's DROP TABLE in both the title and the comic, so it must be relevant
- znnajdla 3mo agoWhy are databases so hard?
- DarkUranium 3mo agoBecause of the guarantees they provide. Storing some data in a binary file isn't very hard. Making it so that you can do quick lookups on it (indexes) and implementing joins in a sane way is kinda hard, but easy compared to the real problem: Ensuring ACID (in the case of "traditional" databases). I.e. Atomicity, Consistency, Isolation, Durability. You need to protect against data corruption in the event of failure, all while guaranteeing atomic operations at the user level concurrently (in most production DBs; SQLite is a notable exception in that it fully serializes writes --- but it can get away with this because it's an embedded database with the primary use case of a single-process writer). And the entire thing must land on a known good state at the end of all of those concurrent transactions. ... and they must do it all while maintaining good performance, and sometimes on a combination of filesystem + hardware that's actively hostile towards the idea of data integrity (e.g. hidden RAM caches in disk or RAID controllers that don't flush on power loss --- thankfully, those are getting rarer, or so I've come to understand).
- insumanth 3mo agoThis mostly applies to almost any database. Deletes are Writes and Writes are resource intensive. This is more prominent on databases like Elastic Search. When I was tasked to delete millions of (old) documents, it overloaded the cluster and almost brought it down. Only scalable solution was to split the index and drop the whole index.
- elnerd 3mo agoI’m using Postgres for my DNS log service. I only store data for 90 days. To delete data, my strategy is to use partitions based on month. At the start of every month, I drop one partition. I am not sure of this is the best way to do this, but it works for me.
- rfsck4 3mo ago[dead]
- nicman23 3mo agofunnily enough i had a broken mysql / mariadb install that drop would not work at all - pinned a core to 100% - but delete would be instant in 1M rows lmao
- HackerThemAll 3mo agoIs that post written by people who think a degree in IT is a waste of time, or is it written for just those people? One day you'll maybe discover ACID properties of RDBMS systems, and all the puzzles will fall into place.
- lanycrost 3mo agobut partitioning not adding it's own overheads in DTO/Models side? and what about the performance versus just drops. If it's not for space saving you can just update and add delete marker or only do inserts and use the latest one.
- mugiseyebrows 3mo agoupdate users set deleted=1 where
- venkat971 3mo agoAssociating drop table to scalabe delete is not a good framing. These are two different things, and very sensitive on production workloads if there are FK and MVs involved. Databases has 'Trucate table' for a reason. The post header should have used 'Truncate table' instead of 'Drop table' framing.
- cknura 3mo ago[flagged]
- sgarland 3mo agoThe same is true to a lesser extent in MySQL / MariaDB. It does better since it doesn’t do oldest-to-newest tuple chains, but it’s still adding non-trivial work to the DB, much of which is effectively wasted if you don’t care about the visibility of the deleted (or soon-to-be deleted) tuples to other transactions. I sincerely hope that Planetscale’s efforts succeed long-term to shift devs’ understanding and acceptance of RDBMS operations. Their blog posts and docs are generally quite good. IME, devs (and even ops-ish teams) simply do not care about all of this, and will create elaborate bespoke tooling to run DELETEs in bulk, because they either don’t understand the capabilities of the database, or don’t want to deal with the [minor] increased complexity that a partitioned schema brings, and will happily pay the extra cost / latency for deletions.
- deleted 3mo ago[deleted]
- kro 3mo agomysql/maria also lets you turn off/down the isolation level for queries if you know the guarantees aren't needed, to speed things up. I think postgres does not have that option.
- alternbet25676 3mo agoPostgres does support changing the isolation level at query, session and config: https://www.postgresql.org/docs/current/sql-set-transaction.html https://www.postgresql.org/docs/current/sql-set-transaction....
- pstuart 3mo agoYep, partitions are the way to go there.
- awinter-py 3mo ago^ this been exploring clickhouse and while it is definitely not a general purpose DB, for time-series shaped data that can survive some insert latency, the automatic partition-based TTL is very nice and, at least so far, requires zero attention to maintain which I guess is solved by `pg_partman` at the bottom of the post
- crazygringo 3mo agoOnly by a weird definition of "scalable". The first sentence says: > Counterintuitively, large DELETEs add work to the database. There is nothing counterintuitive about this. It takes just as much work to delete a row as it takes to insert a row. Why wouldn't it? Obviously you have to do almost all the same operations: write a log, write the deletion, update indices, replicate it, etc. And yes, it's a well-known trick for all major relational databases (not just Postgres) that if you want to delete 90% of rows from a large table, it's much faster to just copy the rows you want to keep to a new table, run DROP TABLE on the old table, and rename the new table to the old table. Since DROP TABLE is ~instantaneous, mainly involving table-level metadata. DELETE scales just fine, in the sense that if you are constantly inserting and deleting individual rows, DELETE scales the same as INSERT. Basic database functionality is designed around the assumption of lots of small transactions. Whenever you have to do something involving millions of rows at once, you generally need to investigate solutions that work well in "bulk". E.g. loading rows directly from a file rather than with SQL, adding indices only after the data has been loaded rather than before, disabling foreign key checks on large operations (if you know by design that the keys are valid)... and yes, taking advantage of DROP TABLE instead of DELETE. This doesn't mean small transactions aren't scalable, it just means bulk operations are qualitatively different and benefit from their own solutions. And DELETE is no different from INSERT in this regard.
- setr 3mo ago> It takes just as much work to delete a row as it takes to insert a row. Why wouldn't it? Obviously you have to do almost all the same operations: write a log, write the deletion, update indices, replicate it, etc. It takes far more work to delete/update than insert. My recent example is updating ~2TB of text data was about 40x slower than inserting 12TB (was trying to correct some large text truncation that occurred during migration into PG, ended up being faster to redo).
- crazygringo 3mo ago> It takes far more work to delete/update than insert. Updating rows of text data is going to be more work, because variable-length text can't be updated in-place. So in terms of allocating space, it's more like a delete plus an insert. That's not surprising. (An in-place update that doesn't touch indices is generally going to be faster than an insert, though.) I'm not aware of instances where a delete is "far more work" than an equivalent insert though. That's not the general case, and I'm having a hard time thinking of any situations where that would be true.
- jandrewrogers 3mo agoThis generalizes to most (all?) databases. Selective deletion is largely an unsolved problem at scale in databases to the extent it doesn't release the deleted resources. Under the hood databases try to turn this into selective resource truncation, which scales much better, but in most cases that is not possible without careful design of your data model. Similarly, you often have to remind devs that in many databases an UPDATE is just an INSERT + DELETE, with all of the scaling issues implied.
- mike_hearn 3mo agoRocksDB and other LSM tree backed databases do have cheap deletes and updates, although you could argue that's because they make everything else expensive. If you have spare cores it can be a good trade though.
- levkk 3mo agoCRUD apps don't usually delete in bulk. It's also hard to structure partitions in a way that doesn't wipe out months of important business data -- this is why teams often ETL their DB into Snowflake/ClickHouse and only then drop partitions. That makes it hard for the app to use that data again. The better approach is either to change your storage engine (e.g. OrioleDB is working on adding the undo log to Pg), or to shard which distributes the vacuum load across multiple servers.
- sgarland 3mo agoThey should be performing bulk deletions, due to GDPR: “Data must be stored for the shortest time possible.” Unless you have some kind of rolling cron checking every few minutes (and even then, depending on your scale, that may well be considered bulk), that generally resolves to something like daily or weekly deletions.
- aftbit 3mo agoTIL about pg_partman https://github.com/pgpartman/pg_partman/blob/development/doc/pg_partman.md https://github.com/pgpartman/pg_partman/blob/development/doc...
- saisrirampur 3mo agoPartially true but too much of a blanket statement and clickbaity. DELETE with well-tuned autovacuum works pretty well. Have seen it work at TBs scale with no hicuups. If DELETEs are large, we used to recommend customers to follow that with a manual VACUUM for table to reclaim space right away for future rows. DROP TABLE can be risky, it requires an ACCESS EXCLUSIVE LOCK and if its waiting, it blocks all other statements following it, because of how lock queues work in Postgres. And you cannot keep doing high concurrent DROP TABLEs to run your large scale CRUD app.
- CharlieDigital 3mo ago> And you cannot keep doing high concurrent DROP TABLEs to run your large scale CRUD app In this kind of use case/design, I would assume it would make use of partitions to make this more palatable in which case it would seem that you would bypass this issue of "high concurrent DROP TABLE". Large scale CRUD app just points to recent-ish partitions. Old partitions are either going to be low or on access and can be dropped easily or transformed/transferred into some long term/cold storage.
- saisrirampur 3mo ago(Most) CRUD/OLTP applications don't delete data by timestamp; they delete by primary key. For those workloads, DROP TABLE (or dropping a partition) isn't a palatable option. The entire premise here is really about time-series workloads where most operations are based on a timestamp. In those apps partition dropping has been a standard and recommended retention strategy for years. That's precisely why extensions like pg_partman and TimescaleDB exist. Given that context, the title feels more clickbaity, and could easily mislead readers into thinking this applies broadly to OLTP systems when it doesn't;
- parthdesai 3mo ago> (Most) CRUD/OLTP applications don't delete data by timestamp; they delete by primary key. For those workloads, DROP TABLE (or dropping a partition) isn't a palatable option. UUID v7 to the rescue!