<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>winrickLabs Blog</title>
        <link>https://blog.winricklabs.com</link>
        <description>A detailed and to-the-point technology blog that relates to winrickLabs LLC</description>
        <lastBuildDate>Sat, 13 May 2023 00:00:00 GMT</lastBuildDate>
        <docs>http://blogs.law.harvard.edu/tech/rss</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved 2019, winrickLabs LLC</copyright>
        <item>
            <title><![CDATA[(5-13-2023) Reverse Engineering Netpanzer and Extracting Assets]]></title>
            <link>https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html</link>
            <guid>https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html</guid>
            <pubDate>Sat, 13 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[(5-13-2023) Reverse Engineering Netpanzer and Extracting Assets - Size: 15.5 kB. 10 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(5-13-2023) Reverse Engineering Netpanzer and Extracting Assets | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Sat May 13 2023</h6>
<h1 id="a-hrefhttpsblogwinricklabscom5-13-2023-reverse-engineering-netpanzer-and-extracting-assetshtmlreverse-engineering-netpanzer-and-extracting-assetsa"><a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html">Reverse Engineering Netpanzer and Extracting Assets</a></h1>
<p><em>Article size is 15.5 kB and is a 10 min read</em></p>
<p>A free game I used to love to play around 2006 was NetPanzer. The only machines I had access to were free Pentium III machines,
which I threw Linux on, so that kinda limited me to games like NetPanzer and WolfET.</p>
<p>This was also the game where, in my young youth, learned through the chat feature how much people hated Americans :)</p>
<p>Recently I&#39;d been fixing bugs and stuff in WolfET (a little in 2014, and then again recently), and wondered what was up
with NetPanzer.</p>
<h2 id="the-current-state-of-things">The Current State of Things</h2>
<p>The game, today, does not run. If you install NetPanzer from your repo&#39;s package manager and start it, it will crash.</p>
<p>If you run it from the shell, you&#39;ll see that&#39;s because it can&#39;t reach the master server, and then it segfaults.</p>
<p><code>netpanzer.info</code> is no longer online in 2023. I found an archived forum post from 2019 of a maintainer asking for help
with the project. It seems soon after, when nobody replied, the project slowly died and went offline.</p>
<p>UPDATE May 9th: It seems after bugging the maintainer, about a month later, netpanzer.info is back online, and they restored the master
server. So you can play NetPanzer again! I am also working with the maintainer to run a second master serer. I&#39;ve already thrown up one 0.8.x
dedicated server that supports up to 100 players.</p>
<h2 id="a-new-map-builder">A New Map Builder?</h2>
<p>One of the issues with the existing game is that, while you can run your own server, you can&#39;t create new maps.</p>
<p>The map building tool is long gone, and the asset storage is not immediately obvious. So how do we go about fixing that? Custom maps
allow people to mod the game.</p>
<p>One idea I&#39;ve been floating around is adding the ability to build and import maps from an existing popular tool. So let&#39;s see
if we can figure out how the game assets are managed.</p>
<h1 id="extracting-the-assets">Extracting The Assets</h1>
<p>The first assets that are easy to extract are the game flags and icons, which are in BMP format. This gave an early
clue as to how the game assets are stored elsewhere - as raw, uncompressed, bitmaps.</p>
<p>We find some interesting files in a <code>wads</code> folder:</p>
<ul>
<li><code>wads/netp.act</code></li>
<li><code>wads/netpmenu.act</code></li>
<li><code>wads/summar12mb.tls</code></li>
</ul>
<p>... and in <code>units/pics/pak</code> we find many files ending in <code>.pak</code> files, with the unit names (like <code>ArchHNSD.pak</code>).</p>
<p>Games of this era commonly stored the assets in &quot;.pak&quot; files, but they all used their own format, the exception being IDTech 3 engine
games (like WolfET) which all used the same format designed by John Carmack.</p>
<p>NetPanzer is not an IDTech 3 game, and uses a custom binary format.</p>
<p>So let&#39;s reverse engineer the pak files.</p>
<h2 id="the-different-file-types">The Different File Types</h2>
<p>There are a few binary formats.</p>
<p>The <code>.pak</code> files are purely a header in binary, with the bitmap image data after.</p>
<p>To read the files, we simply seek through X number of bytes at a time:</p>
<pre><code>const unit_contents = fs.readFileSync(&#39;units/pics/pak/TitaHNSD.pak&#39;);
const unit_buffer = SmartBuffer.fromBuffer(unit_contents);
console.log(&#39;unit version&#39;, unit_buffer.readInt32LE());
console.log(&#39;unit x&#39;, unit_buffer.readInt32LE());
console.log(&#39;unit y&#39;, unit_buffer.readInt32LE());
console.log(&#39;unit frame_count&#39;, unit_buffer.readInt32LE());
console.log(&#39;unit fps&#39;, unit_buffer.readInt32LE());
console.log(&#39;offset_x&#39;, unit_buffer.readInt32LE());
console.log(&#39;offset_y&#39;, unit_buffer.readInt32LE());</code></pre><p>Whereas, the unit metadata is stored in <code>/profiles</code> as text:</p>
<pre><code>hitpoints =  120;
attack =  350;
reload =  110;
range =  48;
regen =  90;
defend_range =  119;
speed_rate =  16;
speed_factor =  2;
image =  &quot;pics/menus/vehicleSelectionView/archer.bmp&quot;
bodysprite =  &quot;units/pics/pak/ArchHNSD.pak&quot;
bodyshadow =  &quot;units/pics/pak/ArchHSSD.pak&quot;
turretsprite =  &quot;units/pics/pak/ArchTNSD.pak&quot;
turretshadow =  &quot;units/pics/pak/ArchTSSD.pak&quot;
soundselected =  &quot;mu-selected&quot;
soundfire =  &quot;mu-fire&quot;
weapon =  &quot;DOUBLEMISSILE&quot;
boundbox =  40</code></pre><p>So what about the <code>.act</code> and <code>.tls</code> files?</p>
<p>Well, the <code>.act</code> files are color palettes. It turns out that the pak files, and the <code>.tls</code> files, simply store offsets
into the <code>.act</code> files. The reduces the amount of data stored in each map and unit file. We simply store the colors once, and can
use a single 8 bit integer to index into one of the <code>256</code> colors to save disk space.</p>
<h2 id="extracting-game-units">Extracting Game Units</h2>
<p>Extracting the game unit assets was quite easy. A bit of time was spent doing this myself, and then I found a file called
<code>pak2bmp.cpp</code> in the original code</p>
<p>First was to figure out the build system, which uses <code>scons</code>. <code>scons</code> uses a Python file to define the build process, like so:</p>
<pre><code>def globSources(localenv, sourcePrefix, sourceDirs, pattern):
    sources = []
    sourceDirs = Split(sourceDirs)
    for d in sourceDirs:
        sources.append(glob.glob( sourcePrefix + &#39;/&#39; + d + &#39;/&#39; + pattern))
    sources = Flatten(sources)
    targetsources = []
    for s in sources:
        targetsources.append(buildpath + s)

    return targetsources</code></pre><p>The build script is a ~280 line Python script. After installing <code>scons</code> the build promptly fails with <code>print not found</code> - as the
file was written for Python 2. Luckily converting it to Python 3 was quite easy, so onward...</p>
<p>I have no idea how to change the default build target with scons to just build our <code>pak2bmp.cpp</code> tool. Eventually I realized I can
just change the default in the <code>SContruct</code> file:</p>
<pre><code>pak2bmp = env.Program( binpath+&#39;pak2bmp&#39;+exeappend, &#39;support/tools/pak2bmp.cpp&#39;)
Alias(&#39;pak2bmp&#39;,pak2bmp)

bmp2pak = env.Program( binpath+&#39;bmp2pak&#39;+exeappend, &#39;support/tools/bmp2pak.cpp&#39;)
Alias(&#39;bmp2pak&#39;,bmp2pak)

Default(map2bmp)</code></pre><p>This worked, however I ran into compilation issues with how the codebase was extending <code>std::exception</code> for the networking layer (I would later fix this, but that&#39;s for a separate post).</p>
<p>Eventually I decided - we don&#39;t need Networking - so after learning <code>scons</code> some more I simply omitted all the networking code from the build.</p>
<pre><code>$ ./pak2bmp 
pak2bmp for NetPanzer V 1.1
use: ./pak2bmp &lt;filename&gt; &lt;output_folder&gt; [palette_file_optional]
note: if using palette file, use only the name without extension or path
note2: even on windows the path must be separated by &#39;/&#39;
note3: YOU have to create the output directory
example for using palete (default is netp):
./pak2bmp units/pics/pak/TitaHNSD.pak titan_body netp</code></pre><p>Success!</p>
<p>We can now extract all the unit assets with a little script:</p>
<pre><code>ls units/pics/pak | xargs -i sh -c &#39;mkdir ./bmp/units/pics/{}&#39;
ls units/pics/pak/ | xargs -i sh -c &#39;./pak2bmp units/pics/pak/{} ./bmp/units/pics/{}/&#39;</code></pre><p>The unit assets are split up into three categories for each unit:</p>
<ul>
<li>The unit body, with a frame for each rotation.</li>
<li>The unit turret, with a frame for each rotation.</li>
<li>The unit shadow, again with many frames.</li>
</ul>
<p>So while in game they look like:</p>
<div class="text-center">
    <a href="images/netpanzer-units.png">
    <img 
            src="images/netpanzer-units.png"
            alt="NetPanzer Units"
            title="NetPanzer Units" />
    </a>
</div>

<p>They are actually stored like:</p>
<div class="text-center">
    <a href="images/netpanzer-units-raw.png">
    <img 
            src="images/netpanzer-units-raw.png"
            alt="NetPanzer Units Stored"
            title="NetPanzer Units Stored" />
    </a>
</div>

<h2 id="extracting-game-map">Extracting Game Map</h2>
<p>Map data. This is the hard part. There is no &quot;dump the map data&quot; script.</p>
<p>It turns out the actual tile images are stored in <code>summer12mb.tls</code>. As you could guess, this is a <strong>12mb</strong> file.
The actual maps are quite small, because they are just pointers into offsets into this file.</p>
<p>It&#39;s quite a bit more complicated than that, however. I was hoping I could just get a list of bytes, after figuring out the
pointers into the palette, and dump it to a bit bitmap file for each map, and then convert this file to another more modern format.</p>
<p>It was not going to be quite this simple. :)</p>
<p>The map data is split across a few structures.</p>
<p>First, the <code>.tls</code> file itself starts with:</p>
<pre><code>class TILE_DBASE_HEADER
{
public:
    unsigned char    netp_id_header[64];
    unsigned short    version;
    unsigned short    x_pix;
    unsigned short    y_pix;
    unsigned short    tile_count;
    unsigned char    palette[768];
}</code></pre><p>The map tiles also have a list of this structure, which stores the <code>move_value</code> (how fast a unit can move, if at all. The game uses A*):</p>
<pre><code>class TILE_HEADER
{
public:
    char    attrib;
    char    move_value;
    char    avg_color;
}</code></pre><p>...and finally a uint8 of map_data.</p>
<p>Eventually I figured out how the game loads and renders maps, so I could stop reverse engineering the structure and create my own
<code>map2bmp.cpp</code>:</p>
<p>First, we need to &quot;start the map loading&quot;, which takes a map name, whether or not to load the map tiles, and the
number of partitions to load it into:</p>
<pre><code>MapInterface::startMapLoad(filename.c_str(), true, 1);</code></pre><p>Then there is a function we are supposed to repeatedly call to load the map, which takes a pointer to an integer
that is updated to track the progress:</p>
<pre><code>int percent_complete;
char strbuf[256];

while( MapInterface::loadMap( &amp;percent_complete ) == true ) {
    sprintf( strbuf, &quot;Loading Game Data ... (%d%%)&quot;, percent_complete);
}

printf(&quot;Map loaded!\n&quot;);</code></pre><p>Now let&#39;s create an SDL1 <code>Surface</code> to write the map to:</p>
<pre><code>int total_width = MapInterface::getMap()-&gt;getWidth();
int total_height = MapInterface::getMap()-&gt;getHeight();
Surface unpacked(total_width, total_height, 1);

SDL_Surface *surf = SDL_CreateRGBSurfaceFrom(unpacked.getFrame0(),
                                              unpacked.getWidth(),
                                             unpacked.getHeight(),
                                             8,
                                             unpacked.getPitch(), // pitch
                                             0,0,0,0);


if ( ! surf )
{
    printf(&quot;surface is null!&quot;);
}</code></pre><p>Load our palette and tell SDL about it:</p>
<pre><code>Palette::loadACT(palettefile);
SDL_SetColors(surf, Palette::color, 0, 256);</code></pre><p>Now we can clear our packed surface and write the tiles:</p>
<pre><code>unpacked.fill(0);
for (int x = 0; x &lt; MapInterface::getMap()-&gt;getWidth(); x++) {
    for (int y = 0; y &lt; MapInterface::getMap()-&gt;getHeight(); y++) {
//        pak.setFrame(n);
        blitTile(unpacked, MapInterface::getMap()-&gt;getValue(x, y), x + 32, y + 32);
    }
}</code></pre><p>The <code>blitTile</code> function was not easily accessible, as I could not get enough of the project to compile as the time to use it. Luckily
we can just copy it into our script:</p>
<pre><code>void blitTile(Surface &amp;dest, unsigned short tile, int x, int y)
{
    PIX * tileptr = TileInterface::getTileSet()-&gt;getTile(tile);

    int lines = 32;
    int columns = 32;

    if ( y &lt; 0 )
    {
        lines = 32 + y;
        tileptr += ((32-lines)*32);
        y = 0;
    }

    if ( x &lt; 0 )
    {
        columns = 32 + x;
        tileptr += (32-columns); // advance the unseen pixels
        x = 0;
    }

    PIX * destptr = dest.getFrame0();
    destptr += (y * dest.getPitch()) + x;


    if ( y + 32 &gt; (int)dest.getHeight() )
    {
        lines = (int)dest.getHeight() - y;
    }

    if ( x + 32 &gt; (int)dest.getWidth())
    {
        columns = (int)dest.getWidth() - x;
    }

    PIX * endptr = destptr + (lines * dest.getPitch());

    for ( /* nothing */ ; destptr &lt; endptr; destptr += dest.getPitch())
    {
        memcpy(destptr,tileptr,columns);
        tileptr +=32;
    }

}</code></pre><p>This is quite a fun piece of code because it uses pointer arithmetic. Instead of working with an array, we simply get
an address of memory and offset the address as we copy the memory to our surface based on the &quot;pitch&quot; of bytes in the format, which
is number of bytes in between &quot;rows&quot;. The array syntax in C is just sugar for doing this (why we don&#39;t use the sugar I don&#39;t know).</p>
<p>This is the final version of the code. It took a few iterations to get it right:</p>
<div class="text-center">
    <a href="images/netpanzer-map-attempt-1.png">
    <img 
            src="images/netpanzer-map-attempt-1.png"
            alt="Bad Neuburg Extracted Badly (#1)"
            title="Bad Neuburg Extracted Badly (#1)" />
    </a>
</div>

<p>Our second attempt, a very blurry map:</p>
<div class="text-center">
    <a href="images/netpanzer-map-attempt-2.png">
    <img 
            src="images/netpanzer-map-attempt-2.png"
            alt="Bad Neuburg Extracted Badly (#2)"
            title="Bad Neuburg Extracted Badly (#2)" />
    </a>
</div>

<p>Then a higher quality map that is obviously messed up:</p>
<div class="text-center">
    <a href="images/netpanzer-map-attempt-3.png">
    <img 
            src="images/netpanzer-map-attempt-3.png"
            alt="Bad Neuburg Extracted Badly (#3)"
            title="Bad Neuburg Extracted Badly (#3)" />
    </a>
</div>

<p>... and finally! The resulting image is uncompressed 11k x 11k pixels. It&#39;s 121 megabytes as a BMP file, or 47mb as a webp with no percieved loss of quality.</p>
<p>Click to enlarge.</p>
<div class="text-center">
    <a href="images/netpanzer-bad-neuburg-full.webp">
    <img 
            src="images/netpanzer-bad-neuburg-cover.png"
            alt="NetPanzer Bad Neuburg Extracted"
            title="NetPanzer Bad Neuburg Extracted" />
    </a>
</div>

<h2 id="conclusion">Conclusion</h2>
<p>I&#39;ll be happy to see a new community form around the game if we can add map creation and improve
the hosting story around the server, and maybe some central stats/prestige tracking.</p>
<p>It&#39;s neat to see what they did to compress the assets, whereas today we might just end up with a &quot;tiny&quot; 1GB game on steam. :)</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(4-26-2020) Making FastComments Realtime]]></title>
            <link>https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html</link>
            <guid>https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html</guid>
            <pubDate>Sun, 26 Apr 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[(4-26-2020) Making FastComments Realtime - Size: 8.3 kB - 4.15 kB. 7 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(4-26-2020) Making FastComments Realtime | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Sun Apr 26 2020</h6>
<h1 id="a-hrefhttpsblogwinricklabscom4-26-2020-making-fastcomments-realtimehtmlmaking-fastcomments-realtimea"><a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html">Making FastComments Realtime</a></h1>
<p>One great part of trying to sell a product is that you get a lot of great, free ideas. Sometimes that person even decides to be
your customer - after you&#39;ve bugged them - and your product wasn&#39;t good enough.</p>
<p>I contacted one company that basically sold pub-sub solutions, who was using a competitor&#39;s comment system on their site. Trying to
sell them on FastComments they politely said no, however they suggested it&#39;d be cool if FastComments was real time and that they&#39;d
give me a great discount on their pub-sub service.</p>
<p>Sales tactics aside, I looked at my TODO list. I had some things I really wanted to do, but I couldn&#39;t stop thinking about making FastComments real time.
It bugged me in the shower, and while I was working on other features. So I pulled the plug and over the weekend - <a href="https://fastcomments.com" target="_blank">FastComments</a> is now real time!</p>
<p>Did I use the potential customer&#39;s pub-sub service? No. It was far too expensive. Running an Nchan/Nginx cluster is a very easy, secure, and scalable way
to handle fan out. I had experienced doing it with Watch.ly. Nchan can handle way more connections and throughput than most applications will ever see. So, Nchan will
handle our Websocket connections.</p>
<p>I decided on a pub-sub approach - there would be a publicly exposed /subscribe endpoint that your browser connects to. It cannot send messages over this
connection - it can only receive. Then, there&#39;s a /publish endpoint that only listens on a local ip that the backend instances have access to.</p>
<h3 id="scaling-nchan">Scaling Nchan</h3>
<p>Scaling Nchan is relatively easy - so nothing very interesting here. You deploy more instances, add a Redis cluster, and then do the things required for your OS
to make it accept more connections than default.</p>
<h3 id="deploying-nchan">Deploying Nchan</h3>
<p>Deployment takes about two seconds using Orcha, the build/deployment software I&#39;ve been working on. For example, here&#39;s the complete file structure
for defining a deployment of Nchan right now. I just push a change to the repo and done - the cluster updates with the latest configuration. Similar to terraform or ansible. </p>
<p>Orcha will be open sourced at some point.</p>
<p><img 
    src="https://blog.winricklabs.com/images/fc-ws-config.png"
    alt="FastComments WS Config"
    class='lozad'
    height="250px" /></p>
<h3 id="architecture-and-problems">Architecture and Problems</h3>
<p>The following problems had to be solved/decisions to be made:</p>
<ol>
<li>When to push new comment and vote events.</li>
<li>What the UI should do when a new comment or vote has been received.</li>
<li>How to re-render part of the widget safely to update the &quot;Show New X Comments&quot; button.</li>
<li>How to do this without increasing the client side script size substantially.</li>
</ol>
<h3 id="when-to-push-new-comment-and-vote-events">When to push new comment and vote events.</h3>
<p>This might sound like an easy question, but the problem is that when an event is pushed we need to know who to fire the event to.
We solve that by using channels - the ID representing a URL (basically a clean version of the URL) - is what the client listens on for new messages.</p>
<p>The backend says, here&#39;s a new comment for this URL ID and Nchan forwards the message to all the respective clients.</p>
<p>So here&#39;s our first problem. The person that sent the comment is also on that page, so they&#39;ll get the event. How do you efficiently do a de-dupe in the UI to prevent
that extra comment from showing? We can&#39;t do it server-side. Our fan-out mechanism should remain very simple and efficient, and Nginx/Nchan does not support this behavior. It also
couldn&#39;t, since it doesn&#39;t know the event originated from the user. From Nchan&#39;s perspective, it originates from our servers.</p>
<p>Obviously we need to do a check - see if that comment is already in memory on the client. The approach I went with was to create a broadcast id on the client, pass that along
with the request, and then that id is included in the event sent to each client. If the broadcast id in the client&#39;s list of broadcast ids, we know we can ignore that event. </p>
<p>This may seem like an extra step. Why not just not render the first comment and let the event from the pubsub system cause the comment to be rendered? The
first reason is that this may cause some lag which does not feel satisfying. The other reason is that the event will not be broadcast if the comment had been marked as spam in realtime, for example.
However, you&#39;ll still want to show the user the comment they just posted. So we can&#39;t rely on the message API for my own feedback when posting a comment - you&#39;re asking
for trouble.</p>
<p>We do the same things for vote events to prevent a single click causing the number from jumping twice locally.</p>
<p>So to recap this approach does two things.</p>
<ol>
<li>Prevent race conditions.</li>
<li>Ensure the correct people see the correct comments and vote changes in real time.</li>
</ol>
<h3 id="what-the-ui-should-do-when-a-new-comment-or-vote-has-been-received">What the UI should do when a new comment or vote has been received.</h3>
<p>First all, we definitely don&#39;t want to be rendering new comments as they are posted. This would be distracting from the content someone may be trying to
read or interact with. So what we need to do is render little buttons like &quot;5 New Comments - Click to View&quot; in the right places.</p>
<p>What we want to do is show a count on the first visible parent comment like:</p>
<p>&quot;8 New Comments - Click to show&quot;</p>
<p>This means that if you could have:</p>
<pre><code>Visible Comment (2 new comments, click to show)
    - Hidden New Comment
        - Hidden New Comment</code></pre><p>So we need to walk the tree to find the first visible parent comment and then calculate how many hidden comments are under that parent.</p>
<p>We also need to insert the new comment at the right point in the tree, and mark it as hidden for now. This way if something triggers
a full re-render of the tree the comment is not rendered.</p>
<p>If the received comment doesn&#39;t have a parent - like when replying to a page instead of a parent comment - we can just
treat the page as the root and don&#39;t have to walk any tree.</p>
<p>We make an assumption: We should always receive the events in order so that we can just walk
the tree backwards.</p>
<p>Then we just need to render that text/button. Clicking it then goes and changes the hidden
flag on said comments and re-renders that part of the tree.</p>
<p>Here it is in action! It&#39;s pretty satisfying to watch and use.</p>
<p><video src="images/fc-realtime-demo.mp4" autoplay="true" controls="true" alt="FastComments Realtime" title="FastComments Realtime"></video></p>
<h3 id="how-to-do-this-without-increasing-the-client-side-script-size-substantially">How to do this without increasing the client side script size substantially?</h3>
<p>This is always the fun part of FastComments - optimization. In this case the story is really boring. It just wasn&#39;t much code. The logic
is pretty straight forward. There&#39;s some simple graph/tree traversal but that is pretty compact since our rendering mechanism had been kept simple.</p>
<p>I&#39;m very happy with how things turned out - let me know what questions you have, and I&#39;ll happily update this article. Cheers.</p>
<h3 id="update-2022">Update 2022</h3>
<p>FastComments now no longer uses Nginx with Nchan. We found it to have stability problems that the community and original author
were not able to figure out. It would randomly stop responding to publish events and hang.</p>
<p>FastComments now uses its own custom PubSub system in Java built on top of Vertx. This has also allowed us to build some really cool features
that would have otherwise been too expensive, as they would have required us to store extra state in a database.</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(04-02-2020) Just How Fast is EJS]]></title>
            <link>https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html</link>
            <guid>https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html</guid>
            <pubDate>Thu, 02 Apr 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[(04-02-2020) Just How Fast is EJS - Size: 21.2 kB - 102 kB. 14 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(04-02-2020) Just How Fast is EJS | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Thu Apr 02 2020</h6>
<h1 id="a-hrefhttpsblogwinricklabscom04-02-2020-just-how-fast-is-ejshtmljust-how-fast-is-ejsa"><a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html">Just How Fast is EJS?</a></h1>
<p><em>Article size is 21.2 kB - 102 kB and is a 14 min read</em></p>
<p>With <a href="https://fastcomments.com" target="_blank">FastComments</a> most of our server-side rendered pages are served using the EJS ExpressJS plugin. Since
we&#39;re using NodeJS this makes sense for us. EJS is fast, and easy to write. The amount of abstraction is also low since the library is fairly small, and I think everyone
knows how big of a fan I am of the right (and less) abstractions.</p>
<p>So EJS works well for our marketing site and administration app with pages being able to be rendered in single-digit milliseconds. But what about the really heavily
hit stuff - the comment widget itself?</p>
<h4 id="quick-background---how-the-comment-widget-works">Quick Background - How The Comment Widget Works</h4>
<p>To load the comment widget, your browser has to make the following requests:</p>
<ol>
<li>embed.min.js</li>
<li>/embed endpoint</li>
<li>comment-ui.min.js</li>
<li>/comments API</li>
</ol>
<p>The reason for a couple more calls than you might expect is because of the need of an iframe, for security reasons. That&#39;s explained in more detail in
<a href="/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html" target="_blank">this post</a>.</p>
<p>The script (#1) is needed to control the iframe - its styling, the auto resizing, etc.</p>
<p>The /embed endpoint (#2) loads our iframe content.</p>
<p>The comment-ui script (#3) contains the code for the widget and #4 is the api call to actually get the comments, current logged-in user information, and translations.</p>
<p>So what we&#39;re talking about here is #2.</p>
<h4 id="just-cache-it-dude">Just cache it dude</h4>
<p>The problem with caching in this scenario is when items fall out of the cache. For FastComments to properly live up to its name, we don&#39;t want to risk
the 1% percentile latency being bad. So that /embed endpoint should always be fast.</p>
<p>So let&#39;s benchmark.</p>
<h4 id="benchmark---template-literal">Benchmark - Template Literal</h4>
<p>First let&#39;s get a baseline. Let&#39;s compare EJS to the fastest thing we can - returning a string. No caching, although in production currently we use an LRU cache.</p>
<p>What if the endpoint was just a string literal?</p>
<p><img 
    src="https://blog.winricklabs.com/images/fastcomments-embed-string.png"
    alt="FastComments Embed Endpoint as String Literal"
    class='lozad'
    height="400px" /></p>
<p>We&#39;ll use autocannon with the default settings to run our test, using and endpoint with actual parameters taken from production.</p>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬─────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max     │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼─────────┤
│ Latency │ 2 ms │ 2 ms │ 4 ms  │ 5 ms │ 2.25 ms │ 0.69 ms │ 15.7 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴─────────┘
┌───────────┬─────────┬─────────┬────────┬─────────┬─────────┬────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%    │ 97.5%   │ Avg     │ Stdev  │ Min     │
├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼────────┼─────────┤
│ Req/Sec   │ 2175    │ 2175    │ 3869   │ 4001    │ 3631    │ 555.19 │ 2174    │
├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 3.54 MB │ 3.54 MB │ 6.3 MB │ 6.52 MB │ 5.91 MB │ 904 kB │ 3.54 MB │
└───────────┴─────────┴─────────┴────────┴─────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
36k requests in 10.08s, 59.1 MB read
</div>

<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 2 ms │ 2 ms │ 3 ms  │ 3 ms │ 2.08 ms │ 0.31 ms │ 10.98 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Req/Sec   │ 3755    │ 3755    │ 3945    │ 4015    │ 3920.82 │ 79.38  │ 3755    │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 6.12 MB │ 6.12 MB │ 6.43 MB │ 6.54 MB │ 6.39 MB │ 129 kB │ 6.12 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
43k requests in 11.08s, 70.2 MB read
</div>

<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 2 ms │ 2 ms │ 3 ms  │ 3 ms │ 2.06 ms │ 0.28 ms │ 11.24 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 3887    │ 3887    │ 3965    │ 4033    │ 3969.73 │ 41.4    │ 3887    │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 6.33 MB │ 6.33 MB │ 6.46 MB │ 6.57 MB │ 6.47 MB │ 66.9 kB │ 6.33 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
44k requests in 11.08s, 71.1 MB read
</div>

<p>You can see it gets faster each time as we make Turbofan happy and the JIT kicks in.</p>
<p>We can see that after JIT compilation our max latency is 11.24ms with the lowest requests/second over ten seconds being 3887.</p>
<p>On kind of unrelated tangent, I wanted to show something real quick. Logging in NodeJS is slow, even with the best of frameworks. How slow?</p>
<p>Well, using connect w/ log4js, logging a line like this with each request:</p>
<div class="code">
[2020-04-02T00:04:05.642] [DEBUG] express - ::1 - - "GET /embed?config=%7B%22tenantId%22%3A%22DTGXwVKr%22%2C%22apiHost%22%3A%22http%253A%252F%252Flocalhost%253A3001%22%2C%22urlId%22%3A%22http%253A%252F%252Flocalhost%253A63342%252Fwatchly-models%252Fscripts%252Flocal-test-lifetime.html%253F_ijt%253Dlh9s0h0fgv1t2n2ceibdot1c1g%22%7D HTTP/1.1" 200 1065 "http://localhost:63342/watchly-models/scripts/local-test-lifetime.html?_ijt=lh9s0h0fgv1t2n2ceibdot1c1g" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
</div>

<p>It has an impact - surprisingly. So let&#39;s disable logging for a second.</p>
<p>I ran it twice, here&#39;s the second run.</p>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 1 ms │ 1 ms │ 2 ms  │ 3 ms │ 1.15 ms │ 0.44 ms │ 13.68 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Req/Sec   │ 5051    │ 5051    │ 5467    │ 5623    │ 5429.28 │ 172.67 │ 5049    │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 7.78 MB │ 7.78 MB │ 8.42 MB │ 8.67 MB │ 8.36 MB │ 266 kB │ 7.78 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
60k requests in 11.08s, 92 MB read
</div>

<p>Wow! Our max latency isn&#39;t much better, but our average is cut in half, and we go from 44k requests/second to 60.
Anyway, you probably don&#39;t want to disable your logging. I&#39;d rather just deploy an extra server, so let&#39;s continue the benchmarks with logging enabled.
Also, the impact is so profound just because what we&#39;re testing is so light.</p>
<h4 id="benchmark---ejs">Benchmark - EJS</h4>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg    │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼────────┼─────────┼──────────┤
│ Latency │ 4 ms │ 4 ms │ 7 ms  │ 8 ms │ 4.4 ms │ 0.94 ms │ 19.83 ms │
└─────────┴──────┴──────┴───────┴──────┴────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%  │ Avg     │ Stdev  │ Min     │
├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼────────┼─────────┤
│ Req/Sec   │ 1327    │ 1327    │ 2223    │ 2269   │ 2072.73 │ 286.91 │ 1327    │
├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 1.81 MB │ 1.81 MB │ 3.03 MB │ 3.1 MB │ 2.83 MB │ 391 kB │ 1.81 MB │
└───────────┴─────────┴─────────┴─────────┴────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
23k requests in 11.08s, 31.1 MB read
</div>

<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 4 ms │ 4 ms │ 5 ms  │ 5 ms │ 4.11 ms │ 0.37 ms │ 12.36 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 2079    │ 2079    │ 2225    │ 2259    │ 2201.73 │ 57.87   │ 2078    │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 2.83 MB │ 2.83 MB │ 3.04 MB │ 3.08 MB │ 3 MB    │ 79.2 kB │ 2.83 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
24k requests in 11.08s, 33 MB read
</div>

<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 4 ms │ 4 ms │ 5 ms  │ 5 ms │ 4.07 ms │ 0.31 ms │ 13.22 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%  │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 2173    │ 2173    │ 2239    │ 2271   │ 2240.6  │ 28.83   │ 2173    │
├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 2.97 MB │ 2.97 MB │ 3.05 MB │ 3.1 MB │ 3.06 MB │ 38.9 kB │ 2.96 MB │
└───────────┴─────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
22k requests in 10.07s, 30.6 MB read
</div>

<p>Wow, a huge difference. We can only serve half the requests in the same amount of time and latency is doubled across the board.</p>
<p>Again remember - it may seem crazy to optimize this. But remember that the nature of FastComments - a widget hosted on a third party page - can mean
that we get flooded with tons of requests for uncached pages at once. Even with autoscaling this could cause an outage or at least a hiccup.
Being able to handle twice the uncached traffic in a spike is a big deal.</p>
<p>So let&#39;s take a step back. What if we also add an LRU cache to our template literal?</p>
<p><img 
    src="https://blog.winricklabs.com/images/fastcomments-embed-string-lru.png"
    alt="FastComments Embed Endpoint as String Literal w/ Cache"
    class='lozad'
    height="400px" /></p>
<p>First it starts off not so great, since the JIT hasn&#39;t warmed up yet.</p>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 2 ms │ 2 ms │ 4 ms  │ 5 ms │ 2.24 ms │ 0.72 ms │ 24.13 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬────────┬─────────┐
│ Stat      │ 1%     │ 2.5%   │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min     │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Req/Sec   │ 2145   │ 2145   │ 3897    │ 4087    │ 3659    │ 546.44 │ 2145    │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 3.5 MB │ 3.5 MB │ 6.35 MB │ 6.66 MB │ 5.96 MB │ 890 kB │ 3.49 MB │
└───────────┴────────┴────────┴─────────┴─────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
40k requests in 11.07s, 65.6 MB read
</div>

<p>But then!</p>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev  │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼────────┼──────────┤
│ Latency │ 2 ms │ 2 ms │ 3 ms  │ 3 ms │ 2.06 ms │ 0.3 ms │ 11.41 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 3849    │ 3849    │ 3967    │ 4037    │ 3970.8  │ 53.39   │ 3848    │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 6.27 MB │ 6.27 MB │ 6.46 MB │ 6.58 MB │ 6.47 MB │ 86.9 kB │ 6.27 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
40k requests in 10.08s, 64.7 MB read
</div>

<p>Wait, isn&#39;t that the same as our string literal test? Let&#39;s check again.</p>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 2 ms │ 2 ms │ 3 ms  │ 3 ms │ 2.08 ms │ 0.36 ms │ 11.58 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬────────┬────────┐
│ Stat      │ 1%     │ 2.5%   │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min    │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼────────┼────────┤
│ Req/Sec   │ 3699   │ 3699   │ 3975    │ 4051    │ 3932.28 │ 96.96  │ 3699   │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼────────┼────────┤
│ Bytes/Sec │ 5.7 MB │ 5.7 MB │ 6.13 MB │ 6.25 MB │ 6.06 MB │ 150 kB │ 5.7 MB │
└───────────┴────────┴────────┴─────────┴─────────┴─────────┴────────┴────────┘
Req/Bytes counts sampled once per second.
43k requests in 11.08s, 66.6 MB read
</div>

<p>Yup. Just res.send(<code>${some} stuff</code>) is faster than checking if an item is in the cache and sending it back in the case of our
micro benchmark.</p>
<p>Just for giggles, what if we just used a regular map for the cache instead of this fancy lru cache?</p>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev  │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼────────┼──────────┤
│ Latency │ 2 ms │ 2 ms │ 3 ms  │ 3 ms │ 2.06 ms │ 0.3 ms │ 11.84 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%  │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 3779    │ 3779    │ 3977    │ 4023   │ 3961.73 │ 62.58   │ 3779    │
├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 5.82 MB │ 5.82 MB │ 6.13 MB │ 6.2 MB │ 6.1 MB  │ 96.6 kB │ 5.82 MB │
└───────────┴─────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
44k requests in 11.08s, 67.1 MB read
</div>

<p>At this point what we are seeing is that most of the time is not spent actually constructing the value, but to actually enqueue it into the event loop
and send it over the wire.</p>
<p>So where do we go from here? Well, aside from making major architecture changes, let&#39;s revisit that logging thing. What if we disable logging for just that route?
It&#39;s not a big deal - we have the logs in Nginx.</p>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 0 ms │ 1 ms │ 1 ms  │ 1 ms │ 0.69 ms │ 0.49 ms │ 11.32 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬───────┬───────┬─────────┬─────────┬─────────┬────────┬───────┐
│ Stat      │ 1%    │ 2.5%  │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min   │
├───────────┼───────┼───────┼─────────┼─────────┼─────────┼────────┼───────┤
│ Req/Sec   │ 9103  │ 9103  │ 9447    │ 9663    │ 9438.91 │ 147.37 │ 9102  │
├───────────┼───────┼───────┼─────────┼─────────┼─────────┼────────┼───────┤
│ Bytes/Sec │ 14 MB │ 14 MB │ 14.6 MB │ 14.9 MB │ 14.5 MB │ 227 kB │ 14 MB │
└───────────┴───────┴───────┴─────────┴─────────┴─────────┴────────┴───────┘
Req/Bytes counts sampled once per second.
104k requests in 11.09s, 160 MB read
</div>

<p>Wow! Double the requests, half the average latency. But why?
I originally thought the reason is that this request has a giant query string that gets logged. Observe - if we remove that config from the query string and keep the request logs:</p>
<div class="code">┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 2 ms │ 2 ms │ 3 ms  │ 3 ms │ 2.04 ms │ 0.25 ms │ 11.12 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Req/Sec   │ 4035    │ 4035    │ 4347    │ 4423    │ 4324.64 │ 103.49 │ 4034    │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 4.68 MB │ 4.68 MB │ 5.04 MB │ 5.13 MB │ 5.02 MB │ 119 kB │ 4.68 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
48k requests in 11.08s, 55.2 MB read
</div>

<p>A little better, but not much! Logging, and express middleware, is just slow.</p>
<p>I think what we are going to do is what bought us the lowest latency - rewriting in Rust! Just kidding. Here:</p>
<ol>
<li>Be careful caching in NodeJS. Thrashes GC, takes longer to get from cache than to reconstruct string after JIT.</li>
<li>Don&#39;t log these requests in the Node layer.</li>
<li>Template literals.</li>
<li>Cache at the proxy layer.</li>
<li>COMMENT YOUR OPTIMIZATIONS</li>
</ol>
<p>I wouldn&#39;t apply these optimizations to other things, but really we&#39;re desperate to make sure FastComments is fast as it can be in certain areas. Engineering is all
about trade-offs. Also, be a good developer. If you&#39;re going to write code that&#39;s tough to deal with, explain why:</p>
<div class="code">worker.use('/embed', require('./routes/embed')); // OPTIMIZATION This is here so we don't log the requests, which is slow.
worker.use(express.static(path.join(__dirname, 'public'))); // OPTIMIZATION This is here so we don't log the requests, which is slow.
worker.use(log4js.connectLogger(logger, { level: 'debug' }));
</div>

<p>Also, interestingly having the express.static router first slows things down by about 30%.</p>
<p>We could also probably get better performance out of optimizing a some code returned by the /embed endpoint, but that&#39;s for another day. :)</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(02-17-2020) - Efficient Data Structures For MMO Game Backends in Java]]></title>
            <link>https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html</link>
            <guid>https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html</guid>
            <pubDate>Mon, 17 Feb 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[(02-17-2020) - Efficient Data Structures For MMO Game Backends in Java - Size: 14.8 kB - 18.6 MB. 11 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(02-17-2020) - Efficient Data Structures For MMO Game Backends in Java | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Mon Feb 17 2020</h6>
<h1 id="a-hrefhttpsblogwinricklabscom02-17-2020---efficient-data-structures-for-mmo-game-backends-in-javahtmlefficient-data-structures-for-mmo-game-back-ends-in-javaa"><a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures for MMO Game Back-ends in Java</a></h1>
<p><em>Article size is 14.8 kB - 18.6 MB and is a 11 min read</em></p>
<p>... and how I almost rewrote the backend in Rust, but didn&#39;t.</p>
<p>Java gurus beware - none of this is new news. However, it is interesting, and I ran into it recently for the first time dealing with large heaps.
Most Java applications I&#39;ve worked on either had data on the heap very short-lived like per-request or the heap allocation was very temporary (like batch processing).</p>
<h4 id="the-context">The Context</h4>
<p>However, for the past six months I&#39;ve been working on <a href="https://tdworldgame.com">TDWorld</a>. TDWorld can best be described as a global-scale
tower defense MMO. That&#39;s a mouthful, but that&#39;s what it is. It&#39;s a location based game where you claim buildings and connect them. For example,
let&#39;s say you claim your house and then the supermarket down the street. You connect them, and if your creeps make it from your house to the supermarket
you get in-game currency. The game plays on a real map - using real roads and buildings. Your enemies can take a building in between your house and that supermarket,
level it up, and stop your creeps from reaching their destination. Hence, the strategy part.</p>
<h4 id="the-problem">The Problem</h4>
<p>We have a few requirements. We need to be able to simulate vehicle traffic, in the form of in-game creeps, and also support queries that allow towers to
efficiently find the nearby creeps to attack. On top of this we need to sync state with your phone. It&#39;s not a small task.</p>
<p>On top of that, we don&#39;t have a lot of money. I started with MapBox for the traffic routing and quickly racked up thousands of dollars in API requests during
testing. This was a big no-no, so we now run our own instance of the routing server.</p>
<p>For a quick example, here&#39;s a very early version of the client that rendered an android TextureView over MapBox&#39;s SurfaceView by using reflection to grab
its contents and overwrite it (Android devs will know how dirty of a hack this is). But it lets us demonstrate the game. Don&#39;t worry, I&#39;m getting to the technical stuff soon.</p>
<h5 id="interesting-update">Interesting Update</h5>
<pre><code>As of 2022 The game is now built in a custom 3D OSRM renderer on top of libgdx. The article continues with the old 2d renderer.</code></pre><p>This example is playing the game in Redwood City. Ignore the graphics. Just see how the creeps follow the roads and the towers have to shoot them.
Everyone on their phone should see the same thing as you.</p>
<p><img 
    src="https://blog.winricklabs.com/images/map.gif"
    alt="TDWorld Example!"
    title="TDWorld Example!"
    class='lozad' /></p>
<p>This demonstrates the kind of queries we need to support. For this we make heavy use of <a href="https://github.com/davidmoten/rtree" target="_blank">David Moten&#39;s rtree</a>.</p>
<p>That&#39;s not so much what this post is about, I can write a separate post about immutable rtrees. This is about reducing memory, because oh man did we have a problem with that.</p>
<h5 id="interesting-update-1">Interesting Update</h5>
<pre><code>As of 2022, we now use a custom grid-based geospacial index, instead of R-trees which proved to be too complicated and too much overhead.</code></pre><h4 id="technical-juicy-stuff">Technical Juicy Stuff</h4>
<p>Here are all the problems I had, as of last Friday:</p>
<ol>
<li>Seeding was super slow. On launch, we want to have NPCs in every major city in the US (before we launch globally) proportional to the population of said city, randomly distributed from the city&#39;s center. It took almost 30 minutes to seed this data and build the indexes, which was too slow to iterate and develop on.</li>
<li>Saving and restoring game state was super slow. Like a minute to checkpoint to disk and ten minutes to restore.</li>
<li>We were using way too much memory. For around 300k towers and a few thousand creeps we saw heap sizes of 14gb. This could equal hundreds of gigabytes in the real world - needlessly expensive! Also, the connections themselves will take a lot of memory so can&#39;t use all resources just on the game engine.</li>
</ol>
<p>Efficiency is important here because of scale. A poorly coded backend could mean six months of work wasted if we go down on launch day and nobody comes back to the app - and we
don&#39;t have the money or time to scale horizontally. So to &quot;get it out there&quot; we need to have a backend that&#39;ll scale on one machine, up to a million concurrent connections, and handle
tens of millions of actors (creeps) moving around concurrently. For concurrency, we shard in-memory geographically, but I&#39;ll cover this in a separate post. If we need to scale horizontally
it&#39;s just a matter of building an orchestrator to move the shards between instances - and no we can&#39;t back this with a traditional database. Millions of concurrent writes with a high write concern
basically mean that the app layer needs to have its own in-memory database that checkpoints to disk (and that&#39;s what we have). This is also how Redis works!</p>
<p>There was a point where I blamed Java for my poor performance, and I decided a trial rewrite in Rust. I figured I&#39;d just 1:1 port the Java code and see what the performance difference was.</p>
<p>I ported All the structures and then the seeding code. This was a super painful two days, and I am not a Rust fan yet. I think I get Rust, it&#39;s just stressful and not so fun to write.</p>
<p>Anyway, the code written in Rust blew away the code in Java. It could seed the data for a thousand cities, and create all the NPC users, in seconds.</p>
<p>Then I realized I had forgotten to pull out my profiler. N00b mistake. A quick dig into JDK Mission Control showed a number of problems.</p>
<p>I had read the hot path in the Java code a hundred times and couldn&#39;t see why the seeding was so slow. The code was so trivial in fact I just didn&#39;t think of using a profiler.
I suppose I thought it was Java&#39;s fault because I was fighting the garbage collector for a while, but I should have known better because switching to Shenandoah fixed that.</p>
<p>(By the way, Shenandoah is awesome, we went from many second pauses to never seeing more than a few hundred milliseconds STW pauses even during serialization/check pointing to disk)</p>
<p>So anyway. The profiler. Here is the cause of my first problem, the seeding being slow. During seeding, we enqueue a request to route between the source and target towers - and there
can be hundreds of thousands of these requests. The downstream routing server is in C++ but it can still only handle so many requests concurrently, so we queue them up in memory in the
game backend using a custom task executor.</p>
<p>And that executor - uses a ConcurrentLinkedQueue.</p>
<p><img 
    src="https://blog.winricklabs.com/images/tdw-router.png"
    alt="Router Class Fields"
    title="Router Class Fields"
    class='lozad' /></p>
<p>So what&#39;s wrong with that? Well, every time we enqueue a request we check the count of the queued items to see if we should execute that one right away:</p>
<p><img 
    src="https://blog.winricklabs.com/images/tdw-router-size.png"
    alt="Router Class Oopsie"
    title="Router Class Oopsie"
    class='lozad' /></p>
<p>The problem is ConcurrentLinkedQueue is implemented using a linked list. We call size() on it A LOT, and each time it was iterating through the whole queue.
I&#39;m not sure why the JDK doesn&#39;t optimize this using atomics, as that would probably introduce less contention than what it&#39;s doing today. All we had to do was use an AtomicInteger to keep track of the count.</p>
<p><img 
    src="https://blog.winricklabs.com/images/tdw-router-fix.png"
    alt="Router Class Fix"
    title="Router Class Fix"
    class='lozad' /></p>
<p>So that&#39;s #1 solved. The Java version was now faster than Rust.</p>
<p>So now #2</p>
<h3 id="heres-where-we-talk-about-data-structures-sorry">Here&#39;s where we talk about data structures, sorry.</h3>
<p>Ok - I wrote the title of this and then realized it takes a while to actually get to what the title&#39;s about. Thanks for getting this far.</p>
<p>Most of what we need to store in memory is the routing information. Each route - for example between your house and the grocery store - consists of a bunch of steps.</p>
<p>These steps are basically objects with the properties {duration, speed, and lat/lng}. And we store A LOT of these. Whenever a creep is primed to move again, we get the next step and then
update its position in the rtree.</p>
<p>We had originally represented geographical position information using doubles. This gives us a very high degree of accuracy - but at a cost!</p>
<p>Before we optimize, let&#39;s look at some stats. We can optimize forever, so we have to quantify when we&#39;re done. Let&#39;s 5gb of heap is where we&#39;d be happy with our testing dataset.</p>
<p>Note that the creep/tower count is just to some info about one shard that I have running, it doesn&#39;t mean that much.</p>
<h4 id="before-optimization">Before Optimization:</h4>
<p>CREEPS=3934 TOWERS=263278 TICK DURATION=125.0</p>
<p><strong>Size on disk</strong>: 2.322gb</p>
<p><strong>Memory</strong>: 14gb</p>
<h5 id="serialization-checkpoint-and-app-startup-time">Serialization (checkpoint and app startup) Time</h5>
<p><strong>To Disk (nvme)</strong>: 69540ms</p>
<p><strong>From Disk</strong>: 10 minutes</p>
<p>Wow! Over a minute to save! That&#39;s not too bad, however think about the fact that it&#39;s taking that long because it&#39;s <em>doing something</em> intensive. The game engine will already check
for how many threads are free and pause one of the shards if needed to serialize to disk - however we don&#39;t want to do that for a long time.</p>
<p>So first thing&#39;s first. double in the JVM world is 8 bytes. Float is 4. Also, we store a lot of Strings but don&#39;t really make use of the String class. Let&#39;s change everything to float and byte[].</p>
<p><img 
    src="https://blog.winricklabs.com/images/tdw-floatstring.png"
    alt="GitHub Changes"
    title="GitHub Changes"
    class='lozad' /></p>
<p>Not too bad. Now let&#39;s measure what the affect was.</p>
<h4 id="after-changing-double-to-float-and-string-to-byte">After changing double to float and String to byte[]</h4>
<p>CREEPS=2910 TOWERS=263278 DURATION=202.0</p>
<p><strong>Size on disk</strong>: 1.49gb</p>
<p><strong>Memory</strong>: 12gb</p>
<h5 id="serialization-checkpoint-and-app-startup-time-1">Serialization (checkpoint and app startup) Time</h5>
<p><strong>To Disk</strong>: 64443ms</p>
<p><strong>From Disk</strong>: 9.5 minutes</p>
<p>Alright, we gave up about 10 meters of accuracy (changing geographical coordinate representation) from double to float and got 2gb of ram and minor serialization improvements.</p>
<p>Not great.</p>
<p>However, one thing I realized was that each Route has many Step[] arrays. Each Step just contains a few floats and a Position object now (which also has two floats).</p>
<p><img 
    src="https://blog.winricklabs.com/images/tdw-route-0.png"
    alt="Route Obj Before Optimization"
    title="Route Obj Before Optimization"
    class='lozad' /></p>
<p>So basically our route information is a series of floats. Each entry in that Step[] array is also eight bytes for the pointer. That <em>really</em> adds up.
Let&#39;s get rid of those objects.</p>
<p><img 
    src="https://blog.winricklabs.com/images/tdw-route-1.png"
    alt="Route Obj Using float[]"
    title="Route Obj Using float[]"
    class='lozad' /></p>
<p>Then we just need some utilities to access this newly formatted data:</p>
<p><img 
    src="https://blog.winricklabs.com/images/tdw-route-2.png"
    alt="Route Obj Utils"
    title="Route Obj Utils"
    class='lozad' /></p>
<p>So what in the Kotlin is going on here? Well, we&#39;re basically serializing our data at rest into an array. This is much more compact than a bunch of objects and
is a common tactic for databases written in Java - use primitives as much as possible.</p>
<p>This means that this:</p>
<pre class="code">
{
  "route": [
    {
      "duration": 2,
      "speed": 5,
      "position": {
        "lat": 76.4,
        "lng": -170
      }
    },
    {
      "duration": 4,
      "speed": 3,
      "position": {
        "lat": 76.2,
        "lng": -170.1
      }
    }
  ]
}
</pre>

<p>Becomes:</p>
<pre class="code">
{
  "route": [
    2,
    5,
    76.4,
    -170,
    4,
    3,
    76.2,
    -170.1
  ]
}
</pre>

<p>Not only is this much nicer for the garbage collection it&#39;s much better for serialization in general - and not only to disk but over the network.</p>
<p>You&#39;d think that the JVM is optimizing things like this already, but the best it can do is keep the data the references point to
close to each other, and maybe compact the pointers.</p>
<p>The JVM does magical things. But behold.</p>
<p><strong>After redesigning the Route storage</strong></p>
<table>
<thead>
<tr>
<th></th>
<th>Before Optimizations</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>Size on disk</td>
<td>2.322gb</td>
<td>873mb</td>
</tr>
<tr>
<td>Memory</td>
<td>14gb</td>
<td>1.8gb</td>
</tr>
<tr>
<td>To Disk Time</td>
<td>69540ms</td>
<td>4183ms</td>
</tr>
<tr>
<td>From Disk Time</td>
<td>10 minutes</td>
<td>24 seconds</td>
</tr>
</tbody></table>
<p>Amazing. Our shard size on disk has gone down from over two gigabytes to 873mb. Note that the app is still running in the background and at 527314 creeps the size on disk is still less than a gigabyte.</p>
<p>Our memory usage went from 14gb to less than two.</p>
<p>Startup time went from ten minutes to 24 seconds, and writing to disk went from over a minute to four seconds.</p>
<p>This was a super productive past couple days, and I&#39;m very happy with the result. I&#39;m hoping that soon we can show off the full game and prepare for launch.</p>
<p>Next I&#39;ll be doing a load test with simulating millions of active clients and having the backend continually keep them in sync. I suspect that will be much harder.
The websocket library I&#39;m using currently dies at 1k connections on my Windows machine (that&#39;s 32 threads and 64gb of ram). Hopefully it performs well in a POSIX environment.</p>
<p>If it doesn&#39;t, we&#39;ll most likely be fronting the game engine with Nginx/Nchan. That&#39;s what we did for <a href="https://watch.ly" target="_blank">Watch.ly</a> and it works well.</p>
<p>Cheers!</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(02-08-2020) Being on the Homepage of Hacker News]]></title>
            <link>https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html</link>
            <guid>https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html</guid>
            <pubDate>Sat, 08 Feb 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[(02-08-2020) Being on the Homepage of Hacker News - Size: 4.2 kB - 187 kB. 3 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(02-08-2020) Being on the Homepage of Hacker News | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Sat Feb 08 2020</h6>
<h1 id="a-hrefhttpsblogwinricklabscom02-08-2020-being-on-the-homepage-of-hacker-newshtmlbeing-on-the-homepage-of-hacker-newsa"><a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html">Being on the Homepage of Hacker News</a></h1>
<p><em>Article size is 4.2 kB - 187 kB and is a 3 min read</em></p>
<p>I have to say, this was a big moment for me. When I started to gather feedback for <a href="https://fastcomments.com" target="_blank">FastComments</a> it was terrifying. I started small on Reddit and got around 25 comments.
When I first opened that thread I was terrified. I almost didn&#39;t want to read the feedback. What if it was terrible, what if it made me think I was wasting my time?</p>
<p>But, for the most part it was helpful. I think people found some issues right away that I fixed that night.
So then the next step was <a href="https://news.ycombinator.com" target="_blank">Hacker News</a>.</p>
<p>The post is here: <a href="https://news.ycombinator.com/item?id=22209411" target="_blank">Show HN: FastComments</a></p>
<p>Over 111 votes and 65 comments! Wow! I had gotten over the anxiety of sharing the project at this point. I posted the link on Friday night on a whim, expecting
to only get a few up votes and comments. Instead, I woke up, and it was #13 on the homepage:</p>
<p><img 
    src="https://blog.winricklabs.com/images/fc-onfrontpage.png"
    alt="On the front page!"
    title="On the front page!"
    class='lozad' /></p>
<p>Heck, I was happy it wasn&#39;t flagged or something. You never know what will happen.</p>
<p>Overall it was really helpful. I got feedback on:</p>
<ul>
<li>The pricing model</li>
<li>The design</li>
<li>The marketing site text</li>
<li>The feature set and also various competitors I hadn&#39;t known about, including open source projects</li>
</ul>
<p>Also, I got to see how much traffic you actually get when this happens. I think the peak load on the server (not enough traffic to enable horizontal scaling yet, although that&#39;s coming) was like .3, and this isn&#39;t a big server.
The app is well optimized though, so it&#39;s expected not to fall over. At the peak I think I was seeing something like five requests a second, spiking around 50. Not terribly high.</p>
<p>People also left hundreds of comments on the <a href="https://fastcomments.com/demo" target="_blank">Demo Page</a>, so they were pretty engaged.</p>
<p>You&#39;ll see the comment count on the demo page is less now since a lot of them expired, which is one of the features of FastComments.</p>
<p>Using the Analytics page of the dashboard on FastComments we can see when the spike occurred:</p>
<p><img 
    src="https://blog.winricklabs.com/images/fc-onfrontpage-metrics.png"
    alt="Metrics"
    title="Metrics"
    class='lozad' /></p>
<p>I posted the link on Jan 31st, and on Feb 1st over four thousand people viewed the demo page, 67 people left comments, 5 people voted, and 141 commenter accounts were created.</p>
<p>I also got one paying customer. I added the &quot;add FastComments to a website&quot; link to the demo page a little to late.</p>
<p>Another self plug - the <a href="https://watch.ly">Watch.ly</a> Daily shows the overall traffic to the homepage and user retention time:</p>
<p><img 
    src="https://blog.winricklabs.com/images/top-hn-watchly-daily.png"
    alt="Watch.ly Email Top"
    title="Watch.ly Email Top"
    class='lozad' /></p>
<p><img 
    src="https://blog.winricklabs.com/images/top-hn-watchly-daily2.png"
    alt="Watch.ly Email Bottom"
    title="Watch.ly Email Bottom"
    class='lozad' /></p>
<p>As you can see there were 12k hits to the homepage, around 11k being unique. Watch.ly also shows session time. It looks like someone left the tutorial open in one of my apps so that&#39;s why
that&#39;s on the top.</p>
<p>I also got a ton of requests from random scripts that people were writing. One person sent thousands of requests at once as some kind of test.</p>
<p>I saw requests from HTTP clients written in Go, Ruby, and Node. Who knows what people were doing. If you make an API people will abuse it.</p>
<p>Overall this was a great and useful experience. I don&#39;t think you should expect to get your users from HN but the feedback was useful in my case.</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(2-08-2020) Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments]]></title>
            <link>https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html</link>
            <guid>https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html</guid>
            <pubDate>Sat, 08 Feb 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[(2-08-2020) Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments - Size: 2.6 kB. 2 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(2-08-2020) Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Sat Feb 08 2020</h6>
<h1 id="a-hrefhttpsblogwinricklabscom2-08-2020-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deploymentshtmlfixng-nginx-502-gateway-timeout-with-proxy_pass-during-deploymentsa"><a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html">Fixng Nginx 502 Gateway Timeout With proxy_pass During Deployments</a></h1>
<p><em>Article size is 2.6 kB and is a 2 min read</em></p>
<p>I use Nginx a lot. One issue I&#39;ve always never been able to figure out is when I use it as a proxy to some backend like Node or Java is that
when I restart/redeploy those services Nginx thinks they are down for ~10 seconds.</p>
<p>Recently I realized that Nginx is creating an &quot;upstream&quot; declaration when you use proxy_pass.</p>
<p>View documentation for upstream <a href="http://nginx.org/en/docs/http/ngx_http_upstream_module.html">here</a>.</p>
<p>The thing about <code>ngx_http_upstream_module</code> is that it supports a configuration parameter called <code>fail_timeout</code>. What Nginx uses this for is if the upstream
server is not available Nginx won&#39;t allow calls to it during this duration. The upstream module allows you to define many backends for a single proxy, meaning
you basically create a load balancer.</p>
<p>When you use proxy_pass without explicitly using <code>ngx_http_upstream_module</code> you can think of that as creating a load balancer with only one available node.</p>
<div class="fastcomments-banner-logo text-center">
    <a href="https://fastcomments.com" target="_blank"><img src="/images/fastcomments-banner-1.png" alt="FastComments Banner" /></a>
</div>

<p>So, if you restart your service and Nginx tries to proxy a request to it - and it fails - Nginx will consider that server unavailable for <code>fail_timeout</code>.
And since you only have one server in your &quot;load balancer&quot; Nginx can&#39;t forward that request to anything. So it returns a 502.</p>
<p>What you can do is set <code>fail_timeout</code> to 0. Then Nginx will never consider the backend unavailable. I&#39;d only do this if you have a single upstream server.
Even then it is risky in a high load environment because then if the server becomes overwhelmed it might not have a grace period to recover.</p>
<p>However, if you&#39;re in a high load environment I trust you have more than one upstream node :).</p>
<p>So here&#39;s what you came for:</p>
<div class="code">upstream backend {
    server localhost:3001 fail_timeout=0;
}
server {
    location / {
        proxy_pass http://backend;
    }
}
</div>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(2-05-2020) How we Localized FastComments Without Slowing It Down]]></title>
            <link>https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html</link>
            <guid>https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html</guid>
            <pubDate>Wed, 05 Feb 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[(2-05-2020) How we Localized FastComments Without Slowing It Down - Size: 4.0 kB. 3 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(2-05-2020) How we Localized FastComments Without Slowing It Down | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Wed Feb 05 2020</h6>
<h1 id="a-hrefhttpsblogwinricklabscom2-05-2020-how-we-localized-fastcomments-without-slowing-it-downhtmlhow-we-localized-fastcomments-without-slowing-it-downa"><a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html">How We Localized FastComments Without Slowing It Down</a></h1>
<p><em>Article size is 4.0 kB and is a 3 min read</em></p>
<p>To start - note that I would not recommend the techniques outlined here to most companies. FastComments is an unusual beast where we
are optimizing for milliseconds. Most web applications will be fine with adding 100ms of latency as a trade-off for localization support... but not us.</p>
<p>So what did we do for <a href="https://fastcomments.com" target="_blank">FastComments</a>?</p>
<p>What I&#39;ve seen in web apps so far is if you want to localize the client it does a lot of parsing of browser/system settings and then fetches a JSON file containing
a map of translation id to localized string. The application is usually structured in a way that this network request is part of the framework setting itself up, meaning
that you can&#39;t fetch your translations concurrently while fetching application state/data. They do get cached however - but we need to optimize for initial page load.</p>
<p>FastComments already doesn&#39;t use a framework (other than what a native browser provides) and fetching initial state is relatively simple for us, so we have a lot of freedom.</p>
<p>We&#39;ve already gotten to a state where our /comments endpoint is no longer RESTful in that it returns more than just comments. It returns configuration for rendering the widget, for starters.
We can do this since fetching the comment objects is very simple and fast - if it took seconds we&#39;d have to have a separate request for initializing the widget, so we could show some content quickly.</p>
<p>So we already have a fast endpoint that&#39;s following a convention where we can tell the API to include additional information via query params. For example, for localization
we added a <code>?includei10n</code> flag in which case the client will do locale detection and send back the appropriate set of translations. This response is gzipped by Nginx just like the JS file.
We only pass this flag for initial /comment calls from the widget.</p>
<p>So that&#39;s basically it. The client script dropped a few hundred bytes in size (gzipped) and the API response grew a little - but we didn&#39;t add any additional requests to load the widget.</p>
<p>Additionally, the translation keys are <strong>minimized</strong>. At build time, a parser goes through the JS distribution and replaces code like
<code>translations.SOME_LONG_TRANSLATION_KEY</code> with <code>t.T123</code>. These short numbers are incremental for improved compression by lz77. Additionally,
shorter code is less code the JS engine has to parse, and less data to send over the wire on initial load.</p>
<p>Both the JS build system and server know when to use the long or short translations based on the environment (dev or production).</p>
<p>The translations for each locale are also cached in-memory in each worker.</p>
<p>Another thing we looked at was addressing the endpoint that loads the initial iframe. Right now it&#39;s <a href="https://fastcomments.com/embed?config=%7B%22tenantId%22%3A%22nYrnfYEv%22%2C%22urlId%22%3A%22https%253A%252F%252Fblog.fastcomments.com%252F(12-30-2019)-fastcomments-demo.html%22%7D" target="_blank">rendered with EJS</a>.
It turns out that EJS is indeed pretty fast. We tried replacing the template engine with just string concatenation - it&#39;s hardly faster than using EJS. Request response time still hovered around 20ms (including dns resolution). Initial requests with EJS seem to spike around 30 milliseconds though, so we may explore this again in the future.</p>
<p>Sorry if you were looking for something super fancy. Sometimes the fastest solution is also the simplest. :)</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(2-03-2020) How Optimized Threaded Pagination Works]]></title>
            <link>https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html</link>
            <guid>https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html</guid>
            <pubDate>Mon, 03 Feb 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[(2-03-2020) How Optimized Threaded Pagination Works - Size: 3.5 kB - 173 kB. 3 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(2-03-2020) How Optimized Threaded Pagination Works | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Mon Feb 03 2020</h6>
<h1 id="a-hrefhttpsblogwinricklabscom2-03-2020-how-optimized-threaded-pagination-workshtmlhow-optimized-threaded-pagination-worksa"><a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html">How Optimized Threaded Pagination Works</a></h1>
<p><em>Article size is 3.5 kB - 173 kB and is a 3 min read</em></p>
<p>For <a href="https://fastcomments.com" target="_blank">FastComments</a> one of our challenges was dealing with long comment threads.
For example, our <a href="https://fastcomments.com/demo" target="_blank">demo page</a> already has over 200 comments at time of writing. Long threads make us miss our SLA of 40ms for a /comments api call. We still miss this occasionally for other reasons, but this was a big hit.</p>
<p>Ideally, the call finishes in 30ms. That&#39;s our internal goal.</p>
<p>There are a lot of easy solutions to pagination, nothing revolutionary there. However, it gets a little more fun when you throw nested replies (threads) into the mix.</p>
<p><img 
    src="https://blog.winricklabs.com/images/fc-pagination01.png"
    alt="Threads Example"
    title="Threads Example"
    class='lozad'
    height="700px" /></p>
<p>Reason being is that if you cop-out and do it the easy (and potentially bad way) way that works 90% of the time you end up with something that&#39;s very slow the other 10% of the time.
Being Fast, we don&#39;t want to do that. Some examples which don&#39;t make us happy:</p>
<ol>
<li>Fast - Bad Interaction - not showing the threads and instead just showing the &quot;top-level&quot; comments.</li>
<li>Mostly Fast - Bad Interaction - paginating by the top-level comments and always querying all replies (could potentially show hundreds instead of just 30 a page like we want to). </li>
</ol>
<p>What we wanted to do is paginate by &quot;content breaks&quot;. To illustrate let&#39;s break up the example from earlier:</p>
<p><img 
    src="https://blog.winricklabs.com/images/fc-pagination02.png"
    alt="Threads Example Sliced Up"
    title="Threads Example Sliced Up"
    class='lozad'
    height="700px" /></p>
<p>A page can occur at any point in the thread. This gives us a lot of flexibility. But how do you optimize this? An obvious solution is to do what we did originally - query
the whole set of comments - then just run DFS through the threads and short-circuit when you reach your pagination limit.</p>
<p>We take it a step further and only do this operation during <em>write</em>. So any vote, comment, or moderator action triggers a job that updates the pageNumber at a comment level.
Those from the SQL world will think of this as a stored procedure that runs &quot;ON UPDATE&quot;.</p>
<p>Of course, for us it&#39;s about 20 lines of JavaScript. This job so far seems to execute in around 300ns for threads with ~300 comments. This includes reading from and writing to the DB.</p>
<p><img 
    src="https://blog.winricklabs.com/images/fc-pagination03.png"
    alt="Some OK Code"
    title="Some OK Code"
    class='lozad'
    height="650px" /></p>
<p>Another option would have been to use a graph database. However, wanting to build a stable product and not having the operational experience with a graph database
made that not a good idea.</p>
<p>For very large threads, a check is done to offload the work into a dedicated batch job for processing. To reduce the confusion of
a submitted comment in a large thread not showing right away, it is inserted in the &quot;correct&quot; place in the correct page, even if it
increases the size of that page slightly.</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(1-25-2020) Making an Embeddable Widget Safe from Cross-Site Attacks]]></title>
            <link>https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html</link>
            <guid>https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html</guid>
            <pubDate>Sat, 25 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[(1-25-2020) Making an Embeddable Widget Safe from Cross-Site Attacks - Size: 2.3 kB - 26.6 kB. 2 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(1-25-2020) Making an Embeddable Widget Safe from Cross-Site Attacks | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Sat Jan 25 2020</h6>
<h1 id="a-hrefhttpsblogwinricklabscom1-25-2020-making-an-embeddable-widget-safe-from-cross-site-attackshtmlmaking-an-embeddable-widget-safe-from-cross-site-attacksa"><a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html">Making an Embeddable Widget Safe from Cross-Site Attacks</a></h1>
<p><em>Article size is 2.3 kB - 26.6 kB and is a 2 min read</em></p>
<p>Recently we&#39;ve launched <a href="https://fastcomments.com">FastComments</a> and one challenge was security. We wanted our widgets to be
easy to use, but also protect people that write comments from CSRF attacks. The widget needs to be authenticated to take actions like comment
and vote, however we wanted the same session cookie to authenticate you on fastcomments.com - so if you&#39;re logged into the widget to comment you can manage
your account and vise-versa. This allows for some nice interaction like being able to go from the widget directly to your account page.</p>
<p>The obvious solution is to use an iframe, right? That way the cookies can have the SameSite=None, Secure=true properties set and we simply remove the CORS header from our http request responses. Well not so fast. Iframes are no stranger to me, however 
usually they&#39;re either a specific size or absolutely positioned and take up the whole viewport. While the FastComments widget can have a fixed horizontal size it can expand 
vertically so how do you deal with that?</p>
<p>So what I decided to do was just use cross-frame communication. The script running inside the iframe can post a message to the parent with its current size:</p>
<p><img 
    src="https://blog.winricklabs.com/images/fastcomments-broadcast-height.png"
    alt="Height Broadcast Code"
    class='lozad'
    height="400px" /></p>
<p>Then the parent embeddable code, the snippet that our customers include on their sites, simply captures that event and updates the iframe:</p>
<p><img 
    src="https://blog.winricklabs.com/images/fastcomments-capture-height.png"
    alt="Height Capture Code"
    class='lozad'
    height="400px" /></p>
<p>And that&#39;s it! There is one downside which is that there&#39;s another network request to load the iframe, however we can optimize that easily. Happy coding.</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(5-17-2019) Pixmap Release Launching Rewind]]></title>
            <link>https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html</link>
            <guid>https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html</guid>
            <pubDate>Fri, 17 May 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[(5-17-2019) Pixmap Release Launching Rewind - Size: 4.2 kB - 22 MB. 4 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(5-17-2019) Pixmap Release Launching Rewind | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Fri May 17 2019</h6>
<h1 id="a-hrefhttpsblogwinricklabscom5-17-2019-pixmap-release-launching-rewindhtmlpixmap-release-launching-rewinda"><a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html">Pixmap Release: Launching Rewind</a></h1>
<p><em>Article size is 4.2 kB - 22 MB and is a 4 min read</em></p>
<p>Today I&#39;m excited to announce a new feature for Pixmap. While I had planned on mostly
focusing on the native iOS port in Swift a couple friends brought up an idea that sounded
too fun to implement, so here it is.</p>
<p><img 
    src="https://blog.winricklabs.com/images/pixmap-rewind-demo.gif" 
    alt="Pixmap rewind demo"
    class='lozad'
    height="500px" /></p>
<p>Think version control for pixel art, except it&#39;s just linear. It&#39;s useful and fun to use.
If you buy Pixmap Ultimate you can use rewind to download snapshots or rewind an image.</p>
<p>When you rewind an image it modifies the history like this:</p>
<p><img 
    src="https://blog.winricklabs.com/images/pixmap-how-rewind-works.svg" 
    alt="Pixmap rewind how it works"
    class='lozad'
    height="500px"
    width="80%" /></p>
<p>How it works is pretty simple. Luckily we chose to store every change everyone makes which made this possible.
When you start a rewind the application simply paginates the event history from the backend and when you rewind
an image we use the same architecture for drawing. All the changes get added to the same queue, processed by the same
servers, etc. The only real difference is that a lock is created on the image and then released once the rewind is done
so that no editing can happen during the rewind.</p>
<p>The main challenge client side was performance.</p>
<p>If you drag the bar all the way to the beginning of time and start dragging
it forward that is very &quot;light&quot; since each &quot;tick&quot; of the bar is simply applying changes to the scene from the last tick.
However, when you drag backwards you have to re-render the whole scene from the beginning of time to where the user dragged 
the bar. Doing this on the UI thread resulted in lots of lag, so I had to implement an &quot;observer&quot; thread that waits for
things to render, does the processing, and pushes the result to the UI thread. I also ended up changing how concurrency
worked in the renderer to reduce CPU usage, making both rewind and drawing smoother. </p>
<p>There were three challenges server side.</p>
<p>The first was that since I&#39;m using websockets the original plan was for the client to send an event
and then the server would paginate through the data and send the pages to the client. This didn&#39;t work with websockets
because no matter the frame size or delay between messages the connection could get clogged resulting in high memory server
side, delay in WS handshakes, and ultimately the connection closing. The solution was to implement traditional pagination
over websockets where the client says &quot;give me the next 10k after this id&quot;. At that point you don&#39;t even need websockets
but I already have the infrastructure for it for other features.</p>
<p>The second problem was the pagination itself. We use MongoDB, and it runs on a $5 VPS for Pixmap. That instance can handle
tens of thousands of people drawing at once, but one rewind could destroy it with my first implementation. You see, Mongo
doesn&#39;t optimize pagination queries like &quot;find X, sort by Y, start 1000, limit 100&quot;. It will literally find all of X, sort it all,
and then select the page from the result even though we are sorting in the same direction as the index etc (this is another
thing about Mongo (or B-Trees I guess) I will write up later, where you can optimize for sort direction). This would kill
the instance.</p>
<p>The solution was to still use pagination but without &quot;start after N&quot; - instead we &quot;start after this _id&quot; since Mongo&#39;s
ids are naturally sortable in a non-sharded environment (assuming you let the DB create the id, and you don&#39;t create it application side). This drops
cpu usage from around 80% during a rewind of &quot;Mid Peninsula&quot; to a negligible amount.</p>
<p>Anyway, I had fun building it and hope people enjoy it. Cheers.</p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(5-5-2019) About This]]></title>
            <link>https://blog.winricklabs.com/(5-5-2019)-about-this.html</link>
            <guid>https://blog.winricklabs.com/(5-5-2019)-about-this.html</guid>
            <pubDate>Sun, 05 May 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[(5-5-2019) About This - Size: 640 B. 1 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(5-5-2019) About This | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Sun May 05 2019</h6>
<h1 id="a-hrefhttpsblogwinricklabscom5-5-2019-about-thishtmlwhat-is-winricklabsa"><a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html">What is winrickLabs?</a></h1>
<p><em>Article size is 640 B and is a 1 min read</em></p>
<p>winrickLabs is a place for executing ambitious ideas. Things that may be deemed &quot;too hard&quot; or &quot;not profitable&quot; or &quot;too fun&quot; end up here. This doesn&#39;t mean elegant solutions are rejected. In fact, they are encouraged. But the end product should be something to be proud of and the journey should have a story worth telling. </p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
        <item>
            <title><![CDATA[(5-5-2019) How This Blog Works]]></title>
            <link>https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html</link>
            <guid>https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html</guid>
            <pubDate>Sun, 05 May 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[(5-5-2019) How This Blog Works - Size: 1.3 kB - 8.39 kB. 1 min read]]></description>
            <content:encoded><![CDATA[<html>
<head>
    <title>(5-5-2019) How This Blog Works | winrickLabs Blog</title>
    <link rel="shortcut icon" href="/images/favicon.png"/>
    <link rel="stylesheet" href="/css/pure-min.css">
    <link rel="stylesheet" href="/css/grids-responsive-min.css">
    <link rel="stylesheet" href="/css/blog.css">
    <script src="js/blog.min.js"></script>
    <script src="https://fastcomments.com/js/comment-ui.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script async src="https://watchlycdn.com/js/watchly.min.js" data-key="5d1fcfabcc00a66e161816b4"></script>
    <link rel="alternate" type="application/rss+xml" title="WinrickLabs Blog" href="https://blog.winricklabs.com/rss.xml">
</head>
<body>
<div id="layout" class="pure-g">
    <div class="sidebar pure-u-1 pure-u-md-1-5">
        <div class="header">
            <h1 class="brand-title"><a href="/">winrickLabs</a></h1>
            <h2 class="brand-tagline">Blog</h2>
            <div class="buttons">
                <a href="https://blog.winricklabs.com/rss.xml"><img src="/images/rss.png"/></a>
                <a href='https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fblog.winricklabs.com%2Frss.xml'
                   target='blank'><img id='feedlyFollow'
                                       src='https://s3.feedly.com/img/follows/feedly-follow-rectangle-volume-medium_2x.png'
                                       alt='follow us in feedly' width='71' height='28'></a>
            </div>
        </div>
        <div class="sidebar-links">
                <div class="date">Sat May 13 2023</div>
                <a href="https://blog.winricklabs.com/(5-13-2023)-reverse-engineering-netpanzer-and-extracting-assets.html"> Reverse Engineering Netpanzer and Extracting Assets</a>
                <div class="date">Sun Apr 26 2020</div>
                <a href="https://blog.winricklabs.com/(4-26-2020)-making-fastcomments-realtime.html"> Making FastComments Realtime</a>
                <div class="date">Thu Apr 02 2020</div>
                <a href="https://blog.winricklabs.com/(04-02-2020)-just-how-fast-is-ejs.html"> Just How Fast is EJS</a>
                <div class="date">Mon Feb 17 2020</div>
                <a href="https://blog.winricklabs.com/(02-17-2020)---efficient-data-structures-for-mmo-game-backends-in-java.html">Efficient Data Structures For MMO Game Backends in Java</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(02-08-2020)-being-on-the-homepage-of-hacker-news.html"> Being on the Homepage of Hacker News</a>
                <div class="date">Sat Feb 08 2020</div>
                <a href="https://blog.winricklabs.com/(2-08-2020)-fixing-nginx-502-gateway-timeout-with-proxy_pass-during-deployments.html"> Fixing Nginx 502 Gateway Timeout With proxy_pass During Deployments</a>
                <div class="date">Wed Feb 05 2020</div>
                <a href="https://blog.winricklabs.com/(2-05-2020)-how-we-localized-fastcomments-without-slowing-it-down.html"> How we Localized FastComments Without Slowing It Down</a>
                <div class="date">Mon Feb 03 2020</div>
                <a href="https://blog.winricklabs.com/(2-03-2020)-how-optimized-threaded-pagination-works.html"> How Optimized Threaded Pagination Works</a>
                <div class="date">Sat Jan 25 2020</div>
                <a href="https://blog.winricklabs.com/(1-25-2020)-making-an-embeddable-widget-safe-from-cross-site-attacks.html"> Making an Embeddable Widget Safe from Cross-Site Attacks</a>
                <div class="date">Fri May 17 2019</div>
                <a href="https://blog.winricklabs.com/(5-17-2019)-pixmap-release-launching-rewind.html"> Pixmap Release Launching Rewind</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-about-this.html"> About This</a>
                <div class="date">Sun May 05 2019</div>
                <a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html"> How This Blog Works</a>
        </div>
    </div>

    <div class="content post-page pure-u-1 pure-u-md-3-4">
        <div>
            <div class="posts">
                <section class="post">
                    <div class="post-description">
                        <p>
                            <h6 id="postdate">Sun May 05 2019</h6>
<h1 id="a-hrefhttpsblogwinricklabscom5-5-2019-how-this-blog-workshtmlhow-does-this-blog-worka"><a href="https://blog.winricklabs.com/(5-5-2019)-how-this-blog-works.html">How does this blog work?</a></h1>
<p><em>Article size is 1.3 kB - 8.39 kB and is a 1 min read</em></p>
<p>For those curious, I decided against having this blog be dynamically generated at runtime (think Wordpress, Pico, etc). It really didn&#39;t need to be interactive, doesn&#39;t need any client side javascript, and really doesn&#39;t even need a database. However, writing HTML manually for each post is a pain to maintain and not worth my time.</p>
<p>So to get the best of both worlds - maintainability and simplicity - I do dynamically generate the blog but only at build time.</p>
<p>What this means that I write the posts in Markdown and check them into github in the same repo. The repository contains a NodeJS app and when
a GitHub event fires to the build server the build server runs the NodeJS app just one time which generates the site. Then it just sends the build result off to the appropriate server running nginx.</p>
<p><img 
    src="https://blog.winricklabs.com/images/blog-arch-diagram.svg"
    alt="Blog diagram"
    class='lozad'
    height="500px"
    width="80%" /></p>

                        </p>
                    </div>
                </section>
                <script src="https://fastcomments.com/js/embed-v2.min.js"></script>
                <div id="fastcomments-widget"></div>
                <script>
                    window.FastCommentsUI(document.getElementById('fastcomments-widget'), {
                        tenantId: 'StrTVey_yMpw'
                    });
                </script>
            </div>
        </div>

        <div class="footer">Copyright winrickLabs LLC 2019 - 2023</div>
    </div>
</div>
</body>
</html>
]]></content:encoded>
            <author>winrid+wlb @ gmail DOT com (Devon Winrick)</author>
        </item>
    </channel>
</rss>