<?xml version="1.0" encoding="utf-8"?>



<feed xmlns="http://www.w3.org/2005/Atom"
    xmlns:fh="http://purl.org/syndication/history/1.0"
    xmlns:at="http://purl.org/atompub/tombstones/1.0">

    <title>Publ: Development Blog</title>
    <subtitle>A personal publishing system for the modern web</subtitle>
    <link href="http://publ.beesbuzz.biz/blog/feed?tag=performance" rel="self" />
    <link href="http://publ.beesbuzz.biz/blog/feed" rel="current" />
    <link href="https://busybee.superfeedr.com" rel="hub" />
    
    
    <link href="http://publ.beesbuzz.biz/blog/" />
    <fh:archive />
    <id>tag:publ.beesbuzz.biz,2020-01-07:blog</id>
    <updated>2024-10-02T02:13:22-07:00</updated>

    
    <entry>
        <title>Publ v0.7.31 released</title>
        <link href="http://publ.beesbuzz.biz/blog/569-v0.7.31-released" rel="alternate" type="text/html" />
        <published>2024-10-02T02:13:22-07:00</published>
        <updated>2024-10-02T02:13:22-07:00</updated>
        <id>urn:uuid:7d73cb31-dd0c-537b-9f47-07c45ba77692</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>There&rsquo;s a new release of Publ. There&rsquo;s no new features, but there&rsquo;s a <em>huge</em> performance improvement.</p><p>I&rsquo;d been having some performance issues where on my larger sites, the main Atom feed was taking a long time to render. It didn&rsquo;t really bother me too much because thanks to aggressive caching it would only cause an occasional slow page load (on the order of a few seconds) every now and then, but I thought there was probably something wrong with the I/O characteristics of how pages render.</p><p>Boy <em>howdy</em> was I wrong about that.</p>

<p>The way that Publ handles count-based pagination is to look at the first eligible entry and then traverse forward in the query results until it finds that number of visible ones. There&rsquo;s a few other fancy things for handling the presence of unauthorized entries for various purposes (for example, <a href="http://publ.beesbuzz.biz/manual/api/150-View-object#has_unauthorized"><code>view.has_unauthorized</code></a> and being able to retrieve a limited number of unauthorized entries for friends-only stubs on feeds and so on), but the prevailing assumption was that PonyORM would just have a query result cursor page through every time the iterator incremented on the loop.</p><p>Turns out that, no. No it doesn&rsquo;t.</p><p>In fact, what was happening was Pony ends up retrieving <em>every</em> possible entry first, before Publ&rsquo;s auth filtering could run. So on <a href="https://beesbuzz.biz/">my main site</a>, for example, which has around 3300 entries, it was retrieving all 3300 entry rows in order to render the Atom feed.</p><p>So, yeah, no wonder it was taking multiple seconds to render!</p><p>Anyway, now in those situations, Publ just fetches chunks of entries at a time, basically doing its own ad-hoc cursor. It&rsquo;s not quite as efficient as it would be with a proper database cursor (since now it&rsquo;s using <code>LIMIT</code>/<code>OFFSET</code> queries) and it could still be way faster if I were using a database layer other than Pony (for example, switching to SQLAlchemy, or eschewing a database entirely as I&rsquo;ve <a href="http://publ.beesbuzz.biz/blog/274-Publ-v0.7.26-released">briefly discussed</a> and <a href="https://beesbuzz.biz/code/3635-Making-a-hash-of-data">rambled</a> about), but wow, this makes a huge performance improvement overall.</p><p>Also, while I was trying to diagnose the issue, I had followed a red-herring path about changing the way that entry auth is stored, and ended up simplifying it in a way which is how I should have done it to begin with. Unfortunately that required a schema change, which means sites need to be reindexed after upgrading. Fortunately that&rsquo;s still a largely transparent operation.</p><p>Someday I need to do some pretty big overhauls to Publ. But for now I&rsquo;m happy just keeping it going and making it better all the time.</p><p>Anyway, <strong><em>huge</em></strong> thanks to BearPerson from eevee&rsquo;s discord for having the curiosity and tenacity to find the <em>actual</em> performance issue.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>SQLite vs. Postgres, at a glance</title>
        <link href="http://publ.beesbuzz.biz/blog/616-SQLite-vs.-Postgres-at-a-glance" rel="alternate" type="text/html" />
        <published>2021-05-05T11:28:50-07:00</published>
        <updated>2021-05-05T11:28:50-07:00</updated>
        <id>urn:uuid:bb6c33bc-50d5-587e-8d94-8820bb28c7e4</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>There&rsquo;s a general belief that SQLite is a &ldquo;slow&rdquo; database and Postgres is &ldquo;fast,&rdquo; and many software packages (including FOSS) insist that SQLite is only suitable for testing and doesn&rsquo;t scale. However, this doesn&rsquo;t make much sense when you think about it; SQLite is an in-process database so there&rsquo;s no communications overhead between the service and the database, and because it&rsquo;s only designed to be accessed from a single process it can make use of optimistic locking to speed up transactions.</p><p>Since I was installing postgres for another purpose on my webserver, I decided to quickly see if Publ performs better on Postgres vs SQLite. To test the performance I compared the timing for <a href="https://beesbuzz.biz/">my website</a> on both doing a full site reindex, and rendering the Atom feed several times (using the debug Flask server and caching disabled).</p>

<p>Times are in seconds:</p>
<table>
<thead>
<tr>
<th>Database</th>
<th>Index</th>
<th>Atom feed</th>
</tr>
</thead>

<tbody>
<tr>
<td>SQLite</td>
<td>23.267</td>
<td>0.799</td>
</tr>
<tr>
<td>Postgres 12</td>
<td>26.132</td>
<td>1.270</td>
</tr>
</tbody>
</table>
<p>So, SQLite is, as I had assumed, substantially faster than Postgres, and also has much lower administrative overhead. Thus, I will continue to recommend that as the database of choice for traditionally-hosted deployments.</p><p>My belief is that, in general, if you&rsquo;re building something where there&rsquo;s only a single process connecting to the database (i.e. you don&rsquo;t have a cluster talking to a single database instance), SQLite will perform better than Postgres. The reason to use Postgres is so that you can scale to multiple processes or servers talking to a single centralized data store. If you can build your system such that each database connection can be isolated to a single database instance, SQLite is going to perform much better.</p><p>There are other considerations, of course, but if performance is your primary concern, SQLite isn&rsquo;t a bad way to go.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Publ 0.6.6, Authl 0.4.0</title>
        <link href="http://publ.beesbuzz.biz/blog/85-Publ-0.6.6-Authl-0.4.0" rel="alternate" type="text/html" />
        <published>2020-05-31T03:32:50-07:00</published>
        <updated>2020-05-31T03:32:50-07:00</updated>
        <id>urn:uuid:d31d1c24-6e50-5491-8b27-d9515e0db01a</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>I&rsquo;ve just released new versions of Publ and Authl.</p><p>Publ v0.6.6 changes:</p>
<ul>
<li>Fixed a regression that made it impossible to log out</li>
<li>Fixed a problem where <code>WWW-Authenticate</code> headers weren&rsquo;t being cached properly</li>
<li>Improve the changed-file cache-busting methodology</li>
<li>Add object pooling to Entry, Category, and View (for a potentially big memory and performance improvement)</li>
</ul>
<p>Authl v0.4.0 changes:</p>
<ul>
<li>Finally started to add unit tests</li>
<li>Removed some legacy WebFinger code that was no longer relevant or ever touched</li>
<li>Added a mechanism to allow providers to go directly to login, as appropriate</li>
<li>Added friendly visual icons for providers which support them (a so-called &ldquo;<a href="https://indieweb.org/NASCAR_problem">NASCAR interface</a>&rdquo;)</li>
</ul>


<h2 id="85_h2_1_Publ-0.6.6"><a href="http://publ.beesbuzz.biz/blog/85-Publ-0.6.6-Authl-0.4.0#85_h2_1_Publ-0.6.6"></a>Publ 0.6.6</h2><p>The main reason for this update is just that the embarrassing logout bug was rearing its head and I wanted to fix it on my site without monkeypatching it or temporarily moving to git head or whatever. The <code>WWW-Authenticate</code> fix is nice, though, as it&rsquo;s related to some work I&rsquo;m doing on Pushl (namely adding the ability to retrieve bearer tokens from an external helper program).</p><p>It&rsquo;s difficult to estimate what a performance change will be like based on testing on a developer desktop vs. a production VPS. In particular, the various I/O performance characteristics can vary a lot, and Publ is primarily I/O bound. In my desktop-side testing I found that the object pooling increased performance by 15%, which is already pretty great, but that&rsquo;s also on a machine with a lot of memory, a huge file cache, and no disk virtualization. I&rsquo;ve only deployed Publ 0.6.6 on my personal website around half an hour ago, but already my site monitoring is showing a <em>rather impressive</em> performance improvement. For example, the Atom feed used to take around 30 seconds to render on a cache miss. Right now it seems to take 2.5 seconds.</p><p>So, yeah, it takes only 10% of the time to run now &ndash; that&rsquo;s around a <em>900% performance improvement</em> in a typical deployment scenario. So, that&rsquo;s pretty great.</p><p>Right now the largest remaining performance bottleneck seems to be in PonyORM, which is unfortunate. I haven&rsquo;t yet figured out if it&rsquo;s with PonyORM itself, or with its interface to sqlite. From what I can tell, the way that trace profiling works in Python means that things with a lot of function calls become quite a lot slower than long-running things within a single function, so things that do a lot of abstraction and dependency injection (like, say, PonyORM) get unfairly impacted in trace profiling. A sample-based profiling approach would be much more fair and realistic, but I haven&rsquo;t found any sample-based Python profilers (and I don&rsquo;t know enough about Python&rsquo;s internals to know if that&rsquo;s even a possibility).</p><p>My short-term goals for Publ are otherwise unchanged since the <a href="http://publ.beesbuzz.biz/blog/467-Publ-v0.6.5">last release announcement</a>.</p><h2 id="85_h2_2_Authl-0.4.0"><a href="http://publ.beesbuzz.biz/blog/85-Publ-0.6.6-Authl-0.4.0#85_h2_2_Authl-0.4.0"></a>Authl 0.4.0</h2><p>I hadn&rsquo;t worked on Authl in quite some time, but I felt like it needed some attention.</p><p>These Authl changes are basically for some UX improvements that had been bugging me for a while; there was an awful lot of text to read and that was possibly scary to newcomers. Now there&rsquo;s still just as much text to read but there&rsquo;s friendly icons for a bunch of the supported services, and silo services such as Twitter can now go straight to the login flow without implying that the username is necessary.</p><p>Here&rsquo;s a before and after on the default Flask template:</p><p><a href="http://publ.beesbuzz.biz/blog/85-Publ-0.6.6-Authl-0.4.0"><img src="http://publ.beesbuzz.biz/static/_img/7b/2a08/authl-0.3.6_08775e7819_320x153.png" width="320" height="153" srcset="http://publ.beesbuzz.biz/static/_img/7b/2a08/authl-0.3.6_08775e7819_320x153.png 1x, http://publ.beesbuzz.biz/static/_img/7b/2a08/authl-0.3.6_08775e7819_640x306.png 2x" loading="lazy" alt="authl-0.3.6.png" title="v0.3.6"></a><a href="http://publ.beesbuzz.biz/blog/85-Publ-0.6.6-Authl-0.4.0"><img src="http://publ.beesbuzz.biz/static/_img/b1/3d85/authl-0.4.0_5c04af48c1_320x160.png" width="320" height="160" srcset="http://publ.beesbuzz.biz/static/_img/b1/3d85/authl-0.4.0_5c04af48c1_320x160.png 1x, http://publ.beesbuzz.biz/static/_img/b1/3d85/authl-0.4.0_5c04af48c1_640x320.png 2x" loading="lazy" alt="authl-0.4.0.png" title="v0.4.0"></a></p><p>The next thing I want to work on for Authl is finally adding actual support for user profiles. This would also probably go along with things like adding more providers, particularly Facebook, Tumblr, and maybe even OpenID 1.x (i.e. Dreamwidth). Better profile support means having a friendlier greeting than just the canonical identity URL, among other things that people might want in their own federated login use cases.</p><h2 id="85_h2_3_Some-other-thoughts-of-things-th"><a href="http://publ.beesbuzz.biz/blog/85-Publ-0.6.6-Authl-0.4.0#85_h2_3_Some-other-thoughts-of-things-th"></a>Some other thoughts of things that would be neat</h2><p>Now that Publ supports <a href="http://publ.beesbuzz.biz/blog/738-Publ-v0.6.4-now-with-attachments">entry attachments</a>, it might be reasonable to add native server-side webmentions; rather than fetching the mentions from webmention.io on every page view, have a webhook on update that triggers a script that fetches and formats the mentions as an attachment that can then be rendered and cached, as well as getting all of the benefits of SEO that it would bring. For some sites, having the comments be indexed by the search engines makes a <em>huge</em> difference to page ranking, since the conversation about an article can add in some useful keywords that weren&rsquo;t in the actual article. (Not to mention it improves the page&rsquo;s &ldquo;freshness&rdquo; as far as the search engine is concerned.)</p><p>Another thought I&rsquo;ve had about attachments is they could be used to implement a server-side comment system, although that would require a <em>lot</em> more work than webmention rendering (UI, moderation/spam-filtering, migrating stuff <em>again</em>) and after all the work I put into my <a href="https://posativ.org/isso/">Isso</a> setup I&rsquo;m not quite ready to think about how to actually do that. I&rsquo;d probably want to do it in the form of having a mechanism to pre-render the Isso comment thread and form into an HTML attachment rather than having every part of it handled via Publ entry attachments.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Caching stats update</title>
        <link href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update" rel="alternate" type="text/html" />
        <published>2020-02-05T13:23:28-08:00</published>
        <updated>2020-02-05T13:23:28-08:00</updated>
        <id>urn:uuid:39ff7921-798e-5013-a96e-7bfdb8ccf119</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>A few weeks ago I had discovered that <a href="http://publ.beesbuzz.biz/blog/304-v0.5.12-released-and-lots-of-documentation-fixes">caching wasn&rsquo;t actually being used most of the time</a>, and took some stats snapshots for future comparison.</p><p>Now that Publ has been running with correct caching for a while, let&rsquo;s see how things have changed!</p>

<h2 id="287_h2_1_Caveats"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h2_1_Caveats"></a>Caveats</h2><p>These stats are based on overall site usage, so it includes both manual browsing and also search crawlers and feed readers and the like. Simply looking at the cache statistics doesn&rsquo;t paint a very clear picture of the actual performance improvements; in the stats, 10 users being able to quickly load a fresh blog entry quickly will be far overshadowed by a single search engine spidering the entire website and thrashing the cache, but those 10 users are, to me, far more important.</p><h2 id="287_h2_2_Measurements"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h2_2_Measurements"></a>Measurements</h2><h3 id="287_h3_3_Throughput"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h3_3_Throughput"></a>Throughput</h3><p>Here&rsquo;s a measurement of how much traffic the cache actually sees:</p><p><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/4d/c3a4/memcached_bytes-week-20191231_dd3a3e801a_320x180.png" width="320" height="180" srcset="http://publ.beesbuzz.biz/static/_img/4d/c3a4/memcached_bytes-week-20191231_dd3a3e801a_320x180.png 1x, http://publ.beesbuzz.biz/static/_img/4d/c3a4/memcached_bytes-week-20191231_dd3a3e801a_640x360.png 2x" loading="lazy" alt="memcached_bytes-week-20191231.png" title="Cache throughput, December 31 2019"></a><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/d5/ab7a/memcached_bytes-week-20200205_d7b44703ac_320x180.png" width="320" height="180" srcset="http://publ.beesbuzz.biz/static/_img/d5/ab7a/memcached_bytes-week-20200205_d7b44703ac_320x180.png 1x, http://publ.beesbuzz.biz/static/_img/d5/ab7a/memcached_bytes-week-20200205_d7b44703ac_640x360.png 2x" loading="lazy" alt="memcached_bytes-week-20200205.png" title="Cache throughput, February 5 2020"></a></p><p>The first graph shows that before I fixed the caching, very little was being written to the cache, but the amount being read from it was pretty steady. As soon as the fix was made and the cache was being written to, amazingly enough it started actually receiving traffic. In the initial spike of activity, the read and write rate were about the same, which seems plausible for a cache that&rsquo;s being filled in with a relatively low hit rate. There&rsquo;s a steady read rate of around 40K/second and a steady write rate of around 8K/sec &ndash; most of that being internal routines that were being written to the cache, uselessly.</p><p>The second graph (post-fix) shows a cache that&rsquo;s actually being actively used. There&rsquo;s an average write rate of 12K/sec, and a read rate of 17K/sec. There are also several write spikes at around 25K/sec, which I am suspecting are due to search crawler traffic.</p><h3 id="287_h3_4_Allocation"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h3_4_Allocation"></a>Allocation</h3><p>This is where things get a bit more useful to look at &ndash; how much stuff is actively being held in the cache?</p><p><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/50/5b4f/memcached_counters-week-20191231_98eea433c8_320x189.png" width="320" height="189" srcset="http://publ.beesbuzz.biz/static/_img/50/5b4f/memcached_counters-week-20191231_98eea433c8_320x189.png 1x, http://publ.beesbuzz.biz/static/_img/50/5b4f/memcached_counters-week-20191231_98eea433c8_640x377.png 2x" loading="lazy" alt="memcached_counters-week-20191231.png" title="Memory allocation, December 31 2019"></a><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/8a/414e/memcached_counters-week-20200205_6c17cef995_320x189.png" width="320" height="189" srcset="http://publ.beesbuzz.biz/static/_img/8a/414e/memcached_counters-week-20200205_6c17cef995_320x189.png 1x, http://publ.beesbuzz.biz/static/_img/8a/414e/memcached_counters-week-20200205_6c17cef995_640x377.png 2x" loading="lazy" alt="memcached_counters-week-20200205.png" title="Memory allocation, February 5 2020"></a></p><p>Before the cache fix, the answer to that was, &ldquo;Not much.&rdquo; The cache was averaging a size of a mere 868KB, and after I flipped the caching fix over, it jumped up considerably. During my testing of the fix, the size would spike up substantially and then drop down as cache items got evicted.</p><p>After the cache fix, the allocation went way up. It never went below 2MB, and during the write spikes it would jump up to 7MB or so. This is still far short of the 64MB I have allocated for the cache process.</p><h3 id="287_h3_5_Commands-results"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h3_5_Commands-results"></a>Commands/results</h3><p>Here&rsquo;s what is actually happening in terms of the cache hits and misses:</p><p><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/3e/0b68/memcached_rates-week-20191231_11ae089b24_320x202.png" width="320" height="202" srcset="http://publ.beesbuzz.biz/static/_img/3e/0b68/memcached_rates-week-20191231_11ae089b24_320x202.png 1x, http://publ.beesbuzz.biz/static/_img/3e/0b68/memcached_rates-week-20191231_11ae089b24_640x403.png 2x" loading="lazy" alt="memcached_rates-week-20191231.png" title="Commands, December 31 2019"></a><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/aa/32c5/memcached_rates-week-20200205_afb89652ea_320x202.png" width="320" height="202" srcset="http://publ.beesbuzz.biz/static/_img/aa/32c5/memcached_rates-week-20200205_afb89652ea_320x202.png 1x, http://publ.beesbuzz.biz/static/_img/aa/32c5/memcached_rates-week-20200205_afb89652ea_640x403.png 2x" loading="lazy" alt="memcached_rates-week-20200205.png" title="Commands, February 5 2020"></a></p><p>Before, the graph shows an average of 44 hits per second, and .63 misses per second. The GET and SET rates are (unsurprisingly) more or less the same.</p><p>After, we see much more interesting patterns &ndash; and not in a good way. It&rsquo;s averaging only 13 hits per second, and .8 misses per second, but that&rsquo;s an average. Eyeballing the graph it looks like the miss rate spikes at about the same time as the incoming traffic spikes, and outside of those spikes the hit rate is around 13 and the miss rate is&hellip; too small to reasonably estimate.</p><h3 id="287_h3_6_Page-load-time"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h3_6_Page-load-time"></a>Page load time</h3><p>Also when I made the change I also started monitoring the load time of a handful of URLs, which is <em>interesting</em>:</p><p><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/e1/2ff5/http_loadtime-week-20200205_2696cef180_320x197.png" width="320" height="197" srcset="http://publ.beesbuzz.biz/static/_img/e1/2ff5/http_loadtime-week-20200205_2696cef180_320x197.png 1x, http://publ.beesbuzz.biz/static/_img/e1/2ff5/http_loadtime-week-20200205_2696cef180_640x395.png 2x" loading="lazy" alt="http_loadtime-week-20200205.png" title="page load time"></a></p><p>What&rsquo;s interesting about these graphs is that Munin loads those URLs once every 5 minutes &ndash; which happens to be the cache timeout, and so that does a lot to explain the rather chaotic nature of the load time graph, especially on the Atom feed (minimum of 113ms, maximum of 45 seconds, average of 12 seconds). The Atom feed is probably the most loadtime-intense page on my entire website, and would most strongly benefit from caching. This graph tells me that based on the average vs. max times, the Atom feed is getting a hit rate of around 25%. That isn&rsquo;t great.</p><h2 id="287_h2_7_Conclusions"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h2_7_Conclusions"></a>Conclusions</h2><p>Aggregate memcached stats aren&rsquo;t really that useful for determining cache performance at this scale.</p><p>More to the point, the cache <em>as currently configured</em> probably isn&rsquo;t really making much of a difference. Items are falling out of the cache before they&rsquo;re really being reused.</p><h2 id="287_h2_8_Next-steps"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h2_8_Next-steps"></a>Next steps</h2><p>It&rsquo;s worth noting that the default memcached expiry time is 5 minutes (which also happens to be how I had my sites configured), which feels like a good tradeoff between content staleness and performance optimization. However, Publ <a href="https://github.com/PlaidWeb/Publ/commit/6ae4ae5731da46027ced9f0ea381dad66e3584a4#diff-650397549bec3d65892e233d5bd328f6R113">soft-expires all cached items</a> when there&rsquo;s a content change, so the only things that should linger with a longer expiry time are things like the &ldquo;5 minutes ago&rdquo; human-readable times on entries, which really don&rsquo;t matter if they&rsquo;re outdated.</p><p>As an experiment I will try increasing the cache timeout to an hour on all of my sites and see what effect that has. My hypothesis is that the allocation size and hit rate will both go up substantially, and the average page load time will go <em>way</em> down, with (much smaller) hourly spikes and otherwise a very fast page load (except for when I&rsquo;m making content changes, of course).</p><p>I&rsquo;m also tempted to try setting the default expiry to 0 &ndash; as in, never expire, only evict &ndash; and see what effect that has on performance. I probably won&rsquo;t, though &ndash; it would have an odd effect on the display of humanized time intervals and make that way too nondeterministic for my taste.</p><h2 id="287_h2_9_Update-Initial-results"><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update#287_h2_9_Update-Initial-results"></a><mark>Update:</mark> Initial results</h2><p>Even after just a few hours it becomes <em>pretty obvious</em> what effect this change had:</p><p><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/86/7de4/apache_processes-pinpoint=1580849415-1580957415_c6b236631a_320x193.png" width="320" height="193" srcset="http://publ.beesbuzz.biz/static/_img/86/7de4/apache_processes-pinpoint=1580849415-1580957415_c6b236631a_320x193.png 1x, http://publ.beesbuzz.biz/static/_img/86/7de4/apache_processes-pinpoint=1580849415-1580957415_c6b236631a_640x386.png 2x" loading="lazy" alt="apache_processes-pinpoint=1580849415,1580957415.png" title="Apache process counts"></a><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/07/92c4/http_loadtime-pinpoint=1580849415-1580957415_9500d1701e_320x197.png" width="320" height="197" srcset="http://publ.beesbuzz.biz/static/_img/07/92c4/http_loadtime-pinpoint=1580849415-1580957415_9500d1701e_320x197.png 1x, http://publ.beesbuzz.biz/static/_img/07/92c4/http_loadtime-pinpoint=1580849415-1580957415_9500d1701e_640x395.png 2x" loading="lazy" alt="http_loadtime-pinpoint=1580849415,1580957415.png" title="Page load time"></a><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/41/ada6/memcached_bytes-pinpoint=1580849415-1580957415_82bcce1c79_320x180.png" width="320" height="180" srcset="http://publ.beesbuzz.biz/static/_img/41/ada6/memcached_bytes-pinpoint=1580849415-1580957415_82bcce1c79_320x180.png 1x, http://publ.beesbuzz.biz/static/_img/41/ada6/memcached_bytes-pinpoint=1580849415-1580957415_82bcce1c79_640x360.png 2x" loading="lazy" alt="memcached_bytes-pinpoint=1580849415,1580957415.png" title="memcached throughput"></a><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"></a><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"></a></p><p>The actual effect is a bit surprising, though; I would have expected the quiescent RAM allocation to be closer to the peak, and for the incoming (<code>SET</code>) traffic to be spikier after that as well. I wonder if improved site performance caused a malfunctioning spider to stop hammering my site quite so much, or something. I do know there are a bunch of spiders that have historically been pretty aggressive.</p><p>Of course the most important metric &ndash; page load time &ndash; has ended up <em>exactly</em> as I expected, with it dropping to an average of 2ms for everything and it only being that high because of hourly spikes. I guess the fact Munin is the still seeing the spikes means that Munin is keeping my cache warm (for a handful of pages), so, thanks Munin!</p><p><a href="http://publ.beesbuzz.biz/blog/287-Caching-stats-update"><img src="http://publ.beesbuzz.biz/static/_img/38/811e/http_loadtime-pinpoint=1580929669-1580958694_5cb55b7b33_320x197.png" width="320" height="197" srcset="http://publ.beesbuzz.biz/static/_img/38/811e/http_loadtime-pinpoint=1580929669-1580958694_5cb55b7b33_320x197.png 1x, http://publ.beesbuzz.biz/static/_img/38/811e/http_loadtime-pinpoint=1580929669-1580958694_5cb55b7b33_640x395.png 2x" loading="lazy" alt="http_loadtime-pinpoint=1580929669,1580958694.png" title="Munin keeping the cache warm"></a></p><p>Maybe I should set the cache expiration to a prime number so that it is less likely to be touched on an exact 5-minute interval.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Publ v0.5.14 released!</title>
        <link href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released" rel="alternate" type="text/html" />
        <published>2020-02-04T17:40:14-08:00</published>
        <updated>2020-02-04T17:40:14-08:00</updated>
        <id>urn:uuid:88837d92-096d-56c5-a963-f5d39a480788</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Today I released v0.5.14 of Publ, which has a bunch of improvements:</p>
<ul>
<li>Fixed a bug in card retrieval when there&rsquo;s no summary</li>
<li>Admin panel works again</li>
<li>Markdown entry headings now get individual permalinks (the presentation of which can be templated)</li>
<li>Markdown entry headings can be extracted into an outline to be used for a <a href="http://publ.beesbuzz.biz/manual/api/115-Entry-objects#toc">table of contents</a></li>
<li>Lots of performance improvements around ToC and footnote extraction, and template API functions in general</li>
</ul>


<h2 id="273_h2_1_Entry-headings"><a href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#273_h2_1_Entry-headings"></a>Entry headings</h2><p>Because of the new entry headings, by default all headings will get an empty <code>&lt;a href&gt;</code> at the beginning; this is intended as a styling hook so that you can use stylesheet rules to add a visible anchor/link to them.</p><p>The format of headings is also templatizable, by passing a <code>heading_template</code> argument to the <code>entry.body</code>/<code>more</code> property-function things; the default value is <code>&quot;{link}&lt;/a&gt;{text}&quot;</code> but you can do, for example, <code>&quot;{link}{text}&lt;/a&gt;&quot;</code> if you want to put the entire heading into a link (although this means that having links within your headings becomes undefined), or <code>&quot;{text}&quot;</code> if you want the old behavior, or <code>&quot;{text}{link}#&lt;/a&gt;&quot;</code> if you want the anchor marker to be part of the text data (rather than styled via CSS), or whatever. Part of why I opted for the current default is that it seemed to be maximally-useful while minimally-intrusive as far as changing existing layouts.</p><p>The <code>&quot;{link}&quot;</code> template fragment can also take some further configuration; if you just want to set its CSS class name, you can do that with the <code>heading_link_class</code> configuration, and you can also set any other arbitrary HTML attributes by passing a dict to <code>heading_link_config</code>. For example, if you want them to all have a <code>title=&quot;permalink&quot;</code> you can  pass the value <code>{&quot;title&quot;:&quot;permalink&quot;}</code> for that configuration value.</p><p>Currently there is no way to have those vary across the links, however; a more robust configuration mechanism (that can perhaps take in functions or format strings or the like) is certainly possible but it felt out-of-scope for this feature.</p><h2 id="273_h2_2_Tables-of-Contents"><a href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#273_h2_2_Tables-of-Contents"></a>Tables of Contents</h2><p>I was originally just building the heading permalink generation as a standalone feature, but then I realized that while I was doing that I&rsquo;d might as well provide tables of contents, too. My original plan for ToCs was to make use of Misaka&rsquo;s built-in ToC formatter, but getting that to work alongside Publ seemed pretty challenging, especially since Publ has the multiple document section stuff that Misaka wasn&rsquo;t intended to work with. (Incidentally, I went through similar things with the built-in footnotes stuff.)</p><p>A ToC is accessible simply by looking at <code>entry.toc</code>. While it takes all of the usual HTML formatting arguments, none of them really have any effect (aside from disabling smartquotes and enabling XHTML). It does add its own argument, <code>max_depth</code>, which chooses how many levels of the ToC to show. This is relative to the highest level in the entry, so you can continue to use whatever top heading level you prefer.</p><h2 id="273_h2_3_Performance-improvements"><a href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#273_h2_3_Performance-improvements"></a>Performance improvements</h2><p>So, footnotes had a bit of a performance impact in that rendering those out also requires rendering out the entire entry multiple times, which can add up a lot. This came down to part of how Publ allows you to use template functions as if they are properties, using a too-clever wrapper called <code>CallableProxy</code>. Put briefly, this is an object that wraps a function in such a way that if you use the function directly in a Jinja context, it gets treated as if you&rsquo;re using the default return value instead. Unfortunately, there are various things at various layers of Flask that make it end up calling the function multiple times, which can be really slow &mdash; especially if the function, say, runs Markdown formatting on the entire entry.</p><p>A long time ago I had <code>CallableProxy</code> set up such that it would cache the return value of the default call, but this had other implications when I started supporting HTTPS and user login and so on. Depending on how objects got pooled and cached this could cause the wrong content to be displayed to the end user &mdash; definitely not a good thing! At the very least this would often cause bugs where outgoing links would flip-flop between being <code>http://</code> or <code>https://</code> or using the wrong hostname or the like, and in the worst case this could theoretically cause page content to display for someone who wasn&rsquo;t authorized to see it, or vice-versa (although I don&rsquo;t believe there&rsquo;s an actual way of causing this). But in any case, it was bad.</p><p>So, what I ended up doing was instead of naïvely caching the default function return, I wrapped it behind <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache"><code>@functools.lru_cache</code></a> and made the various aspects that make these functions non-idempotent part of the cache key; for now this is just the request URL and the current user.</p><p>This cut down on a lot of chaff, but there was still a long ways to go!</p><p>Both tables of contents and footnotes have global numberings, meaning rendering <code>entry.more</code> gets affected by how many of those things live in <code>entry.body</code>. There were some pretty wonky ways I was trying to keep track of that stuff, but generally-speaking this meant that rendering <code>entry.more</code> also required rendering (and discarding) <code>entry.body</code>. This could also end up rendering a whole bunch of extra images, too, if the image configuration between <code>body</code> and <code>more</code> don&rsquo;t match. There were also some attempts at caching the various fragments&#39; buffers, but this got unwieldy and unpleasant.</p><p>So, what does it do instead now?</p><p>First, there&rsquo;s a faster counting-only Markdown processor that will only count the number of footnotes and headings, which lets us do some cute optimizations especially on <code>entry.more</code>.</p><p>Next, any time a count of footnotes or headings comes about naturally based on some other bit of processing, that information gets cached for later.</p><p>This has cut down on the amount of rendering that has to take place. There&rsquo;s still some redundancies (for example, it still has to render out all of the entry content even when it&rsquo;s only trying to extract the footnotes or table of contents) but this at least cuts down on the amount of stuff that has to happen.</p><p>Combined with the <code>CallableProxy</code> optimization this means it&rsquo;s also doing <em>way</em> less work when you&rsquo;re simply checking to see if an entry has a TOC or footnotes, such as when doing:</p><figure class="blockcode"><pre class="highlight" data-language="html" data-line-numbers><span class="line" id="e273cb1L1"><a class="line-number" href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#e273cb1L1"></a><span class="line-content">{% if entry.toc %}</span></span>
<span class="line" id="e273cb1L2"><a class="line-number" href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#e273cb1L2"></a><span class="line-content"><span class="p">&lt;</span><span class="nt">nav</span> <span class="na">id</span><span class="o">=</span><span class="s">&quot;toc&quot;</span><span class="p">&gt;&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Table of Contents<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>{{entry.toc}}<span class="p">&lt;/</span><span class="nt">nav</span><span class="p">&gt;</span></span></span>
<span class="line" id="e273cb1L3"><a class="line-number" href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#e273cb1L3"></a><span class="line-content">{% endif %}</span></span>
</pre></figure><p>This still unfortunately will be doing extraneous rendering &mdash; including image renditions &mdash; as will the similar <code>entry.footnotes</code> code, but still, a 3x improvement is a lot better, even if it&rsquo;s not ideal.</p><p>An obvious next step would be to make a headings-only renderer for the table of contents. This won&rsquo;t help with footnotes, unfortunately, and there&rsquo;s no reasonable way to prevent footnotes from rendering images that are outside of footnotes (and it still needs to be able to render images for ones that <em>are</em> in footnotes), but still, a partial improvement is still better than <em>no</em> improvement.</p><h2 id="273_h2_4_So-why-is-this-still-v0.5.x"><a href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#273_h2_4_So-why-is-this-still-v0.5.x"></a>So why is this still v0.5.x?</h2><p>At some point I decided that my versioning scheme would be based on &ldquo;milestones,&rdquo; and for v0.6 my milestone is having automated testing and unit coverage set up. This seems to be kind of foolish; what I&rsquo;m doing isn&rsquo;t quite <a href="https://semver.org/">semantic versioning</a>, which says that versioning should be based on <code>major.minor.patch</code> where <code>major</code> is for backwards-incompatible changes, <code>minor</code> is for added functionality, and <code>patch</code> is for bug fixes. But if I&rsquo;d been releasing Publ with that scheme, we&rsquo;d be on, like, v2.193.0 by now or something.</p><p>Fortunately, semver does have <a href="https://semver.org/#spec-item-4">a provision for in-development software</a>:</p>
<blockquote>
<p>Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.</p></blockquote>
<p>so I think I&rsquo;m still following in the spirit with semantic versioning for now. If I ever decide to release 1.0 I&rsquo;ll have to re-evaluate my version numbering though!</p><h2 id="273_h2_5_Errata"><a href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#273_h2_5_Errata"></a>Errata</h2><p>There is an issue with watchdog v0.10.0 which causes issues with Pipenv or other lockfile-based deployment mechanisms. If you are developing on macOS and deploying on Linux, I highly recommend pinning the version with:</p><figure class="blockcode"><pre class="highlight" data-language="bash" data-line-numbers><span class="line" id="e273cb2L1"><a class="line-number" href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#e273cb2L1"></a><span class="line-content">pipenv<span class="w"> </span>install<span class="w"> </span><span class="nv">watchdog</span><span class="o">==</span><span class="m">0</span>.9.0</span></span>
<span class="line" id="e273cb2L2"><a class="line-number" href="http://publ.beesbuzz.biz/blog/273-Publ-v0.5.14-released#e273cb2L2"></a><span class="line-content">pipenv<span class="w"> </span>clean</span></span>
</pre></figure><p>See <a href="http://publ.beesbuzz.biz/faq#watchdog090">the FAQ</a> for more information.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>v0.5.12 released, and lots of documentation fixes</title>
        <link href="http://publ.beesbuzz.biz/blog/304-v0.5.12-released-and-lots-of-documentation-fixes" rel="alternate" type="text/html" />
        <published>2019-12-31T00:02:13-08:00</published>
        <updated>2019-12-31T00:02:13-08:00</updated>
        <id>urn:uuid:2a1ac309-6a63-58a3-af88-c284086ec640</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<h2 id="304_h2_1_Release-notes"><a href="http://publ.beesbuzz.biz/blog/304-v0.5.12-released-and-lots-of-documentation-fixes#304_h2_1_Release-notes"></a>Release notes</h2><p>Today I got a fire lit under me and decided to do a bunch of bug fixing and general performance improvements.</p><p>Changes since v0.5.11:</p>
<ul>
<li>Fixed a micro-optimization which was causing some pretty bad cache problems (I really should write a blog entry about this but tl;dr micro-optimizations are usually bugs in disguise)</li>
<li>Fixed an issue which was causing the page render cache to not actually activate most of the time (you <em>know</em> there&rsquo;s going to be a ramble about this below&hellip;)</li>
<li>Fixed a bunch of spurious log meessages about nested transactions</li>
<li>Refactored the way that <code>markup=False</code> works, making it available from all Markdown/HTML contexts</li>
<li>Changed <code>no_smartquotes=True</code> to <code>smartquotes=False</code> (<code>no_smartquotes</code> is retained for template compatibility) (although I missed this on <code>entry.title</code>; I&rsquo;ve already <a href="https://github.com/PlaidWeb/Publ/commit/004fb47a3c53830081579e6ae5c1133f1ca2581e">committed a fix</a> for the next version)</li>
<li>Improve the way that the page render cache interacts with templates</li>
<li>Fixed an issue where changing a template might cause issues to occur until the cache expires</li>
</ul>
<h2 id="304_h2_2_Documentation-improvements"><a href="http://publ.beesbuzz.biz/blog/304-v0.5.12-released-and-lots-of-documentation-fixes#304_h2_2_Documentation-improvements"></a>Documentation improvements</h2>
<ul>
<li>The <a href="http://publ.beesbuzz.biz/manual/deploying/1278-Self-hosting-Publ">Apache/nginx deployment guide</a> is vastly improved:

<ul>
<li>Now it uses UNIX domain sockets instead of localhost ports, making service provisioning a bit easier</li>
<li>The systemd unit is now a user unit instead of a system unit, which improves security and also allows for gentler service restarts</li>
</ul></li>
<li>The <a href="http://publ.beesbuzz.biz/manual/deploying/441-Continuous-deployment-with-git">git deployment guide</a> has been updated per the above, and also some of the code snippets are cleaned up</li>
<li>The information about <a href="http://publ.beesbuzz.biz/html-processing">HTML processing</a> and <a href="http://publ.beesbuzz.biz/image-renditions">image renditions</a> has been consolidated and cleaned up</li>
<li>Information about <a href="http://publ.beesbuzz.biz/manual/706-User-authentication">private posts</a> and <a href="http://publ.beesbuzz.biz/manual/formats/1341-User-configuration-file">user configuration</a> has also been cleaned up somewhat</li>
<li>Also lots of updates to the <a href="https://github.com/PlaidWeb/Publ-templates-beesbuzz.biz/">beesbuzz.biz Publ templates</a></li>
</ul>


<h2 id="304_h2_3_The-caching-stuff"><a href="http://publ.beesbuzz.biz/blog/304-v0.5.12-released-and-lots-of-documentation-fixes#304_h2_3_The-caching-stuff"></a>The caching stuff</h2><p>So, once upon a time, the page render cache was caching at the response level, rather than the render level, which seemed like a good idea at the time. But then I realized this was bad, and made it so that if the request was coming from a browser that could potentially return a <a href="https://httpstatuses.com/304">not modified response</a>, this would break things badly. So, in that situation it just turned the render cache off.</p><p>This of course had the silly side effect of making the rendition cache not active in precisely the situation when it should most be active!</p><p>Later I had refactored the rendition cache to cache at the render level, with the request routing and response (which are cheap) always evaluated and only the page render itself would be cached. But I forgot to remove the check above.</p><p>So, all this time, the caching system was only being used for caching&hellip; stuff that didn&rsquo;t really benefit from being cached. Like low-level file lookups, which aren&rsquo;t exactly a performance hog (and could lead to rather unfortunate issues with template locations being out-of-date until cache expiry took place).</p><p>Anyway, after getting the cache to actually work, I also realized there were a few things I could do to make stale cached renditions no longer linger. Previously, the cache key that&rsquo;s generated for a rendition just involved (essentially) the file paths of the relevant items in the URL; category templates would know about the template&rsquo;s file path and the category path, and entry templates would additionally know about the entry ID, and then at a global level it would also know about the request&rsquo;s base URL (so it would cache different hostnames and schemes differently, which also had the nice side-effect of eliminating key conflicts if two sites were configured with the same memcached key prefix but I digress).</p><p>Well, first I realized it was pretty trivial to have entries and templates express their file fingerprint as part of their cache key, so changes to templates and entries would cause immediate cache misses &ndash; meaning instant updates on the next page load. But this would only apply to content updates on entry pages, not on category pages.</p><p>So I started to go down a rabbit hole where updates to entries would also update the cache key for the category itself, which caused indexing to take a lot more time and also required storing metadata about <em>all</em> categories (and not just ones with configuration metadata) in the database, and this had a few other annoying side-effects (meaning bugs) that had to be ironed out. And it still wouldn&rsquo;t help to update category pages which change due to an update to an entry in a different category.</p><p>Then I realized that the easiest thing to do would be to have the latest file modification be part of the cache key; any content file update would then basically invalidate the entire page render cache. Given that most sites only update very infrequently this seemed like a nice tradeoff. So I started implementing that&hellip;</p><p>&hellip;and then realized that in the early days of me adding caching to Publ, <em>I had already implemented that</em> since I thought it would be useful, and it was just not being used at all! (And I had even touched this code when I was adding mypy annotations to everything, but didn&rsquo;t even think about it&hellip;)</p><p>So, now a bit of functionality that&rsquo;s been there has theoretically made the rendition cache a lot faster, even around site resets. Neat.</p><p>In any case, after all this work I decided to do some benchmarking. I used <a href="https://github.com/Gabriel439/bench"><code>bench</code></a> to time rendering the Publ tests index page, and the results were interesting:</p>
<ul>
<li><p>No cache</p><figure class="blockcode"><pre><span class="line"><span class="line-content">time                 90.92 ms   (85.06 ms .. 97.29 ms)</span></span>
<span class="line"><span class="line-content">                     0.993 R²   (0.985 R² .. 1.000 R²)</span></span>
<span class="line"><span class="line-content">mean                 87.33 ms   (86.27 ms .. 90.70 ms)</span></span>
<span class="line"><span class="line-content">std dev              2.868 ms   (968.0 μs .. 4.970 ms)</span></span>
</pre></figure></li>
<li><p>SimpleCache (in-process object store)</p><figure class="blockcode"><pre><span class="line"><span class="line-content">time                 37.22 ms   (36.19 ms .. 38.11 ms)</span></span>
<span class="line"><span class="line-content">                     0.999 R²   (0.998 R² .. 1.000 R²)</span></span>
<span class="line"><span class="line-content">mean                 38.10 ms   (37.39 ms .. 40.58 ms)</span></span>
<span class="line"><span class="line-content">std dev              2.433 ms   (469.3 μs .. 4.620 ms)</span></span>
<span class="line"><span class="line-content">variance introduced by outliers: 19% (moderately inflated)</span></span>
</pre></figure></li>
<li><p>MemcacheD</p><figure class="blockcode"><pre><span class="line"><span class="line-content">time                 38.38 ms   (37.95 ms .. 39.06 ms)</span></span>
<span class="line"><span class="line-content">                     0.999 R²   (0.999 R² .. 1.000 R²)</span></span>
<span class="line"><span class="line-content">mean                 38.21 ms   (37.92 ms .. 38.51 ms)</span></span>
<span class="line"><span class="line-content">std dev              570.3 μs   (428.0 μs .. 762.1 μs)</span></span>
</pre></figure></li>
</ul>
<p>So, at least on that fairly simple test, the tests index page runs about 2x faster with a cache present than without. (MemcacheD is a little slower than SimpleCache, but that&rsquo;s to be expected, as it has to serialize/deserialize objects over the network. Frankly I&rsquo;m surprised it&rsquo;s only that small of a difference!)</p><p>Then I decided to benchmark the main page of <a href="https://beesbuzz.biz/">my personal website</a>, which is rather more complicated. Running locally I got these results:</p>
<ul>
<li><p>No cache</p><figure class="blockcode"><pre><span class="line"><span class="line-content">time                 280.0 ms   (274.8 ms .. 284.9 ms)</span></span>
<span class="line"><span class="line-content">                     1.000 R²   (0.999 R² .. 1.000 R²)</span></span>
<span class="line"><span class="line-content">mean                 278.0 ms   (277.1 ms .. 279.7 ms)</span></span>
<span class="line"><span class="line-content">std dev              1.548 ms   (749.5 μs .. 2.023 ms)</span></span>
<span class="line"><span class="line-content">variance introduced by outliers: 16% (moderately inflated)</span></span>
</pre></figure></li>
<li><p>SimpleCache</p><figure class="blockcode"><pre><span class="line"><span class="line-content">time                 19.32 ms   (19.19 ms .. 19.42 ms)</span></span>
<span class="line"><span class="line-content">                     1.000 R²   (1.000 R² .. 1.000 R²)</span></span>
<span class="line"><span class="line-content">mean                 19.28 ms   (19.21 ms .. 19.38 ms)</span></span>
<span class="line"><span class="line-content">std dev              201.5 μs   (138.9 μs .. 289.7 μs)</span></span>
</pre></figure></li>
<li><p>MemcacheD</p><figure class="blockcode"><pre><span class="line"><span class="line-content">time                 20.85 ms   (20.62 ms .. 21.13 ms)</span></span>
<span class="line"><span class="line-content">                     0.999 R²   (0.998 R² .. 1.000 R²)</span></span>
<span class="line"><span class="line-content">mean                 20.57 ms   (20.44 ms .. 20.74 ms)</span></span>
<span class="line"><span class="line-content">std dev              341.0 μs   (254.2 μs .. 511.7 μs)</span></span>
</pre></figure></li>
</ul>
<p>So, yeah, 14x faster&hellip; And my site feels way more responsive now, too, at least when Pushl isn&rsquo;t thrashing the cache.</p><p>Time will tell just how much of a difference this makes in practical terms; I&rsquo;ve had <a href="http://munin-monitoring.org/">munin</a> monitoring my MemcacheD for a while and the graphs made it look like it was pretty effective but it was of course not actually monitoring anything useful. But here&rsquo;s some graphs of the last week:</p><p><a href="http://publ.beesbuzz.biz/blog/304-v0.5.12-released-and-lots-of-documentation-fixes"><img src="http://publ.beesbuzz.biz/static/_img/4d/c3a4/memcached_bytes-week-20191231_dd3a3e801a_320x180.png" width="320" height="180" srcset="http://publ.beesbuzz.biz/static/_img/4d/c3a4/memcached_bytes-week-20191231_dd3a3e801a_320x180.png 1x, http://publ.beesbuzz.biz/static/_img/4d/c3a4/memcached_bytes-week-20191231_dd3a3e801a_640x360.png 2x" loading="lazy" alt="memcached_bytes-week-20191231.png" title="MemcacheD bytes"></a><a href="http://publ.beesbuzz.biz/blog/304-v0.5.12-released-and-lots-of-documentation-fixes"><img src="http://publ.beesbuzz.biz/static/_img/50/5b4f/memcached_counters-week-20191231_98eea433c8_320x189.png" width="320" height="189" srcset="http://publ.beesbuzz.biz/static/_img/50/5b4f/memcached_counters-week-20191231_98eea433c8_320x189.png 1x, http://publ.beesbuzz.biz/static/_img/50/5b4f/memcached_counters-week-20191231_98eea433c8_640x377.png 2x" loading="lazy" alt="memcached_counters-week-20191231.png" title="MemcacheD counters"></a><a href="http://publ.beesbuzz.biz/blog/304-v0.5.12-released-and-lots-of-documentation-fixes"><img src="http://publ.beesbuzz.biz/static/_img/3e/0b68/memcached_rates-week-20191231_11ae089b24_320x202.png" width="320" height="202" srcset="http://publ.beesbuzz.biz/static/_img/3e/0b68/memcached_rates-week-20191231_11ae089b24_320x202.png 1x, http://publ.beesbuzz.biz/static/_img/3e/0b68/memcached_rates-week-20191231_11ae089b24_640x403.png 2x" loading="lazy" alt="memcached_rates-week-20191231.png" title="MemcacheD rates"></a></p><p>In a week or so I&rsquo;ll see what they&rsquo;re like and if there&rsquo;s any difference. I&rsquo;m also just realizing that my &ldquo;HTTP load time&rdquo; graph isn&rsquo;t actually very useful so I need to configure Munin more appropriately.</p><p>I&rsquo;m also not entirely sure what those semi-regular spikes in MemcacheD traffic have been; it&rsquo;s unfortunately not easy to tell what individual things are using MemcacheD since it&rsquo;s just a big ol&#39; global key-value store, more or less.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Goodbye peewee, hello PonyORM</title>
        <link href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM" rel="alternate" type="text/html" />
        <published>2018-09-19T02:27:21-07:00</published>
        <updated>2018-09-19T02:27:21-07:00</updated>
        <id>urn:uuid:26ccac61-8792-54c6-8681-eb173adee58c</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>For a number of reasons, I have replaced the backing ORM. Previously I was using peewee, but now I&rsquo;m using <a href="http://ponyorm.com">PonyORM</a>. The primary reason for this is purely ideological; I do not want to use software which is maintained by someone with a track record of toxic behavior.  peewee&rsquo;s maintainer responds to issues and feature requests with shouting and dismissive snark; PonyORM&rsquo;s maintainer responds with helpfulness and grace. I am a <a href="//beesbuzz.biz/7502">strong proponent of the latter</a>.</p><p>PonyORM&rsquo;s API is also significantly more Pythonic, and rather than abusing operator overloads for clever query building purposes, it abuses Python&rsquo;s AST functionality to parse <em>actual Python expressions</em> into SQL queries. Seriously, <a href="https://stackoverflow.com/questions/16115713/how-pony-orm-does-its-tricks">look at this explanation of it</a> and tell me that isn&rsquo;t just <em>amazing</em>.</p>

<p>There are a few downsides to Pony so far, though:</p>
<ul>
<li><p>While it&rsquo;s possible to adapt arbitrary types into database fields, queries don&rsquo;t actually work on them (so at least for Enums I have to convert at query time, which turns out to not be a huge deal)</p></li>
<li><p>There&rsquo;s no simple way to incrementally build a query with an OR branch in it (which I don&rsquo;t actually use anywhere at present but I did have to rework some query API stuff to do that)</p></li>
<li><p>Not really a downside but Pony treats <code>&#39;&#39;</code> and <code>NULL</code> as equivalent, which has some fun implications for storing empty strings in a table</p><p>Of course, SQLite does this too, internally, and my existing code for that case wasn&rsquo;t actually &ldquo;correct&rdquo; (but it happened to work with SQLite anyway). So moving to Pony meant I had to make this <em>actually correct</em> which, on the plus side, means that Publ is more likely to work with MySQL or Postgres (which I haven&rsquo;t tested yet)</p></li>
</ul>
<p>In addition to PonyORM I evaluated a few other options; my other front-runner was to simply store all of the data in in-memory tables and using <code>sorted([e for e in model.Entry where e.foo &gt; bar])</code> or whatever. Which was a gigantic pain to think about. Granted, a lot of what made it painful is stuff I had to do in order to support Pony as well (namely the switch from a query-building syntax to incremental list comprehensions), but the Pony approach happens to also be way more efficient since it can use indexes and also does all the filtering at once and so on.</p><p>Anyway, I&rsquo;m rambling here. How about we look at some quick benchmarks to see if this hurts performance! All these timings are based on building <a href="http://beesbuzz.biz">beesbuzz.biz</a>, which is getting to be a reasonably-large site at this point. These timings are based on simply running it locally on my desktop.</p><p>For the index scan I ran a simple Python script that looks like:</p><figure class="blockcode"><pre class="highlight" data-language="python" data-line-numbers><span class="line" id="e1080cb1L1"><a class="line-number" href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#e1080cb1L1"></a><span class="line-content"><span class="kn">from</span><span class="w"> </span><span class="nn">main</span><span class="w"> </span><span class="kn">import</span> <span class="n">app</span></span></span>
<span class="line" id="e1080cb1L2"><a class="line-number" href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#e1080cb1L2"></a><span class="line-content"><span class="kn">from</span><span class="w"> </span><span class="nn">publ</span><span class="w"> </span><span class="kn">import</span> <span class="n">model</span></span></span>
<span class="line" id="e1080cb1L3"><a class="line-number" href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#e1080cb1L3"></a><span class="line-content"><span class="n">model</span><span class="o">.</span><span class="n">scan_index</span><span class="p">()</span></span></span>
</pre></figure><p>which just sets up the configuration as appropriate and scans the index directly and exists. For the spidering I ran it under gunicorn with <code>gunicorn main:app</code> and used the command:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">time wget --spider -r http://localhost:8000 -X /static,/comics</span></span>
</pre></figure><p>To keep things as fair as I could I spidered the entire site once without checking the time (so that the image cache would be pre-populated, to eliminate its I/O overhead as a variable).</p><h3 id="1080_h3_1_peewee"><a href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#1080_h3_1_peewee"></a>peewee</h3><p>Initial index scan:</p><figure class="blockcode"><pre class="highlight" data-language="terminal-session" data-line-numbers><span class="line" id="e1080cb3L1"><a class="line-number" href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#e1080cb3L1"></a><span class="line-content">$ time pipenv run python ./timing-test.py</span></span>
<span class="line" id="e1080cb3L2"><a class="line-number" href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#e1080cb3L2"></a><span class="line-content"></span></span>
<span class="line" id="e1080cb3L3"><a class="line-number" href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#e1080cb3L3"></a><span class="line-content">real    0m33.809s</span></span>
<span class="line" id="e1080cb3L4"><a class="line-number" href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#e1080cb3L4"></a><span class="line-content">user    0m10.916s</span></span>
<span class="line" id="e1080cb3L5"><a class="line-number" href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#e1080cb3L5"></a><span class="line-content">sys 0m4.004s</span></span>
</pre></figure><p>Time to spider entire website:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">Total wall clock time: 20s</span></span>
<span class="line"><span class="line-content">Downloaded: 421 files, 4.4M in 1.1s (4.12 MB/s)</span></span>
<span class="line"><span class="line-content"></span></span>
<span class="line"><span class="line-content">real    0m20.514s</span></span>
<span class="line"><span class="line-content">user    0m0.285s</span></span>
<span class="line"><span class="line-content">sys 0m0.435s</span></span>
</pre></figure><p>Memory usage after spidering: around 78.6MB according to macOS Activity Monitor</p><h3 id="1080_h3_2_PonyORM"><a href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#1080_h3_2_PonyORM"></a>PonyORM</h3><p>Initial index scan:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">real    0m13.897s</span></span>
<span class="line"><span class="line-content">user    0m4.562s</span></span>
<span class="line"><span class="line-content">sys 0m2.864s</span></span>
</pre></figure><p>Website spider time:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">Total wall clock time: 20s</span></span>
<span class="line"><span class="line-content">Downloaded: 421 files, 4.4M in 1.3s (3.39 MB/s)</span></span>
<span class="line"><span class="line-content"></span></span>
<span class="line"><span class="line-content">real    0m20.041s</span></span>
<span class="line"><span class="line-content">user    0m0.335s</span></span>
<span class="line"><span class="line-content">sys 0m0.444s</span></span>
</pre></figure><p>Memory usage after spidering: 72.6MB</p><h3 id="1080_h3_3_Conclusions"><a href="http://publ.beesbuzz.biz/blog/1080-Goodbye-peewee-hello-PonyORM#1080_h3_3_Conclusions"></a>Conclusions</h3><p>PonyORM takes a little less RAM and it has faster writes. Its queries are also marginally faster. But not enough to make a meaningful difference.</p><p>Anyway, I&rsquo;m mostly just happy that this doesn&rsquo;t significantly <em>hurt</em> performance. The fact that it improves the end product while supporting positive influences in the F/OSS community is a bonus!</p><p>Anyway, the deployed site is still running Publ v0.2.3, but the first Pony-based release will come soon as v0.3.0.</p>

]]>
        </content>
    </entry>
    

    
</feed>