{"id":698,"date":"2011-02-21T10:55:40","date_gmt":"2011-02-21T18:55:40","guid":{"rendered":"http:\/\/www.visophyte.org\/blog\/?p=698"},"modified":"2011-02-22T20:35:49","modified_gmt":"2011-02-23T04:35:49","slug":"why-so-slow-pushlog","status":"publish","type":"post","link":"https:\/\/www.visophyte.org\/blog\/2011\/02\/21\/why-so-slow-pushlog\/","title":{"rendered":"why so slow, pushlog?"},"content":{"rendered":"<p>I am doing something where I need to talk to the Mozilla hg pushlog. \u00a0I noticed things were running disturbingly slow, so I figured I&#8217;d look into it. \u00a0I&#8217;m using node.js so <a href=\"https:\/\/github.com\/mnot\/htracr\">htracr<\/a> (a <a href=\"http:\/\/nodejs.org\/\">node.js<\/a>, <a href=\"http:\/\/www.tcpdump.org\/\">libpcap<\/a>-based via <a href=\"https:\/\/github.com\/mranney\/node_pcap\">node_pcap<\/a> http transaction visualizer) seemed like the easiest and most fun way to figure out what is going on. \u00a0(In a web browser I would just use the built-in timeline.)<\/p>\n<p><a href=\"http:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/htracr-pushlog.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-700\" title=\"Request timeline, with a chunk taken out of the middle.\" src=\"http:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/htracr-pushlog.png\" alt=\"\" width=\"614\" height=\"180\" srcset=\"https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/htracr-pushlog.png 614w, https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/htracr-pushlog-600x175.png 600w, https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/htracr-pushlog-300x87.png 300w\" sizes=\"auto, (max-width: 614px) 100vw, 614px\" \/><\/a><\/p>\n<div id=\"attachment_701\" style=\"width: 293px\" class=\"wp-caption alignleft\"><a href=\"http:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/explained-older-blocks.png\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-701\" class=\"size-thumbnail wp-image-701\" title=\"inefficient query\" src=\"http:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/explained-older-blocks-283x600.png\" alt=\"\" width=\"283\" height=\"600\" srcset=\"https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/explained-older-blocks-283x600.png 283w, https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/explained-older-blocks-141x300.png 141w, https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/explained-older-blocks-483x1024.png 483w, https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/explained-older-blocks.png 733w\" sizes=\"auto, (max-width: 283px) 100vw, 283px\" \/><\/a><p id=\"caption-attachment-701\" class=\"wp-caption-text\">slooooooooooow...<\/p><\/div>\n<p>The short request is a comm-central pushlog request by date. \u00a0The long request is a mozilla-central pushlog request by date. \u00a0mozilla-central has more pushes, suggesting that the query is either doing something sketchy like a table scan or there is lock contention. \u00a0Quick investigation showed no successful pushes in the given timeframe, eliminating lock contention from the likely list of causes. \u00a0(The implementation uses SQLite, which, when not running with the Write-Ahead-Log enabled, will only experience contention when the reads are occurring from separate connections.)<\/p>\n<p>This suggests the query is doing a table scan. \u00a0This tends to be a reasonably straightforward problem. \u00a0But since I have a SQLite opcode visualizer in my toolkit (now on <a href=\"https:\/\/github.com\/asutherland\/grok-sqlite-explain\">github<\/a>!) that needs plugs periodically to keep it on the tip of everyone&#8217;s tongue, I used <a href=\"https:\/\/github.com\/asutherland\/grok-sqlite-explain\">that<\/a>. <a href=\"https:\/\/github.com\/asutherland\/grok-sqlite-explain\">Plug! Plug! Plug!<\/a><\/p>\n<p>If you care about these kind of things, click on the small image on the left and look for the red text. \u00a0The giant warning flag is the &#8220;Rewind&#8221; on the changesets table and the control flow that performs a &#8220;Next&#8221; \u00a0that appears to be part of a loop case right off the bat. \u00a0(Note that this is using SQLite 3.6.23.1 since I assume that&#8217;s what hg.mozilla.org is using.) \u00a0Summary: we perform one table scan of the <em>changesets<\/em> table for every push in the date range.<\/p>\n<p>The &#8220;bad&#8221; schema is:<\/p>\n<pre>CREATE TABLE changesets (pushid INTEGER, rev INTEGER, node text);\r\nCREATE TABLE pushlog (id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT, date INTEGER);\r\nCREATE UNIQUE INDEX changeset_node ON changesets (node);\r\nCREATE UNIQUE INDEX changeset_rev ON changesets (rev);\r\nCREATE INDEX pushlog_date ON pushlog (date);\r\nCREATE INDEX pushlog_user ON pushlog (user)<\/pre>\n<p>The query is: SELECT id, user, date, node from pushlog LEFT JOIN changesets ON id = pushid WHERE date &gt; 1298306930 AND date &lt; 1298306955 ORDER BY id DESC, rev DESC;<\/p>\n<p>The query plan via EXPLAIN QUERY PLAN from SQLite 3.6.23.1 on the &#8220;bad&#8221; schema is:<\/p>\n<pre>0|0|TABLE pushlog WITH INDEX pushlog_date\r\n1|1|TABLE changesets<\/pre>\n<p>This is obvious if you know what you&#8217;re looking for; unfortunately the indication of the problem is the lack of &#8220;WITH INDEX&#8221; or the like rather than text that calls out the problem.  The much nicer SQLite 3.7.4 EXPLAIN QUERY PLAN (which has <a href=\"http:\/\/www.sqlite.org\/eqp.html\">great documentation<\/a>!) would make the problem much more obvious by saying &#8220;SCAN TABLE&#8221; if not for the fact that it ends up creating an automatic index:<\/p>\n<pre>0|0|0|SEARCH TABLE pushlog USING INDEX pushlog_date (date&gt;? AND date&lt;?) (~110000 rows)\r\n0|1|1|SEARCH TABLE changesets USING AUTOMATIC COVERING INDEX (pushid=?) (~7 rows)\r\n0|0|0|USE TEMP B-TREE FOR ORDER BY<\/pre>\n<p>Although one table scan accompanied by a b-tree building is probably a better idea than N table scans, if you read this and think &#8220;SQLite 3.7.x just saved someone&#8217;s bacon&#8221;, you would be wrong because it still throws away the index at the end. \u00a0The server is still experiencing an initial vicious kick in the pants every time the statement is run; there are just no lighter follow-up kicks (lighter because of the OS disk cache, you see&#8230;).<\/p>\n<p><a href=\"http:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/with-index-blocks.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-thumbnail wp-image-717\" title=\"with-index-blocks\" src=\"http:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/with-index-blocks-229x600.png\" alt=\"\" width=\"229\" height=\"600\" srcset=\"https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/with-index-blocks-229x600.png 229w, https:\/\/www.visophyte.org\/blog\/wp-content\/uploads\/2011\/02\/with-index-blocks-114x300.png 114w\" sizes=\"auto, (max-width: 229px) 100vw, 229px\" \/><\/a>In any event, the problem is that there is no index on the <em>pushid<\/em> column in the <em>changesets<\/em> table.\u00a0 (And pushid can&#8217;t be the primary key because it is not unique.\u00a0 There&#8217;s no benefit to using a composite key since SQLite will still create a simple rowid key once we use a composite, so an index is the way to go.)<\/p>\n<p>Once we fix this, our graph looks like the second one at left (in SQLite 3.6.23.1). \u00a0Again, looking for red text and now also orange text, the key things are that we no longer have a &#8220;Rewind&#8221; or &#8220;Next&#8221;, and instead have a SeekGe on the index and a Seek on the table using the row id the index told us about.\u00a0 (We are not using a covering index because we expect extremely high locality in the table based on rowid because the insertions happen consecutively in a single transaction.)<\/p>\n<p>The 3.6.23.1 query plan now looks like:<\/p>\n<pre>0|0|TABLE pushlog WITH INDEX pushlog_date\r\n1|1|TABLE changesets WITH INDEX changeset_pushid<\/pre>\n<p>Much nicer!  What does the lovely 3.7.4 say?:<\/p>\n<pre>0|0|0|SEARCH TABLE pushlog USING INDEX pushlog_date (date&gt;? AND date&lt;?) (~110000 rows)\r\n0|1|1|SEARCH TABLE changesets USING INDEX changeset_pushid (pushid=?) (~10 rows)\r\n0|0|0|USE TEMP B-TREE FOR ORDER BY<\/pre>\n<p>Awww yeah.  Anywho, here&#8217;s the schema one wants to use:<\/p>\n<pre>CREATE TABLE changesets (pushid INTEGER, rev INTEGER, node text);\r\nCREATE TABLE pushlog (id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT, date INTEGER);\r\n<strong>CREATE INDEX changeset_pushid ON changesets (pushid);<\/strong>\r\nCREATE UNIQUE INDEX changeset_node ON changesets (node);\r\nCREATE UNIQUE INDEX changeset_rev ON changesets (rev);\r\nCREATE INDEX pushlog_date ON pushlog (date);\r\nCREATE INDEX pushlog_user ON pushlog (user);<\/pre>\n<p>I am off to file this as a bug&#8230; filed as<a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=635765\"> bug 635765<\/a>.<\/p>\n<p><strong>UPDATE: <\/strong>I initially had a brain glitch where I proposed using a primary key rather than an additional index.\u00a0 Unfortunately, this is what got pushed out to feed readers during the 30 seconds before I realized my massive mistake.\u00a0 If you have come to this page to note the bad-idea-ness of that, please enjoy the corrected blog-post which explains why the primary key is not the best idea before going on to point out any other serious brain glitches \ud83d\ude42<\/p>\n<p><strong>UPDATE 2<\/strong> (Feb 22, 2011): \u00a0Good news: the change has landed and all the existing databases have been updated. \u00a0Woo! \u00a0Bad news: although things are probably faster now, things are still way too slow. \u00a0(Given that this specific deficiency was determined by inspection and not actual profiling, this is not so surprising.) \u00a0It would appear that the retrieval of the information about the changesets is the cause of the slowdown. \u00a0More inspection on my part suggests that populating the list of tags may be involved, but that&#8217;s just a quick guess. \u00a0Please follow the bug or follow-on bugs if you want to witness the entire exciting saga.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I am doing something where I need to talk to the Mozilla hg pushlog. \u00a0I noticed things were running disturbingly slow, so I figured I&#8217;d look into it. \u00a0I&#8217;m using node.js so htracr (a node.js, libpcap-based via node_pcap http transaction &hellip; <a href=\"https:\/\/www.visophyte.org\/blog\/2011\/02\/21\/why-so-slow-pushlog\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[7,4],"tags":[24],"class_list":["post-698","post","type-post","status-publish","format-standard","hentry","category-debugging","category-visualizing","tag-sqlite"],"_links":{"self":[{"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/posts\/698","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/comments?post=698"}],"version-history":[{"count":17,"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/posts\/698\/revisions"}],"predecessor-version":[{"id":714,"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/posts\/698\/revisions\/714"}],"wp:attachment":[{"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/media?parent=698"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/categories?post=698"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.visophyte.org\/blog\/wp-json\/wp\/v2\/tags?post=698"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}