Have you noticed that number of pages in the 'top visitors' report (admin/logs/visitors) doesn't match the real number of records that should be reported?
The queries used to build the pager look like this:
$sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, ac.aid FROM {accesslog} a LEFT JOIN {access} ac ON ac.type = 'host' AND LOWER(a.hostname) LIKE (ac.mask) LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname, a.uid, u.name, ac.aid". tablesort_sql($header);
$sql_cnt = "SELECT COUNT(DISTINCT(uid)) FROM {accesslog}";
Note that while the GROUP BY clausule for the first query has 4 columns, the query used to compute the pager count only uses uid. In the GROUP BY clausule u.name and ac.aid depend on the uid, but hostname (the ip) does not. ;-)
The correct query to compute the pager count should look like this:
$sql_cnt = "SELECT COUNT(DISTINCT(CONCAT(uid, hostname))) FROM {accesslog}";
Try it, and you'll see the difference.
PS: I'm sorry for not attaching a patch here, but my statistics module has several patches applied that are still pending to apply to HEAD.
Comments
Comment #1
moshe weitzman commentednot ready for review cuz it aint a real patch.
Comment #2
markus_petrux commentedok, here we go.
Comment #3
chx commentedI do not think multiolumn DISTINCT would need CONCAT. Please look into whether select (count distinct(uid, hostname)) would work.
Comment #4
markus_petrux commentedI did tests. In my case, running MySQL 4.1.x, I had to use CONCAT.
Comment #5
dries commentedWouldn't it be better if that query did do the exact same group? Not sure which one would be faster. Can't we simplify that massive 'group by' statement. Looks like it might be painfully slow.
Comment #6
markus_petrux commentedThe main query is indeed heavy, though I don't know how it could be optimized. Maybe adding indexes? ...but that would affect performance at INSERT time (ie. at hook_exit time, for every page hit).
As far as I know, all fields specified in the GROUP BY clausule are required. That's how aggregate functions are grouped.
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html
The count query is not so heavy. It could be optimized by adding an index by uid+hostname to the access_log table. uid+hostname is the minimum common factor of the group by clausule used in the query.
Comment #7
markus_petrux commentedWell, the patch fixes the issue, otherwise the count query returns an incorrect result.
Performance would be a different issue...
Comment #8
moshe weitzman commentedif a bug fix negatively impacts performance, it won't be accepted. calling them separate issues does not help us proceed. you might be right, but it doesn't matter :)
Comment #9
markus_petrux commentedBut the performce issue is not related to the query changed by this patch. The heavy query is the one that retrieves the rows for the pager.
Have you checked with the devel module? I did, with an access table filled with more than 10000 rows. The count query is quite fast with and without the patch. The other one is slow, but it is not touched by this patch. If that should be addressed, this is IMHO a different issue.
Comment #10
moshe weitzman commentedok i get it now. i got mixed up with some other proposal to add indexes to accesslog table. thanks for the clarification.
Comment #11
markus_petrux commentedNo problem. Probably my fault for not being clear when I said "Performance would be a different issue...", sorry.
Comment #12
dries commentedCan't we come up with a different count-query? This looks like a really weird approach.
Comment #13
markus_petrux commentedI tried to find alternatives, but none returned the expected result. I ended up with this proposal.
Comment #14
dries commentedCan't come up with a better query either ... anyone else?
Comment #15
markus_petrux commentedMaybe bumping caches some attention to see if anyone else can come up with a different approach?
Comment #16
markus_petrux commentedSince no one came with a better approach, maybe the patch in #2 is RTBC?
Comment #17
Steven commentedI've had issues with COUNT(DISTINCT()) and PgSQL before. Needs a PgSQL check before committing.
Comment #18
markus_petrux commentedI have opened a forum topic asking for help to review the proposed solution in #2:
http://drupal.org/node/58709
Comment #19
markus_petrux commentedNote that the actual code uses
COUNT(DISTINCT()). The proposed patch addsCONCAT(uid, hostname), so the count query returns the correct result for the table pager to compute the number of pages.Please, look at the above mentioned forum topic. grcm has been able to test the query in PostgreSQL.
I can't think of any other alternative.
Comment #20
moshe weitzman commentedFYI, we had a similar CONCAT() in our main node_access query and it performed very poorly with large data sets because it could not use some index. FYI.
Comment #21
markus_petrux commentedI think the only way to improve performance for this query would be to add an index for uid+hostname.
This is just used in admin/logs/visitors. Not sure how important the performance hit is, overall. The number of records to keep on the accesslog table depends on site traffic, but also on the number of days option of the module.
OTOH, node_access is used in the user side. ie. performance was much important. It is not comparable, I think.
Comment #22
nickl commentedRerolled to head.
The issue refers to:
I don't have number of pages I only have: Hits, Visitor, Total page generation time and Operations
Results are the same with or without the patch.
Comment #23
drummThis seems to double the time it takes to execute the query (tried on Drupal.org's database server). However, it is only for one admin page. I'm not sure adding keys and such would be a good idea since we need to keep inserts fast as our priority.
Other reviews?
Comment #24
drummCommitted to HEAD.
Comment #25
(not verified) commented