<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://sigil.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://sigil.org/" rel="alternate" type="text/html" /><updated>2026-03-07T01:13:08+00:00</updated><id>https://sigil.org/feed.xml</id><title type="html">sigil.org</title><subtitle>The ramblings of a madman. Or the exploits of a coder, homelaber, father, and athlete. Maybe both...</subtitle><author><name>Richard Kolkovich</name></author><entry><title type="html">TWIL: 2026-03-06</title><link href="https://sigil.org/twil-2026-03-06/" rel="alternate" type="text/html" title="TWIL: 2026-03-06" /><published>2026-03-06T00:00:00+00:00</published><updated>2026-03-06T00:00:00+00:00</updated><id>https://sigil.org/twil-2026-03-06</id><content type="html" xml:base="https://sigil.org/twil-2026-03-06/"><![CDATA[<p>This week I learned:</p>
<ul>
  <li>We’re at “war” (not an official one, of course) with Iran</li>
  <li>Colorado has more fast food restaurants per capita than any other state (yet
we’re still the 4th healthiest?)</li>
  <li>A lot about SteamOS, but that’s a whole ‘nother post</li>
</ul>]]></content><author><name>Richard Kolkovich</name></author><category term="twil" /><summary type="html"><![CDATA[What I learned the week ending on 2026-03-06]]></summary></entry><entry><title type="html">TWIL: 2026-02-27</title><link href="https://sigil.org/twil-2026-02-27/" rel="alternate" type="text/html" title="TWIL: 2026-02-27" /><published>2026-02-27T00:00:00+00:00</published><updated>2026-02-27T00:00:00+00:00</updated><id>https://sigil.org/twil-2026-02-27</id><content type="html" xml:base="https://sigil.org/twil-2026-02-27/"><![CDATA[<p>Last week I was on vacation, skiing with friends and family, so I skipped this
post.</p>

<p>This week I learned:</p>
<ul>
  <li>Apple currently makes the best value in computing. Only took a RAMpocalypse to
bring this about…</li>
  <li>Metallica is coming to the Sphere in October. I will be there if I can.</li>
</ul>]]></content><author><name>Richard Kolkovich</name></author><category term="twil" /><summary type="html"><![CDATA[What I learned the week ending on 2026-02-27]]></summary></entry><entry><title type="html">TWIL: 2026-02-13</title><link href="https://sigil.org/twil-2026-02-13/" rel="alternate" type="text/html" title="TWIL: 2026-02-13" /><published>2026-02-13T00:00:00+00:00</published><updated>2026-02-13T00:00:00+00:00</updated><id>https://sigil.org/twil-2026-02-13</id><content type="html" xml:base="https://sigil.org/twil-2026-02-13/"><![CDATA[<p>This week I learned:</p>
<ul>
  <li>Lindsey Vonn is my age</li>
  <li>if you embed a <code class="language-plaintext highlighter-rouge">protoc-gen-go</code> generated struct in another struct, <code class="language-plaintext highlighter-rouge">protogetter</code> does not lint properly (investigating and will file a bug if necessary)</li>
  <li>I need to blow out my dryer vent pipe (and not just clean the portion behind the dryer)
    <ul>
      <li>Bonus: I <em>may</em> have a vent pipe for a vent hood in my kitchen - there is another 
  vent outside beside my dryer vent</li>
    </ul>
  </li>
  <li>re-learned that curling is a lowkey addicting sport to watch</li>
</ul>

<p>This week I read:</p>
<ul>
  <li>https://mitchellh.com/writing/my-ai-adoption-journey - the author’s journey
mirrors my own somewhat. It is both validating and frustrating (that we have
made the same mistakes as others…but somehow, that’s the best way to learn)</li>
</ul>]]></content><author><name>Richard Kolkovich</name></author><category term="twil" /><summary type="html"><![CDATA[What I learned the week ending on 2026-02-13]]></summary></entry><entry><title type="html">TWIL: 2026-02-06</title><link href="https://sigil.org/twil-2026-02-06/" rel="alternate" type="text/html" title="TWIL: 2026-02-06" /><published>2026-02-06T00:00:00+00:00</published><updated>2026-02-06T00:00:00+00:00</updated><id>https://sigil.org/twil-2026-02-06</id><content type="html" xml:base="https://sigil.org/twil-2026-02-06/"><![CDATA[<p>Out on a run today, I had an idea (which often happens): ‘This Week I Learned’
blog posts. This is the first installment. The intent? To be a short-form record
of random crap I learned this week. Raw, concise. Some of these items may become
longer posts in the future. Some are just random.</p>

<p>Learning something new every day isn’t ALWAYS possible (some days, you’re just 
buried), but over a week? There should generally be something there.</p>

<p>Now, on to this week’s nuggets (this will be much easier to record in real-time
in the future than to recall on Friday evening):</p>

<ul>
  <li>AI coding assistants are nowhere close to being able to handle a moderately
simple yet significantly-sized refactor. Small chunks with frequent check-ins
keep them on the rails</li>
  <li><em>Fellowship of the Ring</em> is worth seeing on the big screen. You may have to
wait for the 30th anniversary for the next showing, however</li>
</ul>]]></content><author><name>Richard Kolkovich</name></author><category term="twil" /><summary type="html"><![CDATA[What I learned the week ending on 2026-02-06]]></summary></entry><entry><title type="html">Hands-on EMV: Reading Chip Card Data with Android NFC APIs</title><link href="https://sigil.org/hands-on-emv/" rel="alternate" type="text/html" title="Hands-on EMV: Reading Chip Card Data with Android NFC APIs" /><published>2025-08-06T00:00:00+00:00</published><updated>2025-08-06T00:00:00+00:00</updated><id>https://sigil.org/hands-on-emv</id><content type="html" xml:base="https://sigil.org/hands-on-emv/"><![CDATA[<p>Find the code from my <a href="https://fintechdevcon.io/">FintechDevcon 2025</a> talk,
Hands-on EMV, over <a href="https://github.com/sarumont/EMVLab2600">at Github</a>.</p>

<p>I will update this post with a link to the recording of my talk once it is
posted.</p>]]></content><author><name>Richard Kolkovich</name></author><summary type="html"><![CDATA[Find the code from my FintechDevcon 2025 talk, Hands-on EMV, over at Github.]]></summary></entry><entry><title type="html">Fintech Devcon 2025</title><link href="https://sigil.org/ftdc-2025/" rel="alternate" type="text/html" title="Fintech Devcon 2025" /><published>2025-07-23T00:00:00+00:00</published><updated>2025-07-23T00:00:00+00:00</updated><id>https://sigil.org/ftdc-2025</id><content type="html" xml:base="https://sigil.org/ftdc-2025/"><![CDATA[<p>Just a quick update to say that I’ll be giving a talk at <a href="https://fintechdevcon.io/">FintechDevcon 2025</a> in Denver on <a href="https://fintechdevcon.io/speakers/richard-kolkovich/">Wednesday, August 6th</a>. Hope to see you there!</p>

<p><img src="/images/ftdc25.png" alt="Fintech Devcon" /></p>]]></content><author><name>Richard Kolkovich</name></author><summary type="html"><![CDATA[Just a quick update to say that I’ll be giving a talk at FintechDevcon 2025 in Denver on Wednesday, August 6th. Hope to see you there!]]></summary></entry><entry><title type="html">Plex and K8s: solving remote vs. local traffic</title><link href="https://sigil.org/plex-and-k8s-remote-vs-local/" rel="alternate" type="text/html" title="Plex and K8s: solving remote vs. local traffic" /><published>2025-07-10T00:00:00+00:00</published><updated>2025-07-10T00:00:00+00:00</updated><id>https://sigil.org/plex-and-k8s-remote-vs-local</id><content type="html" xml:base="https://sigil.org/plex-and-k8s-remote-vs-local/"><![CDATA[<p>In the current iteration of <a href="https://github.com/sarumont/homelab">my homelab</a>,
I’m running Plex in a k8s cluster (along with everything else I can). This
setup has been humming along nicely until I recently segregated my LAN with
VLANs. Why? Because my kids are old enough that they have friends with devices,
and I trust them about as far as they can throw me. But I digress…</p>

<p>I have a VLAN for “Media and Adults” — my stuff, my wife’s stuff, and any media
devices we need to control from our phones (so I don’t have cross-VLAN
UPnP/Avahi/etc. headaches). At first, I simply allowed all VLANs to access my 
Plex server via firewall rules. Simple, functional.</p>

<p><strong>The VLANs don’t actually have anything to do with this issue</strong>, but when I
implemented them I also:</p>
<ul>
  <li>tightened up my <em>LAN Networks</em> config (by removing the entire <code class="language-plaintext highlighter-rouge">10.0.0.0/8</code> subnet)</li>
  <li>added throttling for remote traffic (since I was recently able to use <em>Remote Access</em> due to escaping CGNAT)</li>
</ul>

<h1 id="a-road-trip-intervenes">A road trip intervenes</h1>

<p>Staring down the barrel of an 11-hour roadtrip for the Fourth this year, I
started loading up some new movies onto the laptops and music onto the iPods.
Wondering what the hell was taking so long, I discovered that Plex was treating
all my local traffic as WAN traffic and throttling it to 20Mbps, as it is
configured to do based on my WAN uplink speed.</p>

<p>Why? Because kubernetes. When your app is running as a container inside of k8s,
there is additional networking happening. This means that every bit of traffic
is coming from a DIFFERENT IP than the actual client IP. It <a href="https://forums.plex.tv/t/x-forwarded-for-trust-configuration/889105">appears that Plex is
ignoring RFC1918 traffic</a> when handling the <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> header. Bummer.</p>

<h1 id="delving-deeper">Delving deeper</h1>

<p>Faced with this limitation, I came up with a plan: two k8s <code class="language-plaintext highlighter-rouge">Service</code> entries of
differing types. How does this get around the problem? Well, I’m using
<a href="https://metallb.io/">metallb</a> to issue IPs for <code class="language-plaintext highlighter-rouge">LoadBalancer</code> services. This
ensures that the IP is not tied specifically to a physical server running my
cluster - its target can move. The implementation details for this result in a
<code class="language-plaintext highlighter-rouge">flannel</code> interface on my k8s nodes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>5: flannel.1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UNKNOWN group default
    link/ether ea:f4:ba:59:7e:5f brd ff:ff:ff:ff:ff:ff
    inet 10.42.0.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::e8f4:baff:fe59:7e5f/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever
</code></pre></div></div>

<p>This is different from the <code class="language-plaintext highlighter-rouge">cni</code> interface that is the Container Network
Interface that the k8s nodes use:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>6: cni0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UP group default qlen 1000
    link/ether be:18:51:21:ad:f5 brd ff:ff:ff:ff:ff:ff
    inet 10.42.0.1/24 brd 10.42.0.255 scope global cni0
       valid_lft forever preferred_lft forever
    inet6 fe80::bc18:51ff:fe21:adf5/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever
</code></pre></div></div>

<p>Take a close look at those IPs, and you’ll see where I’m going. I <a href="https://github.com/sarumont/homelab/blob/26f7fe75d36b4c1d3ce4dcc3fecf276332528d89/tf/plex/main.tf#L67-L85">added a
<code class="language-plaintext highlighter-rouge">NodePort</code>
service</a>
to my Plex deployment. The <code class="language-plaintext highlighter-rouge">NodePort</code> ends up on <code class="language-plaintext highlighter-rouge">cni0</code>, and the <code class="language-plaintext highlighter-rouge">LoadBalancer</code>
ends up on <code class="language-plaintext highlighter-rouge">flannel.1</code>.</p>

<p>Take your <code class="language-plaintext highlighter-rouge">cni0</code> IP to use for your firewall mapping for ingress from your
WAN. Use your <code class="language-plaintext highlighter-rouge">flannel</code> IPs to use for local traffic. Note that you should also
put these IPs in the <em>List of IP addresses and networks that are allowed without 
auth</em> section to only allow local traffic to access your Plex server without
auth. Or just remove everything from that input if, like my case, you have
untrusted VLANs allowed to access Plex.</p>

<p>Check out the <a href="https://github.com/sarumont/homelab/blob/20cfd715329fed4959b5811e8d0dcd90b75d4f6e/tf/plex/README.md">README</a> in my Terraform module for more details on how to implement this.</p>

<h1 id="final-thoughts">Final Thoughts</h1>

<p>Yes, it’s a bit brittle. If my Plex server ends up on another k8s node, my
remote access will stop working. I am OK with that because</p>

<ol>
  <li>k8s doesn’t just move workloads, willy-nilly</li>
  <li>I can always pin the Plex deployment to a node via node selectors</li>
  <li>I can also fix this if I’m the one remote when it breaks (or when I notice
it) by connecting to my Tailnet</li>
  <li>Local traffic is my priority with this setup, so I want THAT to be the more
solid option</li>
</ol>

<p>Brittleness aside, it works well and gets the job done. And besides… what’s a
homelab without a little duct tape?</p>]]></content><author><name>Richard Kolkovich</name></author><category term="homelab" /><summary type="html"><![CDATA[In the current iteration of my homelab, I’m running Plex in a k8s cluster (along with everything else I can). This setup has been humming along nicely until I recently segregated my LAN with VLANs. Why? Because my kids are old enough that they have friends with devices, and I trust them about as far as they can throw me. But I digress…]]></summary></entry><entry><title type="html">Implementing a Ring Buffer in PostgreSQL</title><link href="https://sigil.org/postgresql-ring-buffer/" rel="alternate" type="text/html" title="Implementing a Ring Buffer in PostgreSQL" /><published>2022-01-12T00:00:00+00:00</published><updated>2022-01-12T00:00:00+00:00</updated><id>https://sigil.org/postgresql-ring-buffer</id><content type="html" xml:base="https://sigil.org/postgresql-ring-buffer/"><![CDATA[<p>There are plenty of instances where a persistent ring buffer is a useful construct: search history, remote system stats, etc. This post outlines a method for creating one in PostgreSQL.</p>

<h1 id="sequence">Sequence</h1>

<p>A ring buffer is a fixed-size buffer which cycles once full, overwriting the first entry with the <code class="language-plaintext highlighter-rouge">n+1</code>th (i.e. - if you have a 100-item buffer, the 101st item will overwrite the 1st). To implement something like this with a PostgreSQL table, you will need to key it with a <a href="https://www.postgresql.org/docs/current/sql-createsequence.html">sequence</a>.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="n">SEQUENCE</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="n">history_sequence</span> <span class="k">MINVALUE</span> <span class="mi">1</span> <span class="k">MAXVALUE</span> <span class="mi">100</span> <span class="k">CYCLE</span><span class="p">;</span>
</code></pre></div></div>

<p>This will create a new sequence which will cycle from 1 to 100.</p>

<h1 id="table">Table</h1>

<p>Now, create a basic table to store your ring buffer:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">history</span> <span class="p">(</span>
  <span class="n">sequence</span>          <span class="nb">integer</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span>
  <span class="nb">timestamp</span>         <span class="nb">timestamp</span> <span class="k">not</span> <span class="k">null</span> <span class="k">default</span> <span class="n">now</span><span class="p">(),</span>
  <span class="k">data</span>              <span class="n">jsonb</span>
<span class="p">);</span>
</code></pre></div></div>

<p>Simple enough, right?</p>

<h1 id="upsertion">Upsertion</h1>

<p>To easily implement the write-or-overwrite behavior in a ring buffer, we will need to take advantage of PostgreSQL’s “upsert” functionality - <code class="language-plaintext highlighter-rouge">ON CONFLICT DO UPDATE</code>:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">history</span> <span class="p">(</span><span class="n">sequence</span><span class="p">,</span> <span class="nb">timestamp</span><span class="p">,</span> <span class="k">data</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="n">nextval</span><span class="p">(</span><span class="nv">"history_sequence"</span><span class="p">),</span> <span class="k">default</span><span class="p">,</span> <span class="s1">'{</span><span class="se">''</span><span class="s1">foo</span><span class="se">''</span><span class="s1">: </span><span class="se">''</span><span class="s1">bar</span><span class="se">''</span><span class="s1">}'</span><span class="p">::</span><span class="n">jsonb</span><span class="p">)</span> <span class="k">ON</span> <span class="n">CONFLICT</span> <span class="p">(</span><span class="n">sequence</span><span class="p">)</span> <span class="k">DO</span> <span class="k">UPDATE</span> <span class="k">SET</span> <span class="nb">timestamp</span> <span class="o">=</span> <span class="n">now</span><span class="p">(),</span> <span class="k">data</span> <span class="o">=</span> <span class="s1">'{</span><span class="se">''</span><span class="s1">foo</span><span class="se">''</span><span class="s1">: </span><span class="se">''</span><span class="s1">bar</span><span class="se">''</span><span class="s1">}'</span><span class="p">;</span>
</code></pre></div></div>

<h1 id="putting-it-all-together">Putting it all together</h1>

<p>Generally, you’ll want a sequence per item you’re tracking - be it a user, VM, IP-enabled coffee pot, etc. For this, you’ll want to expand the table to include a secondary identifier and name your sequence accordingly. Let’s use some <code class="language-plaintext highlighter-rouge">pg-promise</code> templated queries to illustrate the real-world example.</p>

<h2 id="sequence-management">Sequence management</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="n">SEQUENCE</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="err">$</span><span class="o">&lt;</span><span class="n">sequenceName</span><span class="p">:</span><span class="n">name</span><span class="o">&gt;</span> <span class="k">MINVALUE</span> <span class="mi">1</span> <span class="k">MAXVALUE</span> <span class="mi">100</span> <span class="k">CYCLE</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="table-definition">Table definition</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">user_history</span> <span class="p">(</span>
  <span class="n">sequence</span>          <span class="nb">integer</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">user_id</span>           <span class="n">uuid</span> <span class="k">not</span> <span class="k">null</span> <span class="k">references</span> <span class="n">user_profile</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">on</span> <span class="k">delete</span> <span class="k">cascade</span><span class="p">,</span>
  <span class="nb">timestamp</span>         <span class="nb">timestamp</span> <span class="k">not</span> <span class="k">null</span> <span class="k">default</span> <span class="n">now</span><span class="p">(),</span>
  <span class="k">data</span>              <span class="n">jsonb</span><span class="p">,</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">sequence</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<h2 id="upsert-statement">Upsert statement</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">user_history</span> <span class="p">(</span>
  <span class="n">sequence</span><span class="p">,</span>
  <span class="n">user_id</span><span class="p">,</span>
  <span class="nb">timestamp</span><span class="p">,</span>
  <span class="k">data</span>
<span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span>
  <span class="n">nextval</span><span class="p">(</span><span class="s1">'$&lt;sequenceName:name&gt;'</span><span class="p">),</span>
  <span class="err">$</span><span class="o">&lt;</span><span class="n">userId</span><span class="o">&gt;</span><span class="p">,</span>
  <span class="k">default</span><span class="p">,</span>
  <span class="err">$</span><span class="o">&lt;</span><span class="n">history</span><span class="o">&gt;</span><span class="p">)</span> 
<span class="k">ON</span> <span class="n">CONFLICT</span> <span class="p">(</span><span class="n">sequence</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span> 
<span class="k">DO</span> <span class="k">UPDATE</span> <span class="k">SET</span> <span class="nb">timestamp</span> <span class="o">=</span> <span class="n">now</span><span class="p">(),</span> <span class="k">data</span> <span class="o">=</span> <span class="err">$</span><span class="o">&lt;</span><span class="n">history</span><span class="o">&gt;</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="history-management">History management</h2>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">sequenceName</span> <span class="o">=</span> <span class="s2">`histseq_</span><span class="p">${</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
<span class="k">await</span> <span class="nx">pg</span><span class="p">.</span><span class="nx">none</span><span class="p">(</span><span class="nx">createSequenceSQL</span><span class="p">,</span> <span class="p">{</span> <span class="nx">sequenceName</span> <span class="p">})</span>
  <span class="p">.</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">pg</span><span class="p">.</span><span class="nx">none</span><span class="p">(</span><span class="nx">insertHistorySQL</span><span class="p">,</span> <span class="p">{</span> <span class="nx">sequenceName</span><span class="p">,</span> <span class="nx">userId</span><span class="p">,</span> <span class="nx">data</span> <span class="p">}));</span>
</code></pre></div></div>

<h2 id="cleanup">Cleanup</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="n">SEQUENCE</span> <span class="n">IF</span> <span class="k">EXISTS</span> <span class="err">$</span><span class="o">&lt;</span><span class="n">sequenceName</span><span class="p">:</span><span class="n">name</span><span class="o">&gt;</span><span class="p">;</span>
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>There you have it - a simple, persistent ring buffer, backed by PostgreSQL.</p>]]></content><author><name>Richard Kolkovich</name></author><category term="dev" /><summary type="html"><![CDATA[There are plenty of instances where a persistent ring buffer is a useful construct: search history, remote system stats, etc. This post outlines a method for creating one in PostgreSQL.]]></summary></entry><entry><title type="html">2019: A Building Year</title><link href="https://sigil.org/2019-a-building-year/" rel="alternate" type="text/html" title="2019: A Building Year" /><published>2019-09-18T00:00:00+00:00</published><updated>2019-09-18T00:00:00+00:00</updated><id>https://sigil.org/2019-a-building-year</id><content type="html" xml:base="https://sigil.org/2019-a-building-year/"><![CDATA[<p>I recently finished approximately 2/3 of the Monarch Mindbender, which was to be my longest MTB race yet at 88 miles. I bailed at 52.3 miles, in the middle of a long but gradual (12 mile) climb up Marshall Pass Rd. First, I ran out of water (started the climb with a full 70 oz.). Next, my legs started going as I was dropped by the duo I had been riding with most of the day. Finally, my headphone batteries died, and I quickly caved. “Why climb, “ I mused, “if I won’t make the cutoff anyway and will have to bail back down this road?” And so I turned around, signed out of the race, and headed to Elevation Beer Company for a well-deserved brew.</p>

<p>A picture is worth a thousand words (at least), and so I present the reason for my abject failure to compete this MTB season:</p>

<p><img src="/images/2018vs2019.png" alt="What a graph!" /></p>

<p>For those unfamiliar with training, the big thing to observe here is the light blue area chart. This represents my fitness as calculated by my heart rate training stress score (hrTSS). Basically, this metric is an estimate of how “fit” one is, derived from exertion during training. Fitness (CTL) is the only metric that really matters for the point of this post.</p>

<p>I’ll point out two things in that graph that I feel are relevant:</p>

<ol>
  <li>My peak CTL this year is 42; I surpassed that in MARCH of 2018</li>
  <li>What the hell was I doing from October through April?!?</li>
</ol>

<h1 id="life">Life</h1>

<p>Life happens. In the smooth arc from my last tracked ride of 2018 (CTL 62) until my first tracked ride of 2019 (CTL 0), I have had plenty going on: transitioning out of the van, buying a house, moving, home improvement projects, and general family responsibilities. This has left no room for training in the off-season, meaning I started from 0 this year. Everything I did last year? Gone. On top of that, I wasn’t even cross-training regularly (as I was before the 2018 season - even while I was building the van out).</p>

<p>Now I don’t mean to throw up my hands and blame <em>life</em>; I chose all the things which took precedent over training in the off-season with full agency. I also did not have an offseason training plan in place to ensure that I would train, regardless of other circumstances. As pathetic as my 2019 season may seem, metrics-wise, I <em>have</em> had consistency in my training, alongside many new life responsibilities.</p>

<p>The takeaway here is this: you <em>cannot</em> allow yourself to get ahead of (or even close to) the end of the runway. If you want to be consistently training, your calendar <em>must</em> reflect that. And it must do so at least a month in advance at all times. It becomes all too easy to ignore things we don’t block time off for, especially when we (as a society) tend to overload ourselves anyway.</p>

<h1 id="the-universe">The Universe</h1>

<p>As my support system is quick to point out, a 52.3 mile ride (69 w/ the bail back to town) on Monarch Crest is no small feat. As I sit here, disappointed in myself and nursing my sore body, I must acknowledge that even a failure to complete is still a success in many regards. I pushed hard, but I bit off more than I could chew. Unfortunately, the Monarch Mindbender will no longer be run as a race. But I have a GPX file, and I will be back out there to conquer it.</p>

<p>So while I am disappointed, I am not deterred. I have recently spent some time to get <em>all</em> my self-quantification data into TrainingPeaks, giving me a single platform from which I can holistically analyze my quantified body. I’ve also been reading books which I should have read a year (or more) ago on training, planning, etc. In short, I am accumulating tools which I can put to use to plan an effective and efficient training plan going forward.</p>

<p>My goal this off-season will be to maintain my base, ideally keeping a CTL of 30-40 to build off of in spring. I’ve done a decent job of incorporating cross-training this season (1-2 strength/HIIT days, 1-2 yoga days each week); I want to bump this up during the off-season to build a bit more base strength and core stability (and flexibility). The rough plan I have in my head now is 2-3 strength, 1-3 yoga, and 2-4 rides each week.</p>

<p>For the remainder of the outdoor season, I’ll be riding as much as possible (especially once the leaves begin to change). But I don’t plan to focus on anything but enjoying myself while I’m out there. It is important that we not lose the love of the sport that brought us to racing in the first place. I do plan to continue something I started this season (kinda): skills building. I’ve spent plenty of time at the bike park, becoming comfortable hucking rollers and table tops. I’ve also tried to start drilling basics (bunny hops, track stands, etc.) at home, and I will continue down this path. I learned to mountain bike with a bunch of ex-roadies, so I never picked up on the “basic” skills that many mountain bikers seem to have.</p>

<h1 id="and-everything">And Everything</h1>

<p>I decided to race the Fall Classic this season, as I am now a Summit County resident. I wrote most ofthis post before this race, and I write now a week after it (having not touched my bike during that week). It was a short race (21 miles) for me, and I felt good going into it. I knew the trails, and I had a very good idea of how my race would lay out. I went into this race with the most comprehensive race plan I have ever had - and that made it seem MUCH more conquerable.</p>

<p>There were three major climbs: up Prospect Hill / Extension Mill (changed Thursday before the race), up B&amp;B / Turks / Sallie Barber / Nightmare on Baldy, and a final section up Boreas Pass Rd. before a long, rowdy descent back to town.</p>

<p>Each climb had a short downhill and/or flowy section following providing, by my calculations, about 10 minutes to “rest” (there is no such thing on a mountain bike) between the 30-45min climbs. I dropped the hammer, with slight restraint, climbing in Z4 and setting PRs for the first two climbs. I could feel my legs full of lead on the third climb and had to push harder to keep my pace. As I turned down Indiana Creek Road, I had to take a few seconds of stretches and a bit of a break before hitting the gas on my descent.</p>

<p>Now, I had ridden up Indiana Creek Road during the Breck Epic Stage 1 (I was just out for a training ride and happened to hook up with the tail of the race). It was raining and miserable that day, and I remembered the trail as being a super chunky and gnarly climb. Well, it was a super chunky, super fast, and super gnarly descent, too. So chunky, in fact, that I dropped my chain. Twice. This has happened one other time this season - during the Firecracker 50, descending another chunky Jeep road in 11th gear. I’ll be shortening it a bit in the offseason…</p>

<p>So I had to stop twice to fix my chain…one of those times, needing to untangle it before resetting it on my crank. Somewhere in the neighborhood of 7 racers passed me while I was fixing my chain. I didn’t know what division they were in, as we didn’t have any markings to determine this. So I rode hard out, through the creek (cold and refreshing…but locked up the leg muscles), and caught up to one of them toward the bottom of Blue River Trail. I knew there was a short, punchy climb coming, so I prepared myself to crank it, muttering “Seek and Destroy” to myself (I didn’t want to take my hand off the handlebar to navigate to the Metallica song by the same name which was coming up in my playlist).</p>

<p>He blew up on the climb, and I passed him, my right quad nearly seizing shortly thereafter. I pedaled through it, getting to the final, short and rolling descent. Narrowly missing a wipe on a corner in pea gravel leading up to the Finish line, I rolled across the line 10th in my division, crushing my 2:30:00 goal by over 30 minutes. Final time: 1:59:02.</p>

<p>I talked to the guy I passed afterward and he was, of course, not in my division. I walked away very pleased with my performance, but I came to be VERY annoyed at my minor mechanical when I saw the full results streaming at Carter Park: the time on the side of the trail may have cost me a podium spot. I was 4:09 behind 3rd place, and I estimate I was off the bike for 2-4 minutes due to the chain. Frustrating? You bet.</p>

<h1 id="wrap-up">Wrap up</h1>

<p>I originally titled this post <em>A Disappointing Season</em>, but I have been told that I can’t call it that (the post or the season). Instead, I have re-framed it as a building year: building my ability to ride at 9600’, building an efficient training protocol, building my skills and knowledge of my new hometown trails, and building balance between racing, work, and, most importantly, family.</p>

<p>I have begun mapping out my offseason training a bit, and, depending on where my CTL sits, I need anywhere from 180-230 hrTSS per week to maintain. In the early season this year, I took many spin classes before the snow melted enough for me to get out on the trail, and I grew to enjoy them in some weird, Nightingale-esque way. That means 2-3hr of spin classes every week will allow me to keep my baseline. I will definitely refine the plan a bit, adding some periodicity into it, too.</p>

<p>I am also considering getting a road or cross bike for outdoor mud season training. A fat bike is, of course, on the wishlist for true winter riding. Lastly, I hope to make a pilgrimage or two down to the desert during the winter to keep my technical chops up.</p>

<p>And I still have much reading and revising to do, especially when it comes to planning out my 2020 season. I will definitely be racing more in Summit County - it’s my home, so I feel like I <em>should</em> be running all the races I can here. I’m not sure what my Peak Race will be next season; I would like to train for the Breck Epic, but I feel like that may be a bit of a stretch for my 3rd season of racing. After the Fall Classic, I’m optimistic that I can find myself on the podium next season, if not on a big endurance race, then on a shorter, XC-style event.</p>

<p>This season, I had taken a lot more onto my plate, both in the realm of racing and everything else. I look forward to balancing myself, family, work, and training even better going forward while working my way into being competitive. Endurance sports take time to ramp up, and I’m only in my second year focusing on racing. As with everything, I must continue to evaluate and tweak to improve. Build, measure, grow. And shred.</p>]]></content><author><name>Richard Kolkovich</name></author><summary type="html"><![CDATA[I recently finished approximately 2/3 of the Monarch Mindbender, which was to be my longest MTB race yet at 88 miles. I bailed at 52.3 miles, in the middle of a long but gradual (12 mile) climb up Marshall Pass Rd. First, I ran out of water (started the climb with a full 70 oz.). Next, my legs started going as I was dropped by the duo I had been riding with most of the day. Finally, my headphone batteries died, and I quickly caved. “Why climb, “ I mused, “if I won’t make the cutoff anyway and will have to bail back down this road?” And so I turned around, signed out of the race, and headed to Elevation Beer Company for a well-deserved brew.]]></summary></entry><entry><title type="html">Race Report: Big Bear Grizzly 75k (and season wrapup)</title><link href="https://sigil.org/race-report-grizzly-75k/" rel="alternate" type="text/html" title="Race Report: Big Bear Grizzly 75k (and season wrapup)" /><published>2018-11-12T00:00:00+00:00</published><updated>2018-11-12T00:00:00+00:00</updated><id>https://sigil.org/race-report-grizzly-75k</id><content type="html" xml:base="https://sigil.org/race-report-grizzly-75k/"><![CDATA[<p>As if on cue, here is my season wrapup, only a month and a half after the Big Bear Grizzly 75k. Let’s get to it…</p>

<h1 id="big-bear">Big Bear</h1>

<p>Unfortunately, it was a dry year, and Big Bear Lake was quite visibly low. No swimming was possible all season, so a nice, cold fall dip was out. The drive into Big Bear, however, was quite excellent. The vistas were beautiful, and the valley was fantastic. The town of Big Bear Lake resembled the mountain towns of my beloved Colorado; the feel was definitely one of quiet calm, awaiting the snow and resurgence of the winter tourism.</p>

<p>I found a fantastic camping spot for Serenity near the town and hit the trails on Thursday afternoon and Friday. I was feeling pretty good going into the race with the exception of the Radford Climb looming in my mind.</p>

<p>On race day, I felt energized and strong. The opening climb was a boring, fire road slog but served to spread everyone out pretty well. The initial descent, however, bunched us right back up. The hundo crew had ripped up the freshly-rebuilt <a href="https://www.strava.com/segments/838293">Seven Oaks</a> on their descent leaving us with some seriously loose, sandy downhill action. I got bunched up behind a few riders, as the trail was too narrow to pass in most places. After that intro, however, I was jamming on.</p>

<p>The middle segment of the race felt amazing to me. There was another crummy road climb leading to some dope singletrack on the Southern loop on the course. I picked up and dropped a few riders on this section which made me feel even better about how my race was going. Come to find out, I was playing leapfrog with Jen Toops who would go on to win the women’s 75k and the NUE Women’s Marathon title. We chatted a bit on the paved section leading up to Radford, and she got an update on her closest competitor from her SAG crew. And proceeded to drop me; I didn’t see her again until after I crossed the finish line.</p>

<p>Radford. Ah, Radford. The billing for the Grizzly puts the Radford Climb into your nightmares:</p>

<blockquote>
  <p>The Radford Climb has made a name for itself as one of the most dreaded 5 mile climbs in Southern California.</p>
</blockquote>

<p>The only climb I have experienced that is anything like it is at my nemesis, <a href="https://www.mtbproject.com/trail/7001750/golden-gate-full-pull">Golden Gate Canyon State Park</a>. Discounting the fact that I have had two terrible rides at Golden Gate, Radford is basically all fire road and fully-exposed, making it qualitatively worse than the GG climb even if the grade is equivalent. In late September, the exposure wasn’t a terrible thing. I did not overheat, but I was just plain bored mentally. The views were great, but climbing at 3mph means they don’t change that often. And at some point, I began to get a bit of gastral discomfort. That’s what I get for not learning my lessons from the last race (even though I was better, I still was not 100% strict on my diet leading up to the race).</p>

<p>After a stop at the porta-john at the aid station at the top of the climb, it was time for some flowy riding on Skyline and a drop down the same fire road we climbed to start the day. I finished strong and came in 17th out of 22 - much worse, relatively, than I thought I was doing. (even though I claimed 10th overall <a href="https://www.strava.com/activities/1873848940/">on Strava</a>)</p>

<h1 id="season-recap">Season Recap</h1>

<p>OK, so I did it - I completed 4 of the 5 NUE races I signed up for. That puts me at 9th out of 10 who finished four or more races in the nation. For my first endurance MTB Racing season? I’d say that’s not too shabby.</p>

<h2 id="whats-next">What’s next?</h2>

<p>Well, I’ve largely been doing nothing on the bike for the past month and a half, save a stop at Phil’s World on the way back to Colorado. Side note: if you own a mountain bike, you owe it to yourself to make it down to Cortez, CO to check it out. Seriously, it was the most fun I have ever had on a mountain bike.</p>

<p>I’m beginning to get myself back on a regular cross-training schedule (I’ve been quite erratic lately) to both balance my training (my arms have grown weak…) and keep my base leg strength up. I am, however, transitioning to a stationary lifestyle (more on that in a future post), so I don’t think I’ll be competing in the NUE series next season. I’ll come back when I get to a point that I am actually competitive, if that day indeed comes.</p>

<p>As I’ll be moving back to Colorado, I plan to adopt a more Colorado-friendly race schedule: local races which start later in the season. It’s hard to train when there’s snow on the trails. I am tentatively planning on competing in the <a href="https://www.warriorscycling.com/">Rocky Mountain Endurance Series</a> and probably tossing in a few random events, as that is only a three-race series. On the tentative list are the Gunnison Growler, the Breck 68, and Kokopelli. I’m planning to plan next month, so we’ll see what I decide upon.</p>

<p>Additionally, I want to bring a bit more balance to my training, so I’m going to look into another Tough Mudder or Spartan Race to keep myself honest about training something other than my cycling muscles. For now, I’ll be hitting the home gym, getting ready for ski season, working on my ATP for next season, and researching indoor trainers…</p>]]></content><author><name>Richard Kolkovich</name></author><summary type="html"><![CDATA[As if on cue, here is my season wrapup, only a month and a half after the Big Bear Grizzly 75k. Let’s get to it…]]></summary></entry></feed>