Received: from maia.hub.org (maia-3.hub.org [200.46.204.243]) by mail.postgresql.org (Postfix) with ESMTP id 1602B1337B59 for ; Tue, 12 Oct 2010 11:23:40 -0300 (ADT) Received: from mail.postgresql.org ([200.46.204.86]) by maia.hub.org (mx1.hub.org [200.46.204.243]) (amavisd-maia, port 10024) with ESMTP id 12856-09 for ; Tue, 12 Oct 2010 14:23:32 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mail-gw0-f46.google.com (mail-gw0-f46.google.com [74.125.83.46]) by mail.postgresql.org (Postfix) with ESMTP id 257B21337B55 for ; Tue, 12 Oct 2010 11:23:31 -0300 (ADT) Received: by gwj17 with SMTP id 17so1426095gwj.19 for ; Tue, 12 Oct 2010 07:23:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:message-id:date:from :user-agent:mime-version:to:cc:subject:references:in-reply-to :content-type; bh=AzSxC7e2/YSo/Ljb4jv3hbVUFPTetAFC76fcOwG7QWk=; b=hEhxXFfu5RI+gQMOEN5tqLTpkY9KRITdJvBrYqaCpVqybw0iyP0gpwuZdlHWf/c0WI lDivEltF4Lnipsdn+DUhfOFqjyYJ+ZljK6xTL3LRAWRX8zMGxPTfPvcY6+5KShYsUzlu /hjvuOKs6L8Y2yMSMke0tw5hIhpWgWTWqfsqU= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=message-id:date:from:user-agent:mime-version:to:cc:subject :references:in-reply-to:content-type; b=nq6azFZpYC9zJRLW9lGBRofjVRe2HZEOJfVxp7O6GHXs4pGCKN2pDufn22YY4kCCW0 0yoVdwVifc5YlflUQCcJNH1WnRn3N4VjKGlGadZEHpYfVaL4L51zxKubYJVqUZF6D7kI nUZap5SgKl5BOkoD0RU4LqxtwruTvcUePLq8Q= Received: by 10.90.114.6 with SMTP id m6mr690200agc.22.1286893368180; Tue, 12 Oct 2010 07:22:48 -0700 (PDT) Received: from gtuhl.a (c-76-97-68-41.hsd1.ga.comcast.net [76.97.68.41]) by mx.google.com with ESMTPS id 6sm10057408anx.32.2010.10.12.07.19.59 (version=SSLv3 cipher=RC4-MD5); Tue, 12 Oct 2010 07:19:59 -0700 (PDT) Message-ID: <4CB46E8D.1010800@gmail.com> Date: Tue, 12 Oct 2010 10:19:57 -0400 From: Joe Uhl User-Agent: Postbox 2.0.0 (Macintosh/20100928) MIME-Version: 1.0 To: Neil Whelchel CC: "pgsql-performance@postgresql.org" Subject: Re: Slow count(*) again... References: <201010091626.18636.neil.whelchel@gmail.com> <201010111254.58479.neil.whelchel@gmail.com> <4CB3A2CC.9070009@vmsinfo.com> <201010111936.46207.neil.whelchel@gmail.com> In-Reply-To: <201010111936.46207.neil.whelchel@gmail.com> Content-Type: multipart/alternative; boundary="------------090601040705070507060403" X-Virus-Scanned: Maia Mailguard 1.0.1 X-Spam-Status: No, hits=-1.899 tagged_above=-10 required=5 tests=BAYES_00=-1.9, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001 X-Spam-Level: X-Archive-Number: 201010/150 X-Sequence-Number: 40569 This is a multi-part message in MIME format. --------------090601040705070507060403 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit >> The biggest single problem with "select count(*)" is that it is >> seriously overused. People use that idiom to establish existence, which >> usually leads to a performance disaster in the application using it, >> unless the table has no more than few hundred records. SQL language, of >> which PostgreSQL offers an excellent implementation, offers [NOT] >> EXISTS clause since its inception in the Jurassic era. The problem is >> with the sequential scan, not with counting. I'd even go as far as to >> suggest that 99% instances of the "select count(*)" idiom are probably >> bad use of the SQL language. > > I agree, I have seen many very bad examples of using count(*). I will go so > far as to question the use of count(*) in my examples here. It there a better > way to come up with a page list than using count(*)? What is the best method > to make a page of results and a list of links to other pages of results? Am I > barking up the wrong tree here? One way I have dealt with this on very large tables is to cache the count(*) at the application level (using memcached, terracotta, or something along those lines) and then increment that cache whenever you add a row to the relevant table. On application restart that cache is re-initialized with a regular old count(*). This approach works really well and all large systems in my experience need caching in front of the DB eventually. If you have a simpler system with say a single application/web server you can simply store the value in a variable, the specifics would depend on the language and framework you are using. Another more all-DB approach is to create a statistics tables into which you place aggregated statistics rows (num deleted, num inserted, totals, etc) at an appropriate time interval in your code. So you have rows containing aggregated statistics information for the past and some tiny portion of the new data happening right now that hasn't yet been aggregated. Queries then look like a summation of the aggregated values in the statistics table plus a count(*) over just the newest portion of the data table and are generally very fast. Overall I have found that once things get big the layers of your app stack start to blend together and have to be combined in clever ways to keep speed up. Postgres is a beast but when you run into things it can't do well just find a way to cache it or make it work together with some other persistence tech to handle those cases. --------------090601040705070507060403 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit
The biggest single problem with "select count(*)" is that it is
seriously overused. People use that idiom to establish existence, which
usually leads to a performance disaster in the application using it,
unless the table has no more than few hundred records. SQL language, of
which PostgreSQL offers an excellent implementation,  offers [NOT]
EXISTS clause since its inception in the Jurassic era. The problem is
with the sequential scan, not with counting. I'd even go as far as to
suggest that 99% instances of the "select count(*)" idiom are probably
bad use of the SQL language.

I agree, I have seen many very bad examples of using count(*). I will go so 
far as to question the use of count(*) in my examples here. It there a better 
way to come up with a page list than using count(*)? What is the best method 
to make a page of results and a list of links to other pages of results? Am I 
barking up the wrong tree here?
One way I have dealt with this on very large tables is to cache the count(*) at the application level (using memcached, terracotta, or something along those lines) and then increment that cache whenever you add a row to the relevant table.  On application restart that cache is re-initialized with a regular old count(*).  This approach works really well and all large systems in my experience need caching in front of the DB eventually.  If you have a simpler system with say a single application/web server you can simply store the value in a variable, the specifics would depend on the language and framework you are using.

Another more all-DB approach is to create a statistics tables into which you place aggregated statistics rows (num deleted, num inserted, totals, etc) at an appropriate time interval in your code.  So you have rows containing aggregated statistics information for the past and some tiny portion of the new data happening right now that hasn't yet been aggregated.  Queries then look like a summation of the aggregated values in the statistics table plus a count(*) over just the newest portion of the data table and are generally very fast.

Overall I have found that once things get big the layers of your app stack start to blend together and have to be combined in clever ways to keep speed up.  Postgres is a beast but when you run into things it can't do well just find a way to cache it or make it work together with some other persistence tech to handle those cases.


--------------090601040705070507060403--