5

I have a very simple update statement:

UPDATE W SET state='thing'
WHERE state NOT IN ('this','that') AND losttime < CURRENT_TIMESTAMP;

The table W only has 90 rows, though the losttime and state columns for each row are updated each about every ~10s seconds. There are indexes on state and losttime (as well as the primary index).

I'm noticing with large databases (i.e. the other tables have a lot of entries, not table W) over a period of time, the query gets slower and slower and slower. After running for 48 hours, I'm timing it by running it in the query window of PqAdminIII and it's taken 17 minutes to execute!

I have a similar query on another table that's showing the same problem:

UPDATE H SET release='1' 
WHERE a NOT IN (SELECT id from A WHERE state!='done') AND release!='1';

H does not have any indexes, but I have tried putting and removing an index on H(release) with no change in behaviour. This query, after the database has been up for 48 hours and the table H has ~100k rows, is taking 27 minutes. Postgres server will have a thread completely pegged (100% CPU utilization) for the duration of the query, so it doesn't look like there's any contention for network, disk, etc.

So in broad strokes, the behaviour I see is that my database runs as expected for about 5 minutes, then gradually everything grinds to a halt as basic maintenance-related UPDATE commands start to take longer and longer to run. By the second day, it's taking an hour to do a simple maintenance cycle (a handful of UPDATES) which were running ~100ms at the outset. It seems clear to me that the performance degrade is super-linear with the amount of information in the database -- maybe N^2 or some such.

Autovacuum is on using the defaults. I read through the manual (again) and didn't see anything that jumped out at me.

I'm scratching my head here. I don't see any bug fixes that seem relevant in 9.0.1 and 9.0.2 release notes. Can anyone help me to understand what is happening? Thanks, M

-x-x-x-x-

Okay, so I may have two problems here.

The first update appears to run fast now. Not sure what happened, so I'll proceed there with the assumption that I need to run VACUUM / ANALYZE or some combination more frequently -- say every minute or so. I would really like to know why autovacuum isn't doing this for me.

The second update continues to run slowly. The query plan suggests that indexes are not being used effectively and that there is a 80k*30k cross occurring, which could account for super-linear runtime that I seem to be observing. (Does everyone agree with this interpretation of the plan?)

I can convert the UPDATE to a SELECT:

SELECT * from H
where a not in (SELECT id from A where state='done') AND release!='1';

with a similar runtime (27 minutes).

If I don't trust the postgres optimizer and do this:

WITH r as (select id from A where state='done')
SELECT a from H 
JOIN on H.a=r.id 
WHERE H.released='0';

then the query runs in ~500ms.

How do I translate this knowledge back into an UPDATE that runs with acceptable speed? My attempt:

UPDATE H SET release='1'
FROM A
where A.state!='done' AND release!='1' AND A.id=H.a;

runs in about 140 seconds, which is faster, but still very very slow.

Where can I go from here?

-x-x-x-x-

VACUUM ANALYZE has been added as part of "routine maintenance" where the application will run it approximately once every minute or so independently of any autovacuum that is running.

Also, rewrote the second query to eliminate the known-to-be-slow NOT IN clause, replacing it with a "Left Anti-Semi Join" (huh?)

UPDATE H SET release='1' 
WHERE release='0' AND NOT EXISTS (SELECT * FROM A WHERE id=H.a AND state!='done');
Juan Mellado
  • 14,973
  • 5
  • 47
  • 54
Mayur Patel
  • 113
  • 1
  • 5
  • how about not updating the tables? just make a view on the info you need to obtain on W and H tables – Hao Dec 24 '10 at 23:54
  • please post the plans for both updates. – Quassnoi Dec 25 '10 at 00:40
  • @Hao: I'm confused by the suggestion. This isn't a read operation, I actually am modifying contents in each table. – Mayur Patel Dec 29 '10 at 13:57
  • @Quassnoi: The first UPDATE plan: "Seq Scan on W (cost=0.00..2.40 rows=1 width=52)" " Filter: ((state <> ALL ('{this,that}'::Wstate[])) AND (losttime < now()))" The second UPDATE plan: "Seq Scan on H (cost=2706.66..129428126.50 rows=83308 width=22)" " Filter: ((release <> 1::smallint) AND (NOT (SubPlan 1)))" " SubPlan 1" " -> Materialize (cost=2706.66..3735.35 rows=73969 width=4)" " -> Seq Scan on A (cost=0.00..2343.69 rows=73969 width=4)" " Filter: (state <> 'done'::Astate)" I'm confused by the seq scan on A: I have an index on 'state.' – Mayur Patel Dec 29 '10 at 14:05
  • please don't post code in the comments. Instead, update your own post. – Quassnoi Dec 29 '10 at 18:03

3 Answers3

2

PostgreSQL implements MVCC.

This means that each time you make an update, a new copy of row is created and the old one is marked as deleted (but is not physically deleted).

This slows down the queries.

You should run VACUUM on a timely basis.

PostgreSQL 8.4.4 runs autovacuum daemon to do this, but it may have some problems on your installation.

Does the situation improve when you run VACUUM manually?

Quassnoi
  • 413,100
  • 91
  • 616
  • 614
  • The manual says that VACUUM once a day should be sufficient. I'm updating rows in the W table with a frequency on the order of seconds. Autovacuum naptime is at the default of 1 minute. The math isn't adding up for me, unless autovacuum simply isn't running for some reason. It is set to run in the postgresql.conf – Mayur Patel Dec 24 '10 at 18:14
  • @Mayur: Interval between vacuums may differ depending on your load. `autovacuum_naptime` only defines the period the daemon wakes up and checks the need for vacuuming, it does not necessarily runs `VACUUM` once per minute. Do the queries return to normal speed after you do a `VACUUM` manually? – Quassnoi Dec 24 '10 at 18:16
  • Sorry, I think you must have edited your response after I replied. I'm still running a couple of other tests in the "corrupt" state before I try running a VACUUM. Because it takes a bit of time for the problem to be very apparent, I'm trying to do as much analysis now as I can. Will report back whether the VACUUM works. (But I will say that yesterday I ran one from pgAdminIII and did not see improved behaviour.) – Mayur Patel Dec 24 '10 at 18:22
  • I ran VACUUM from the pqAdminIII query window. It took ~8s to run, but the second UPDATE I cited is still running after 4 minutes, so I think that's enough to say that it didn't help. If I need to run VACUUM FULL then it might be a deal-breaker, because this database needs to be highly-available. There will be no "downtime" where tables can be completely locked for long periods. (sigh) I'm happy to try other suggestions... – Mayur Patel Dec 24 '10 at 18:30
  • I ran a VACUUM FULL that took 18 minutes to execute. The second UPDATE command I cited is still running after 60s.... – Mayur Patel Dec 24 '10 at 18:58
  • VACUUM FULL should never be used. You might want to try CLUSTER on that table. But I'm pretty sure you need to make autovacuum more aggressive for that table. –  Dec 29 '10 at 21:34
  • @a_horse_with_no_name: why it is implemented then? :) – Quassnoi Dec 30 '10 at 06:11
  • @Quassnoi: the way it works was actually changed in 9.0 because of all the problems it had. I think it's mostly a historical reason. But if you follow the mailing list you will notice that people who know a lot more about PG than I do regularly discourage the usage of VACUUM FULL. –  Dec 30 '10 at 09:04
  • @a_horse_with_no_name: `VACUUM FULL` compresses the table and returns the free space to the tablespace. Plain `VACUUM` only marks the deleted records as free, which only makes them available for the current table (not any table). This does not shrink the table: it just makes it to grow no more. `CLUSTER` rebuild the physical order of the table pages according to the logical order of the index it clusters on. This operation also locks the table in exclusive mode and is even a little more time-consuming as the `VACUUM FULL` is (since the latter does not order). – Quassnoi Dec 30 '10 at 13:55
  • @a_horse_with_no_name: and from the docs: *Plain `VACUUM` may not be satisfactory when a table contains large numbers of dead row versions as a result of massive update or delete activity. If you have such a table and you need to reclaim the excess disk space it occupies, you will need to use `VACUUM FULL`, or alternatively `CLUSTER` or one of the table-rewriting variants of `ALTER TABLE`.* In other words, "should never be used" is a little bit of an overstatement. – Quassnoi Dec 30 '10 at 13:57
  • I'm going to mark this as answered, even though I still have some query optimization to do to make the second UPDATE more efficient. I think the "complete" answer is to do your own "VACUUM ANALYZE" and not trust the autovacuum to keep up with you. – Mayur Patel Dec 30 '10 at 13:58
  • @Quasssnoi: I understand how VACUUM FULL works, but it can lead to index bloat and is generally not recommended by the Postgres team. That's the reason why its internal working has completely been rewritten for 9.0 –  Dec 30 '10 at 13:59
  • @a_horse_with_no_name: `VACUUM FULL` can not lead to index bloat: it is designed to cope with index bloat :) – Quassnoi Dec 30 '10 at 14:04
  • @Quassnoi: Sorry, but you are mistaken. Prior to 9.0 it **does** cause index bloat. See this discussion in the Postgres Wiki: http://wiki.postgresql.org/wiki/VACUUM_FULL (one quote from there: *"Do not use it for table optimization or periodic maintenance, as it's counterproductive."*) –  Dec 30 '10 at 14:11
  • @a_horse_with_no_name: sorry, but I am not. It's not `VACUUM FULL` that causes index bloat: it is extensive `DML` which causes index bloat. `VACUUM FULL` does not shrink the indexes, but prevents them from growing larger (for indexes it behaves like plain `VACUUM`). The fact that is does not cure index bloat does not mean it causes it. As for the quote, databases in not the place where "proven by authority" works. – Quassnoi Dec 30 '10 at 14:28
  • @Quassnoi: I for one will believe what the Postgres team writes in the manual (and the Wiki). The 8.4 manual clearly states *"moving a row requires transiently making duplicate index entries for it (...); so moving a lot of rows this way causes severe index bloat"*. And moving rows is *exactly* what VACUUM FULL is doing. –  Dec 30 '10 at 14:53
  • @a_horse_with_no_name: none can take away your right to believe. However, it you read a little earlier, you will see that `VACUUM` marks dead rows in the indexes as available, and that's where the new versions of the index records pointing to the moved records go. In edge cases (ironically, this means tables which *almost* fill their space), the engine won't find enough recycled space to move the new record to and `VACUUM FULL` will allocate some new space indeed. But if the table contains many dead records, it's not a problem to find a place for the index records within the recycled space. – Quassnoi Dec 30 '10 at 15:09
  • @a_horse_with_no_name: this means that it's not necessary to do a `VACUUM FULL` on a regular basis (that's where you and documentation were right), but it's `OK` to do a `VACUUM FULL` to compact an already bloated table in-place. – Quassnoi Dec 30 '10 at 15:13
2

Check with pg_total_relation_size('tablename') whether your tables are bloated out of proportion. If that is the case, you may need to tweak the autovacuum configuration.

The other alternative is that the tables are locked. Look into pg_stat_activity or pg_locks to find out.

Peter Eisentraut
  • 35,221
  • 12
  • 85
  • 90
  • Yes, sounds like locked records. – Frank Heikens Dec 25 '10 at 10:26
  • pg_total_relation_size == 750 kB – Mayur Patel Dec 29 '10 at 13:32
  • Looked at pg_stat_activity and pg_locks views. Nothing jumped out at me, a couple entries in each view, but nothing that would make me think that massive numbers of rows remain locked. (I'm not sure why the CPU would peg on locked rows, I'd have expected CPU utilization to be very low in that case.) Is there something specific I should be looking at in these views? Thanks – Mayur Patel Dec 29 '10 at 13:52
1

I think you're not correctly closing transactions.

GolezTrol
  • 114,394
  • 18
  • 182
  • 210
  • I am accessing postgres through a python adapter, psycopg. I turn autocommit on when I create the connection because each of these updates can run autonomously, they don't need to be a composite atomic operation. Can you help me understand why you suspect a failure to close transactions? Why would that result in slow queries, and why would I not be getting errors at the time the cursor/connection is (potentially improperly) destroyed? The data in the database is valid, so I'm not seeing a failure there. More insight into your train of thought, please? – Mayur Patel Dec 24 '10 at 18:18
  • Well I'm not sure, but I've just recently seen performance decrease like this in a similar situation where not closing transactions actually was the problem. If you're sure all transactions are committed I must be wrong and it must be something else. – GolezTrol Dec 24 '10 at 19:03
  • I have had postgres server crash when I killed a client with an open connection, but other than that, the data looks "correct" in the database. I will keep an eye open for connection problems. Thanks. – Mayur Patel Dec 29 '10 at 14:09