<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Articles &#8211; DV</title>
	<atom:link href="https://glyphy.com/c/a/feed/" rel="self" type="application/rss+xml" />
	<link>https://glyphy.com</link>
	<description>A few of my faevourite things</description>
	<lastBuildDate>Sun, 26 Oct 2025 00:55:34 +0000</lastBuildDate>
	<language>en-CA</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>My local WordPress dev environment</title>
		<link>https://glyphy.com/a/2024/my-local-wordpress-dev-environment/?pk_campaign=feed&#038;pk_kwd=my-local-wordpress-dev-environment</link>
					<comments>https://glyphy.com/a/2024/my-local-wordpress-dev-environment/?pk_campaign=feed&#038;pk_kwd=my-local-wordpress-dev-environment#respond</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Sat, 03 Aug 2024 18:08:11 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[tech]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=1807</guid>

					<description><![CDATA[I wanted to make some changes to the excellent Autonomie theme, but because I'm yet a Wordpress development tyro I wanted an environment that I could break. At the same time I didn't want to manually copy changes from local to the real remote env if those changes did turn out to work. There's probably a better way of doing all this, but here's what worked for me.<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2024%2Fmy-local-wordpress-dev-environment%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dmy-local-wordpress-dev-environment&amp;action_name=My%20local%20WordPress%20dev%20environment&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p>I wanted to make some changes to the excellent <a href="https://notiz.blog/projects/autonomie/" data-wpel-link="external" rel="external noopener">Autonomie theme</a>, but because I&#8217;m yet a WordPress development tyro I wanted an environment that I could break. At the same time I didn&#8217;t want to manually copy changes from local to the real remote env if those changes did turn out to work. There&#8217;s probably a better way of doing all this, but here&#8217;s what worked for me.</p>



<ol class="wp-block-list">
<li>Install Docker</li>



<li>(optional) Edit your <a href="https://en.wikipedia.org/wiki/Hosts_(file)" data-wpel-link="external" rel="external noopener">hosts file</a> and make <code>local.host</code> point at <code>127.0.0.1</code>. This is maybe unnecessary, but browsers and various tools often have special treatment for &#8220;localhost&#8221; or 127.0.0.1, which has caused problems for me in the past.</li>



<li>Locally, create a root dev directory and subdirectories <code>db</code>, <code>wordpress</code>, and whatever themes you want to work on (I used <code>autonomie-dev</code>). You can skip the theme directory if your theme doesn&#8217;t require any special build steps. I <a href="https://github.com/pfefferle/autonomie/" data-wpel-link="external" rel="external noopener">git-cloned Autonomie</a>.</li>



<li>Create a <code>docker-compose.yaml</code> in the root directory. <a href="#composefile">See below</a>.</li>



<li>Run <code>docker compose up -d</code>. This should get an empty WordPress installation started.</li>



<li><code>docker compose down</code> to stop it, so we could import the remote installation.</li>



<li>Copy over the remote wordpress directory into the local <code>wordpress</code>/ overwriting any existing files. I used <code>rsync -av --delete remote:/path/to/wordpress/ wordpress/</code> from root.</li>



<li>If you don&#8217;t want to import your remote content, skip this step. Otherwise:
<ul class="wp-block-list">
<li><code>docker compose up db</code> to start only MySQL</li>



<li>On the remote side run <code>mysqldump</code> and import locally via <code>mysql -ppassword &lt; dump.sql</code> </li>



<li><code>docker compose up phpmyadmin</code> . Log in to http://localhost:8180 with the MySQL username + password. In the &#8220;options&#8221; table update two records to point to <code>http://localhost:8080/</code> (or <code>http://local.host:8080/</code>, if you updated the hosts file). The records are <code>siteurl</code> and <code>home</code> under the <code>option_name</code> column. This ensures that your local setup doesn&#8217;t redirect to your real remote domain.</li>
</ul>
</li>



<li><code>docker compose up -d</code> . You should be able to see your site on <code>http://localhost:8080</code> (or <code>http://local.host:8080</code> if you changed the hosts file). Now you can change the theme files as needed.</li>
</ol>



<p>I also wanted to be able to version control the theme with Git and have it publish the local changes on the remote side when I push. For that I followed <a href="https://aurooba.com/how-to-deploy-wordpress-themes-using-git/" data-wpel-link="external" rel="external noopener">this tutorial</a> with some tweaks:</p>



<ol class="wp-block-list">
<li>Set up a bare git repo outside the web root on your remote host with <code>git init --bare .</code> 
<ul class="wp-block-list">
<li>e.g. if your web root is <code>/var/www/html/wordpress</code> run <code>git init --bare /var/www/html/your_theme</code></li>
</ul>
</li>



<li>Create an empty theme directory. I used <code>autonomie-dv</code>, so for me the location was <code>/var/www/html/wordpress/wp-content/themes/autonomie-dv</code>. Set this up in the local wordpress install too.</li>



<li>In the bare git repo, under <code>hooks</code> create a file called <code>post-update</code>. Make it executable with <code>chmod ug+x post-update</code> . <a href="#postupdate">Contents are below</a>. Now whenever you push into this repository it&#8217;ll copy over the theme files into your actual remote installation.</li>



<li>Inside your local theme directory at the root level, <code>git init .</code> and then add the bare repo as a git remote: <code>git remote add origin your-ssh-host:/var/www/html/your_theme</code> .</li>



<li>(optional) Specifically for Autonomie, I removed <code>_build</code> from <code>.gitignore</code> because I didn&#8217;t want to run the build step remotely. I wanted to build locally and then have the git hook just copy the files over. I may change my mind about this later. I also tweaked the <code>grunt clean</code> step to not remove the <code>_build</code> directory, because that would break the Docker volume bind and require restarting the containers for every change to remount the recreated directory.
<ul class="wp-block-list">
<li>I tweaked the <code>watch</code> target in Gruntfile.js to trigger on more file extensions, so I could run <code>grunt build watch</code> and have it automatically rebuild on any local changes.</li>
</ul>
</li>
</ol>



<p>Now when you <code>git push</code> you should see rsync output that copies the files over.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="composefile">docker-compose.yaml</h2>



<pre class="wp-block-code"><code>services:
  db:
    restart: unless-stopped
    image: mysql:8
    command: '--mysql-native-password=ON --disable-log-bin --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci'
    environment:
      MYSQL_DATABASE: "wordpress"
      MYSQL_ROOT_PASSWORD: "password"
      MYSQL_USER: "wordpress"
      MYSQL_PASSWORD: "password"
    volumes:
      - "./db:/var/lib/mysql"

  phpmyadmin:
    depends_on:
      - db
    image: phpmyadmin/phpmyadmin:latest
    container_name: phpmyadmin
    ports:
      - 8180:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORD: "password"

  wordpress:
    depends_on:
      - db
    restart: unless-stopped
    image: wordpress
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: "password"

      # If you imported your DB and it uses a table prefix uncomment
      # this and update as needed.
      # WORDPRESS_TABLE_PREFIX: "wp_abc"

    volumes:
      - "./wordpress:/var/www/html"

      # Uncomment the next line and update the paths for your theme.
      # My local theme dir is autonomie-dev, but the theme name in
      # WordPress is dv-autonomie. You could totally name them the
      # same. Also note that I'm using _build/ because Autonomie
      # requires a build step (using grunt) and that's where it
      # outputs the results.
      # - "./autonomie-dev/_build:/var/www/html/wp-content/themes/dv-autonomie:ro"
    ports:
      - "127.0.0.1:8080:80"</code></pre>



<h2 class="wp-block-heading" id="postupdate">post-update</h2>



<pre class="wp-block-code"><code>#!/bin/sh

# Check out the theme into a temporary directory and then
# copy it over via rsync into the actual WordPress theme location

export target="../wordpress/wp-content/themes/autonomie-dv"
export GIT_WORK_TREE="/tmp/autonomie-dv"
mkdir -p "$GIT_WORK_TREE"
git checkout -f

# This will overwrite the target location. If you're not sure that
# you got it right, add '-n' to do a dry-run. i.e.:
# rsync -avn --delete "${GIT_WORK_TREE}/_build/" "${target}/"
rsync -av --delete "${GIT_WORK_TREE}/_build/" "${target}/"</code></pre>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2024%2Fmy-local-wordpress-dev-environment%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dmy-local-wordpress-dev-environment&amp;action_name=My%20local%20WordPress%20dev%20environment&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2024/my-local-wordpress-dev-environment/?pk_campaign=feed&#038;pk_kwd=my-local-wordpress-dev-environment/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>REST API design tip: have something to update</title>
		<link>https://glyphy.com/a/2024/rest-api-design-tip-have-something-to-update/?pk_campaign=feed&#038;pk_kwd=rest-api-design-tip-have-something-to-update</link>
					<comments>https://glyphy.com/a/2024/rest-api-design-tip-have-something-to-update/?pk_campaign=feed&#038;pk_kwd=rest-api-design-tip-have-something-to-update#respond</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Sun, 10 Mar 2024 22:43:06 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[APIs]]></category>
		<category><![CDATA[tech]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=1530</guid>

					<description><![CDATA[If you're struggling to fit an operation into the REST API architecture paradigm, try adding a field to an existing model or creating a new model, such that a change to some fields in the model represents the operation.<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2024%2Frest-api-design-tip-have-something-to-update%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Drest-api-design-tip-have-something-to-update&amp;action_name=REST%20API%20design%20tip%3A%20have%20something%20to%20update&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p>If you&#8217;re struggling to fit an operation into the REST API architecture paradigm, try adding a field to an existing model or creating a new model, such that a change to some fields in the model represents the operation.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>A colleague asked this (paraphrased) question:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>I’m adding an endpoint that will check an authentication token for a user in our token store and refresh it if necessary (<code>/user-auth-tokens/{userId}</code>). Which HTTP method should I use? POST seems to be a bad fit. <a href="https://jsonapi.org/" data-wpel-link="external" rel="external noopener">JSON:API</a> doesn&#8217;t support PUT. Should I use PATCH? JSON:API requires a body in PATCH requests, but I have no data to update. I&#8217;m passing the user id in the URL. Should I move it to the body, so I could use PATCH? Using GET seems worse, because I&#8217;m potentially making an update to refresh the token if it&#8217;s expired, and I don&#8217;t actually want the client to be able to fetch the user&#8217;s token either way. What do I do?</p>
</blockquote>



<p>Note that while the question references <a href="https://jsonapi.org/" data-wpel-link="external" rel="external noopener">JSON:API</a>, which we use at my current job, the problem is more general. Assuming you don&#8217;t want to drop below <a href="https://martinfowler.com/articles/richardsonMaturityModel.html#level2" data-wpel-link="external" rel="external noopener">level 2 of the Richardson Maturity Model</a>, <strong>when designing REST APIs how do you represent operations that are conditional or don&#8217;t neatly align with the common HTTP verbs?</strong></p>



<p>The following is my response. My colleague found it useful, so I&#8217;m reposting it here (after a bit of cleanup to remove internal/sensitive info).</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>I love this question because it lets me share a general approach with the group that’s worked well for me in the past:</p>



<p><strong>Rule of thumb for REST: if you’re acting on something without updating anything, change the model so you have something to update!</strong> 😀</p>



<p>In this case, you could have&nbsp;<code>GET /user-auth-tokens/{userId}</code>&nbsp;return a&nbsp;<code>refreshedOn</code>&nbsp;datetime field, such that the client could then call&nbsp;<code>PATCH /user-auth-tokens/{userId}</code>&nbsp;and set&nbsp;<code>refreshedOn</code> to the current time. The server would respond with the updated&nbsp;<code>refreshedOn</code>.</p>



<p>Let&#8217;s pretend the current time is <code>2024-03-09T10:00:00.000Z</code>. The client asks the server to refresh the user&#8217;s token if it&#8217;s invalid:</p>



<pre class="wp-block-code"><code>PATCH /user-auth-tokens/someUserId123

{"refreshedOn": "2024-03-09T10:00:00.000Z"}</code></pre>



<p>The server could respond with the &#8220;new now&#8221; from the server&#8217;s perspective, if the token was refreshed:</p>



<pre class="wp-block-code"><code>200 OK

{"refreshedOn": "2024-03-09T10:00:00.150Z"}</code></pre>



<p>Note that it took the server 150ms to refresh the token, so its date is different in the response than what the client provided. If the server supports the ability to set the refresh date explicitly, the date in the response may match the client&#8217;s.</p>



<p>If the token is still valid, the server could respond with the old token refresh date:</p>



<pre class="wp-block-code"><code>200 OK

{"refreshedOn": "2024-02-01T09:59:14.697Z"}</code></pre>



<p>But the point is, there&#8217;s now a field that the client changes (or tries to change) to indicate their intent, and the server responds with the result. This means you don&#8217;t need a separate endpoint for different operations on the same entity. An operation here is actually a modification (<code>PATCH</code>) of one or more data model fields. This leaves <code>POST</code>, <code>PUT</code>, and <code>DELETE</code> for creating, replacing, or deleting the entity as a whole. This works for a lot of situations where you’d usually be tempted to switch to the RPC-over-HTTP style, i.e. <code>POST /user-auth-tokens/{userId}/refresh-if-needed</code>.</p>



<p>Let&#8217;s take another example. This time, we want to migrate legacy tokens to a new format. Instead of <code>/user-auth-tokens/{userId}/migrate-legacy</code> we could add a <code>version</code> field to the existing model. So, it would now contain two fields: <code>refreshedOn</code> and <code>version</code>. To migrate a user&#8217;s token, the client would (again) call <code>PATCH /user-auth-tokens/{userId}</code> and set the new version in the request body:</p>



<pre class="wp-block-code"><code>PATCH /user-auth-tokens/someUserId123

{"version": 2}</code></pre>



<p>The server may reply with a bunch of different error codes if, for example, the token is already at version 2 or if it can&#8217;t be migrated for some reason. A 200 OK would mean “migration is done” and <code>version</code> in the response would be <code>2</code>.</p>



<p>This extends to batch operations too! For these cases, it&#8217;s best to add new endpoints to represent the batch operation as its own entity. The client would call&nbsp;<code>POST /user-auth-token-migrations</code>&nbsp;with some user ids and versions in the body to kick off a migration. The response is <code>201</code> with an added header <code>Location:&nbsp;/user-auth-token-migrations/migration-id-123</code>. Calling&nbsp;<code>GET /user-auth-token-migrations/migration-id-123</code>&nbsp;returns the model representing the status of the batch operation, e.g.&nbsp;<code>{"status":"running"}</code>. Client could <code>PATCH</code> with&nbsp;<code>{"status":"paused"}</code>&nbsp;to pause the batch, if the server supports that. If that&#8217;s not supported, the server could reply with a 400-series error.</p>



<p>The beauty of this approach is the server may execute <em>many</em> operations behind the scenes and update <em>multiple</em> fields in the response without requiring a new endpoint. This stops the proliferation of endpoints that&#8217;s common with RPC APIs and allows each one to evolve along with the product. For example, let’s go back to the token refresh endpoint. Let&#8217;s say the model is now two fields: <code>refreshedOn</code> and <code>expiresOn</code>. When clients call <code>PATCH</code> with <code>{"refreshedOn": "2024-03-09T10:00:00.000Z"}</code> the server would respond with that date in <code>refreshedOn</code> <em>and</em> a new <code>expiresOn</code>. Maybe that&#8217;s set to one day later (2024-03-10T10:00:00.000Z), if that’s the standard token validity duration:</p>



<pre class="wp-block-code"><code>PATCH /user-auth-tokens/someUserId123

{"refreshedOn": "2024-03-09T10:00:00.000Z"}</code></pre>



<pre class="wp-block-code"><code>200 OK

{
  "refreshedOn": "2024-03-09T10:00:00.000Z",
  "expiresOn":   "2024-03-10T10:00:00.000Z"
}</code></pre>



<p>Now the client knows when it should request the next refresh.</p>



<p>What if we want to support the ability for some clients to get a long-lived token? They could just send both fields in the <code>PATCH</code>, setting <code>expiresOn</code> to something like <code>2025-01-01</code>. If that’s allowed, the server responds with 200 OK and new values in both fields, including the 2025 date in <code>expiresOn</code>. If not, the response is a 400-series error, or a <code>200 OK</code> with new <code>refreshedOn</code> and the standard one-day <code>expiresOn</code> (basically ignoring the client’s update to the field). Perhaps we now support extending an existing valid token without refreshing it? Clients can <code>PATCH</code> with just the new <code>expiresOn</code> date! With the RPC style, you have to either create separate endpoints <code>/refresh</code> and <code>/extend</code>  or move this logic into query params (<code>/user-auth-tokens/someUserId123?extendTo=date</code>). The latter doesn’t seem too bad at first, but it opens the door to infinite scope creep of the endpoint, because the query param becomes a catch-all &#8220;do something&#8221; instruction, even if it doesn&#8217;t make sense for the entity (e.g. <code>/user-auth-tokens/{userId}?generateReport=true</code>). <code>PATCH</code>ing fields in the model constrains the endpoint to the entity it represents.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>This &#8220;one weird trick&#8221; really unlocked REST for me when I was struggling with it earlier in my career. It feels strange at first to represent operations as updates to some fields of the entity model. But REST is all about entities! The fundamental contract is: clients perform operations by attempting to update entities to look a desired way, and the server responds with the model that represents the result of that attempt. The client then needs to take that response as the new true state of the entity, even if nothing changed or if there were more changes to the model than the client expected.</p>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2024%2Frest-api-design-tip-have-something-to-update%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Drest-api-design-tip-have-something-to-update&amp;action_name=REST%20API%20design%20tip%3A%20have%20something%20to%20update&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2024/rest-api-design-tip-have-something-to-update/?pk_campaign=feed&#038;pk_kwd=rest-api-design-tip-have-something-to-update/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WordPress Gallery block using too many resources</title>
		<link>https://glyphy.com/a/2023/wordpress-gallery-block-using-too-many-resources/?pk_campaign=feed&#038;pk_kwd=wordpress-gallery-block-using-too-many-resources</link>
					<comments>https://glyphy.com/a/2023/wordpress-gallery-block-using-too-many-resources/?pk_campaign=feed&#038;pk_kwd=wordpress-gallery-block-using-too-many-resources#respond</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Wed, 01 Nov 2023 04:04:15 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[tech]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=1479</guid>

					<description><![CDATA[Instead of adding the Gallery block and dropping a bunch of images onto it, open the Media Gallery, upload the images there, and then add the gallery to the Gallery block.<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2023%2Fwordpress-gallery-block-using-too-many-resources%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dwordpress-gallery-block-using-too-many-resources&amp;action_name=WordPress%20Gallery%20block%20using%20too%20many%20resources&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p>Instead of adding the Gallery block and dropping a bunch of images onto it, open the Media Gallery, upload the images there, and then add the gallery to the Gallery block.</p>



<figure class="wp-block-gallery alignwide has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><a href="https://glyphy.com/wp-content/uploads/2023/10/image.png?x58116" data-wpel-link="internal"><img fetchpriority="high" decoding="async" width="1022" height="474" data-id="1480" loading="lazy" src="https://glyphy.com/wp-content/uploads/2023/10/image.png?x58116" alt="Screenshot of the WordPress Gallery block indicating that the Media Library link should be clicked" class="wp-image-1480" srcset="https://glyphy.com/wp-content/uploads/2023/10/image.png 1022w, https://glyphy.com/wp-content/uploads/2023/10/image-300x139.png 300w, https://glyphy.com/wp-content/uploads/2023/10/image-768x356.png 768w, https://glyphy.com/wp-content/uploads/2023/10/image-900x417.png 900w" sizes="(max-width: 1022px) 100vw, 1022px" /></a><figcaption class="wp-element-caption">After adding the Gallery block click Media Library</figcaption></figure>



<figure class="wp-block-image size-large"><a href="https://glyphy.com/wp-content/uploads/2023/10/image-1.png?x58116" data-wpel-link="internal"><img decoding="async" width="1024" height="561" data-id="1481" loading="lazy" src="https://glyphy.com/wp-content/uploads/2023/10/image-1-1024x561.png?x58116" alt="Screenshot of the WordPress Media Gallery" class="wp-image-1481" srcset="https://glyphy.com/wp-content/uploads/2023/10/image-1-1024x561.png 1024w, https://glyphy.com/wp-content/uploads/2023/10/image-1-300x164.png 300w, https://glyphy.com/wp-content/uploads/2023/10/image-1-768x420.png 768w, https://glyphy.com/wp-content/uploads/2023/10/image-1-900x493.png 900w, https://glyphy.com/wp-content/uploads/2023/10/image-1.png 1410w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><figcaption class="wp-element-caption">Inside the Media Library you can drag and drop or select multiple files to upload</figcaption></figure>
</figure>



<p>Why? It seems that dragging and dropping or selecting several images directly from the Gallery block sends them all to the server concurrently. The uploads may be completing sequentially, but WordPress does a lot of post-processing like resizing for each uploaded image. This post-processing is very memory and cpu intensive, which can cause the server to respond very slowly or outright crash<sup data-fn="a22d01ab-2e06-444c-856c-711cbc263472" class="fn"><a href="https://glyphy.com/a/2023/wordpress-gallery-block-using-too-many-resources/" id="a22d01ab-2e06-444c-856c-711cbc263472-link" data-wpel-link="internal">1</a></sup> if too much of it is happening at once. At least as of WordPress 6.3.2, this background post-processing runs in parallel for all the images selected from or drag-and-dropped directly onto the Gallery block. With the Media Library approach, the images are processed one by one.</p>



<p>If you&#8217;re struggling to upload even a single image you may need to tweak a few PHP settings<sup data-fn="25eedbe5-d3d9-4033-b7ae-62714cf7f2ac" class="fn"><a href="#25eedbe5-d3d9-4033-b7ae-62714cf7f2ac" id="25eedbe5-d3d9-4033-b7ae-62714cf7f2ac-link">2</a></sup>. <a href="https://www.wpbeginner.com/beginners-guide/how-to-upload-large-images-in-wordpress/" data-wpel-link="external" rel="external noopener">This article here describes a few ways of doing it</a>. The htaccess mechanism worked well for me:</p>



<pre class="wp-block-code"><code># I use Cloudflare as a proxy for my site. Their free tier has some additional limitations.
# Cloudflare's max is 100M, so there's no point in allowing file uploads larger than that.
php_value upload_max_filesize 100M
php_value post_max_size 100M

# Cloudflare's timeout is around 100s. PHP default is 30s. The value below could probably be 100, but I'm leaving it higher so that image post-processing completes in the background even if the request is aborted by the proxy.
php_value max_execution_time 300

# Default is 128M and not enough for images &gt;~6mb. Photos from recent phones are around this size, but often larger.
php_value memory_limit 256M</code></pre>



<p>I initially tried tuning the <code>max_file_uploads</code> setting. It controls how many concurrent file uploads PHP will accept. It didn&#8217;t make a difference in my testing, though! WP uploads images one at a time anyway, but background processing is concurrent and not restricted by this. Default is 20.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>When the server is overloaded it either responds with a generic HTTP 500-series error or closes the connection. WordPress can&#8217;t discern the cause of the issue and, unfortunately, its error messages aren&#8217;t clear. They may say something like &#8220;<em>The response is not a valid JSON response.</em>&#8221; or &#8220;<em>HTTP Error</em>&#8220;. It&#8217;s not a valid JSON response because the web server is returning an HTML page containing some human-readable error text. If you open developer tools in the browser, reload the page, and click on the failing media requests in the Networks tab, you&#8217;ll see what I mean.</p>



<figure class="wp-block-gallery alignwide has-nested-images columns-2 is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><a href="https://glyphy.com/wp-content/uploads/2023/10/image-2.png?x58116" data-wpel-link="internal"><img decoding="async" width="720" height="386" data-id="1482" loading="lazy" src="https://glyphy.com/wp-content/uploads/2023/10/image-2.png?x58116" alt="Three WordPress error message banners saying &quot;The response is not a valid JSON response.&quot;" class="wp-image-1482" srcset="https://glyphy.com/wp-content/uploads/2023/10/image-2.png 720w, https://glyphy.com/wp-content/uploads/2023/10/image-2-300x161.png 300w" sizes="(max-width: 720px) 100vw, 720px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://glyphy.com/wp-content/uploads/2023/10/image-3.png?x58116" data-wpel-link="internal"><img decoding="async" width="1024" height="270" data-id="1483" loading="lazy" src="https://glyphy.com/wp-content/uploads/2023/10/image-3-1024x270.png?x58116" alt="Screenshot of the Firefox developer tools showing WordPress media upload requests failing and responding with an HTML page saying &quot;503 Service Unavailable&quot;" class="wp-image-1483" srcset="https://glyphy.com/wp-content/uploads/2023/10/image-3-1024x270.png 1024w, https://glyphy.com/wp-content/uploads/2023/10/image-3-300x79.png 300w, https://glyphy.com/wp-content/uploads/2023/10/image-3-768x203.png 768w, https://glyphy.com/wp-content/uploads/2023/10/image-3-1536x406.png 1536w, https://glyphy.com/wp-content/uploads/2023/10/image-3-2048x541.png 2048w, https://glyphy.com/wp-content/uploads/2023/10/image-3-900x238.png 900w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>
</figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><a href="https://core.trac.wordpress.org/ticket/46309#ticket" data-wpel-link="external" rel="external noopener">This ticket</a> may be about this bug, or at least a very similar issue.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>


<ol class="wp-block-footnotes"><li id="a22d01ab-2e06-444c-856c-711cbc263472">That&#8217;s what happened in my case. I run WordPress inside Docker on a small VPS and give the container a memory limit. When it goes over that limit the container gets killed (out-of-memory / OOM).  <a href="#a22d01ab-2e06-444c-856c-711cbc263472-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="25eedbe5-d3d9-4033-b7ae-62714cf7f2ac">If you&#8217;re using a shared host like wordpress.com this may not be allowed. Your best bet is to reach out to their support folks and plead your case for a higher memory limit. <a href="#25eedbe5-d3d9-4033-b7ae-62714cf7f2ac-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol><img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2023%2Fwordpress-gallery-block-using-too-many-resources%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dwordpress-gallery-block-using-too-many-resources&amp;action_name=WordPress%20Gallery%20block%20using%20too%20many%20resources&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2023/wordpress-gallery-block-using-too-many-resources/?pk_campaign=feed&#038;pk_kwd=wordpress-gallery-block-using-too-many-resources/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Protecting this WordPress site from Mastodon traffic floods with caching (Caddy server version)</title>
		<link>https://glyphy.com/a/2023/protecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version/?pk_campaign=feed&#038;pk_kwd=protecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version</link>
					<comments>https://glyphy.com/a/2023/protecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version/?pk_campaign=feed&#038;pk_kwd=protecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version#comments</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Mon, 22 May 2023 01:32:17 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[caddy]]></category>
		<category><![CDATA[mastodon]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=1219</guid>

					<description><![CDATA[Mastodon has some fun questionable interesting architectural choices. When you post a link, instead of your server sending metadata about the link (such as title, snippet, and image) out to other servers, it just shares the link raw, which triggers other servers to immediately hit the URL. If that URL includes an oembed link for [&#8230;]<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2023%2Fprotecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dprotecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version&amp;action_name=Protecting%20this%20WordPress%20site%20from%20Mastodon%20traffic%20floods%20with%20caching%20%28Caddy%20server%20version%29&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p>Mastodon has some <s>fun</s> <s>questionable</s> interesting architectural choices. When you post a link, instead of your server sending metadata about the link (such as title, snippet, and image) out to other servers, it just shares the link raw, which triggers other servers to immediately hit the URL. If that URL includes an <a href="https://oembed.com/" data-wpel-link="external" rel="external noopener">oembed link</a> for rendering a preview of the post, that URL also gets a hit. Due to existence of relays, your post may be fanned out to servers that don&#8217;t have any of your followers, amplifying the traffic. Hello, distributed denial of service attack!</p>



<p>Here are a couple of ways to mitigate it.</p>



<h1 class="wp-block-heading">#1: Caching plugin</h1>



<p>I use <a href="https://wordpress.org/plugins/w3-total-cache/" data-wpel-link="external" rel="external noopener">W3 Total Cache</a>. It acts as a pseudo static site generator by crawling your own WordPress instance, creating, and then serving static HTML files. Other WP caching plugins work similarly.</p>



<p>This solves half the problem, as hits against the post URL quickly start returning static data.</p>



<h1 class="wp-block-heading">#2: Caching oembed URLs</h1>



<p>The problem is that caching plugins don&#8217;t seem to account for the oembed URLs that look like <code>&lt;your-site.com>/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fyour-site.com%2Fyour-page%2F</code>. These do query (and take down) the database during the Mastodon traffic storm. For me, that means lots of HTTP 500 responses:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="703" loading="lazy" src="https://glyphy.com/wp-content/uploads/2023/05/image-1024x703.png?x58116" alt="Access log file showing HTTP 500 errors for oembed URLs on my web server." class="wp-image-1220" srcset="https://glyphy.com/wp-content/uploads/2023/05/image-1024x703.png 1024w, https://glyphy.com/wp-content/uploads/2023/05/image-300x206.png 300w, https://glyphy.com/wp-content/uploads/2023/05/image-768x527.png 768w, https://glyphy.com/wp-content/uploads/2023/05/image-1536x1054.png 1536w, https://glyphy.com/wp-content/uploads/2023/05/image-900x617.png 900w, https://glyphy.com/wp-content/uploads/2023/05/image.png 1822w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Caching to the rescue!</h2>



<p>Thankfully, these oembed URLs don&#8217;t change unless your post does, so they&#8217;re safe to cache for at least a few minutes.</p>



<p>I use <a href="https://caddyserver.com/" data-wpel-link="external" rel="external noopener">Caddy</a> as a reverse proxy to a WordPress Docker container, so I added the following block to my site&#8217;s config, after rebuilding Caddy with the <a href="https://github.com/caddyserver/cache-handler" data-wpel-link="external" rel="external noopener">cache-handler</a> plugin (<a href="https://caddyserver.com/docs/modules/http.handlers.cache#github.com/caddyserver/cache-handler" data-wpel-link="external" rel="external noopener">docs</a>):</p>



<pre class="wp-block-code"><code>glyphy.com {
  # ... other configs ...
  route /wp-json/oembed/* {
    cache {
      stale 10m
      ttl 5m
      badger {
        path /tmp/badger_glyphy.com
      }
    }
  }
}</code></pre>



<p>This stores a copy of the oembed responses on disk and returns it for five minutes without actually hitting the WordPress installation itself, which is both quicker and avoids taking it down. I tested this out. The access log records a few hits against the oembed URL followed by complete silence as Caddy returns a cache response to the rest of the requests. It&#8217;s kept this tiny site up during the test.</p>



<p>You can check if it&#8217;s working by looking at response headers in your browser&#8217;s developer tools. There should be something like:</p>



<pre class="wp-block-code"><code>cache-status: Souin; hit; ttl=291; key=GET-https-yoursite.com-/wp-json/oembed/1.0/embed?url=https://yoursite.com/your-post-url/</code></pre>



<p>I haven&#8217;t messed around with any of the more advanced settings of the cache system yet, but there&#8217;re some useful options there, such as one that reduces the maximum size of the cache file on disk.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>If you&#8217;re not using Caddy, you may have another caching mechanism available. The idea is just to configure the cache for the <code>/wp-json/oembed/*</code> URLs for a few minutes, which is generally how long the Mastodon traffic flood lasts. If you&#8217;re using a CDN it may already do this for you.</p>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2023%2Fprotecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dprotecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version&amp;action_name=Protecting%20this%20WordPress%20site%20from%20Mastodon%20traffic%20floods%20with%20caching%20%28Caddy%20server%20version%29&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2023/protecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version/?pk_campaign=feed&#038;pk_kwd=protecting-this-wordpress-site-from-mastodon-traffic-floods-with-caching-caddy-server-version/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Host your own Mastodon instance for free on Oracle Cloud</title>
		<link>https://glyphy.com/a/2022/host-your-own-mastodon-instance-for-free-on-oracle-cloud/?pk_campaign=feed&#038;pk_kwd=host-your-own-mastodon-instance-for-free-on-oracle-cloud</link>
					<comments>https://glyphy.com/a/2022/host-your-own-mastodon-instance-for-free-on-oracle-cloud/?pk_campaign=feed&#038;pk_kwd=host-your-own-mastodon-instance-for-free-on-oracle-cloud#comments</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Mon, 28 Nov 2022 00:16:23 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[fediverse]]></category>
		<category><![CDATA[indieweb]]></category>
		<category><![CDATA[mastodon]]></category>
		<category><![CDATA[oci]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=1040</guid>

					<description><![CDATA[TL;DR Follow the instructions on https://github.com/faevourite/mastodon-oracle-cloud-free-tier. When you join Mastodon you lend trust to whoever runs your server, including trust that they moderate the content, keep the service well-maintained, and back up its data. This is easy if you know the people running it! If no one in your friends/family group is running a server [&#8230;]<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fhost-your-own-mastodon-instance-for-free-on-oracle-cloud%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dhost-your-own-mastodon-instance-for-free-on-oracle-cloud&amp;action_name=Host%20your%20own%20Mastodon%20instance%20for%20free%20on%20Oracle%20Cloud&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-pullquote"><blockquote><p><strong>TL;DR</strong> Follow the instructions on <a href="https://github.com/faevourite/mastodon-oracle-cloud-free-tier" data-wpel-link="external" rel="external noopener">https://github.com/faevourite/mastodon-oracle-cloud-free-tier</a>.</p></blockquote></figure>



<p>When you <a href="https://joinmastodon.org/" data-wpel-link="external" rel="external noopener">join Mastodon</a> you lend trust to whoever runs your server, including trust that they moderate the content, keep the service well-maintained, and back up its data. This is easy if you know the people running it! If no one in your friends/family group is running a server already, and you don&#8217;t mind the administrative responsibility that services like <a href="https://masto.host/" data-wpel-link="external" rel="external noopener">Masto.host</a> save you, you can host your own without breaking the bank by leveraging <a href="https://www.oracle.com/cloud/free/" data-wpel-link="external" rel="external noopener">Oracle Cloud&#8217;s generous free tier</a>.</p>



<p>I did this recently to set up <a href="https://social.glyphy.com/@dv" data-wpel-link="internal">my personal instance</a>! In addition to the reasons above, I wanted to own my data, have a @glyphy.com handle, and be able to make small tweaks to Mastodon itself (like adding <a href="https://docs.joinmastodon.org/user/posting/#emoji" data-wpel-link="external" rel="external noopener">custom server emoji</a>). The last two reasons are admittedly all in service of my vanity. Mastodon can <a href="https://docs.joinmastodon.org/user/profile/#verification" data-wpel-link="external" rel="external noopener">verify my identity via links</a> in my profile, and I have writer&#8217;s block when it comes to the custom emoji (typist&#8217;s block?). But personalization is the soul of <a href="https://indieweb.org/" data-wpel-link="external" rel="external noopener">IndieWeb</a> and I&#8217;m here for it.</p>



<p>While I could just create a free account on <a href="https://www.oracle.com/cloud/free/" data-wpel-link="external" rel="external noopener">Oracle Cloud</a>, spin up a 4-core 24GB ARM-based compute instance (free tier limit) using the console admin UI, and follow the <a href="https://docs.joinmastodon.org/admin/install/" data-wpel-link="external" rel="external noopener">official Mastodon installation instructions</a>, I wanted something more maintainable and automated. If (when?) I mess up my instance beyond repair I&#8217;d like to be able to recover quickly.</p>



<p>Here&#8217;s what I ended up using to accomplish this:</p>



<ul class="wp-block-list">
<li><a href="https://www.docker.com/" data-wpel-link="external" rel="external noopener">Docker</a> for running everything
<ul class="wp-block-list">
<li>Core components: Mastodon apps, Postgres DB, and Redis cache</li>



<li><a href="https://caddyserver.com/" data-wpel-link="external" rel="external noopener">Caddy</a>, to serve everything over TLS, with a certificate provisioned using its Cloudflare integration</li>



<li>Backups via <a href="https://kopia.io/" data-wpel-link="external" rel="external noopener">Kopia</a></li>



<li><a href="https://healthchecks.io/" data-wpel-link="external" rel="external noopener">Healthchecks.io</a> and <a href="https://newrelic.com/" data-wpel-link="external" rel="external noopener">Newrelic</a> for monitoring (free tiers)</li>
</ul>
</li>



<li><a href="https://www.ansible.com/" data-wpel-link="external" rel="external noopener">Ansible</a> to install and configure all of the above</li>



<li><a href="https://developer.hashicorp.com/terraform" data-wpel-link="external" rel="external noopener">Terraform</a> to provision the underlying cloud infrastructure</li>



<li><a href="https://www.cloudflare.com/" data-wpel-link="external" rel="external noopener">Cloudflare</a> (free tier again) to manage DNS and provide some bot protection</li>



<li><a href="https://sendgrid.com/" data-wpel-link="external" rel="external noopener">Sendgrid</a> (you guessed it, free tier) for Mastodon emails, such as password recovery</li>



<li><a href="https://pushover.net/" data-wpel-link="external" rel="external noopener">Pushover</a> for cron job failure notifications
<ul class="wp-block-list">
<li>This is the only thing that&#8217;s paid here (optional, though). $5 lifetime per device. It&#8217;s more than paid for itself over the years.</li>
</ul>
</li>
</ul>



<p><strong>I put all the scripts and manifests together into </strong><a href="https://github.com/faevourite/mastodon-oracle-cloud-free-tier" data-wpel-link="external" rel="external noopener">this GitHub repository</a><strong>, along with instructions on how to get it all running.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>Below are just some notes about the different choices, mostly so I myself remember them when they invariably turn out to be wrong.</p>



<h2 class="wp-block-heading">Docker</h2>



<p>I don&#8217;t want to deal with random OS portability issues or package conflicts. Docker also has a nice side effect of requiring me to think about where things will be stored. An inventory mandate.</p>



<p>One thing I&#8217;m worried about is the difficulty of future updates that require some manual steps in a specific order. Docker-compose&#8217;s &#8220;bring everything up together&#8221; behaviour spells danger here. I don&#8217;t mind a little bit of downtime on this personal instance, but it may be a bigger deal otherwise.</p>



<h2 class="wp-block-heading">Caddy</h2>



<p>Serves as few purposes:</p>



<ol class="wp-block-list">
<li>Multiplexes the &#8220;web&#8221; and &#8220;streaming&#8221; containers over the same domain</li>



<li>Enables compression</li>



<li>Sets aggressive caching headers</li>



<li>With its Cloudflare DNS module it can provision a TLS cert, which means that Cloudflare-&gt;Mastodon traffic is also over HTTPS, and I can just block port 80 entirely</li>
</ol>



<h2 class="wp-block-heading">Kopia</h2>



<p>As far as backup software goes, it&#8217;s relatively young, but I like it. I recently switched to it for some personal backups. It&#8217;s like a fancier restic. I point it at Google Drive via <a href="https://rclone.org/" data-wpel-link="external" rel="external noopener">rclone</a> (baked into Kopia&#8217;s Docker image). If/when I run of storage there I may move to <a href="https://www.backblaze.com/b2/cloud-storage.html" data-wpel-link="external" rel="external noopener">Backblaze B2</a>.</p>



<h2 class="wp-block-heading">Healthchecks</h2>



<p>This is a recent discovery. It&#8217;s like a <a href="https://en.wikipedia.org/wiki/Dead_man%27s_switch" data-wpel-link="external" rel="external noopener">dead-man&#8217;s switch</a> for cron jobs, and has its own copious free tier. I have it integrated with Pushover and email, but it supports many other notification systems.</p>



<h2 class="wp-block-heading">Ansible/Terraform</h2>



<p>I first tried using Ansible to provision the infrastructure, but (slowly) realized why Terraform is preferred for this sort of work. The latter keeps track of state. With Ansible, I think I would&#8217;ve had to save the IDs of every piece of infra somewhere, and then read it back on startup, to avoid having it try to re-create what already exists.</p>



<h2 class="wp-block-heading">Cloudflare</h2>



<p>I turned on its proxying for my Mastodon domain. I was worried at first since <a href="https://glyphy.com/a/2022/cloudflare-blocking-indieweb-ring/" type="post" id="745" data-wpel-link="internal">they seem to mess with traffic initiated by systems</a>, but so far I haven&#8217;t noticed any problems.</p>



<p>I don&#8217;t feel great about using a service that supports right-wing extremists. I&#8217;d like to move away to one that&#8217;s less harmful. For now, I just promise myself not to give them any of my money. I don&#8217;t mind turning off their bot protection, and Caddy is able to automatically refresh a TLS cert, so they&#8217;re really only managing DNS for me right now.</p>



<h2 class="wp-block-heading">Sendgrid</h2>



<p>Their customer service is atrocious and the product is stale. But I already had an account, so that&#8217;s what I went with. I turn off all email notifications from Mastodon itself except for password resets.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>Overall, I&#8217;m very happy with how this all turned out. Maybe one day OCI will clamp down on its free tier or my account will get more popular (unlikely) and I&#8217;ll be forced to pay up for more infra, which is ok with me. I also like the idea of being the only one to blame when something goes wrong with my own instance.</p>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fhost-your-own-mastodon-instance-for-free-on-oracle-cloud%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dhost-your-own-mastodon-instance-for-free-on-oracle-cloud&amp;action_name=Host%20your%20own%20Mastodon%20instance%20for%20free%20on%20Oracle%20Cloud&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2022/host-your-own-mastodon-instance-for-free-on-oracle-cloud/?pk_campaign=feed&#038;pk_kwd=host-your-own-mastodon-instance-for-free-on-oracle-cloud/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>Structural diff with difftastic and JetBrains IDEs</title>
		<link>https://glyphy.com/a/2022/structural-diff-with-difftastic-and-jetbrains-ides/?pk_campaign=feed&#038;pk_kwd=structural-diff-with-difftastic-and-jetbrains-ides</link>
					<comments>https://glyphy.com/a/2022/structural-diff-with-difftastic-and-jetbrains-ides/?pk_campaign=feed&#038;pk_kwd=structural-diff-with-difftastic-and-jetbrains-ides#respond</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Sun, 11 Sep 2022 00:41:23 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[tech]]></category>
		<category><![CDATA[tools]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=889</guid>

					<description><![CDATA[I learned about difftastic today, which aims to show differences between files while being aware of the underlying programming language used in said files (if any). It&#8217;s basically magic when it works! I generally like the built-in diff in the JetBrains suite of IDEs. The one I use these days is GoLand, but I believe [&#8230;]<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fstructural-diff-with-difftastic-and-jetbrains-ides%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dstructural-diff-with-difftastic-and-jetbrains-ides&amp;action_name=Structural%20diff%20with%20difftastic%20and%20JetBrains%20IDEs&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p>I learned about <a href="https://difftastic.wilfred.me.uk/" data-wpel-link="external" rel="external noopener">difftastic</a> today, which aims to show differences between files while being aware of the underlying programming language used in said files (if any).</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="951" loading="lazy" src="https://glyphy.com/wp-content/uploads/2022/09/image-1024x951.png?x58116" alt="Structured diff output with difftastic on a change in Mailhog" class="wp-image-890" srcset="https://glyphy.com/wp-content/uploads/2022/09/image-1024x951.png 1024w, https://glyphy.com/wp-content/uploads/2022/09/image-300x279.png 300w, https://glyphy.com/wp-content/uploads/2022/09/image-768x713.png 768w, https://glyphy.com/wp-content/uploads/2022/09/image-1536x1426.png 1536w, https://glyphy.com/wp-content/uploads/2022/09/image-2048x1902.png 2048w, https://glyphy.com/wp-content/uploads/2022/09/image-900x836.png 900w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption>Structured diff output with difftastic on a change in <a href="https://github.com/mailhog/MailHog" data-wpel-link="external" rel="external noopener">Mailhog</a></figcaption></figure>



<p>It&#8217;s basically magic when it works!</p>



<p>I generally like the built-in diff in the JetBrains suite of IDEs. The one I use these days is GoLand, but I believe they all support <a href="https://www.jetbrains.com/help/go/settings-tools-external-diff-tools.html" data-wpel-link="external" rel="external noopener">adding an external diff tool</a>. Since difftastic is a console app, here&#8217;s what I had to do on my Mac:</p>



<ol class="wp-block-list"><li><code>brew install difftastic   # install the tool</code></li><li>Install <a href="https://github.com/mklement0/ttab#installation-via-homebrew-macos-only" data-wpel-link="external" rel="external noopener">ttab using their brew instructions</a>. This allows GoLand to launch a new tab in <a href="https://iterm2.com/" data-wpel-link="external" rel="external noopener">iTerm</a> and run the <code>difft</code> command there. Otherwise, using the External Diff Tool in Goland would appear to do absolutely nothing, as the output of the tool isn&#8217;t displayed natively.</li><li>Configure the external diff tool using <a href="https://www.jetbrains.com/help/go/settings-tools-external-diff-tools.html" data-wpel-link="external" rel="external noopener">the instructions for GoLand</a>.<ol><li>Program path: <code>ttab</code></li><li>Tool name: <code>Difftastic</code> (but can be anything you like)</li><li>Argument pattern: <code>-a iTerm2 difft %1 %2 %3</code><ol><li>The &#8220;-a iTerm2&#8221; is to ensure that iTerm is used instead of the default Terminal app.</li></ol></li></ol></li></ol>



<p>Now you can click this little button in the standard GoLand diff view to open up the structural diff if needed:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="62" loading="lazy" src="https://glyphy.com/wp-content/uploads/2022/09/image-1-1024x62.png?x58116" alt="Screenshot of GoLand's diff viewer with a highlight around the external diff tool button" class="wp-image-891" srcset="https://glyphy.com/wp-content/uploads/2022/09/image-1-1024x62.png 1024w, https://glyphy.com/wp-content/uploads/2022/09/image-1-300x18.png 300w, https://glyphy.com/wp-content/uploads/2022/09/image-1-768x47.png 768w, https://glyphy.com/wp-content/uploads/2022/09/image-1-1536x94.png 1536w, https://glyphy.com/wp-content/uploads/2022/09/image-1-900x55.png 900w, https://glyphy.com/wp-content/uploads/2022/09/image-1.png 1838w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Ideally the diff would be integrated into GoLand, but I don&#8217;t mind it being an extra click away, since difftastic doesn&#8217;t work reliably in many situations (particularly large additions or refactorings).</p>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fstructural-diff-with-difftastic-and-jetbrains-ides%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dstructural-diff-with-difftastic-and-jetbrains-ides&amp;action_name=Structural%20diff%20with%20difftastic%20and%20JetBrains%20IDEs&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2022/structural-diff-with-difftastic-and-jetbrains-ides/?pk_campaign=feed&#038;pk_kwd=structural-diff-with-difftastic-and-jetbrains-ides/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Prometheus group evaluation order</title>
		<link>https://glyphy.com/a/2022/prometheus-group-evaluation-order/?pk_campaign=feed&#038;pk_kwd=prometheus-group-evaluation-order</link>
					<comments>https://glyphy.com/a/2022/prometheus-group-evaluation-order/?pk_campaign=feed&#038;pk_kwd=prometheus-group-evaluation-order#respond</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Wed, 15 Jun 2022 01:48:06 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[o11y]]></category>
		<category><![CDATA[prometheus]]></category>
		<category><![CDATA[tech]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=739</guid>

					<description><![CDATA[In Prometheus, if one recording rule uses metrics produced by another make sure they're in the same group.<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fprometheus-group-evaluation-order%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dprometheus-group-evaluation-order&amp;action_name=Prometheus%20group%20evaluation%20order&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p><a href="https://prometheus.io/" data-wpel-link="external" rel="external noopener">Prometheus</a> has this line in its docs for recording rules:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Recording and alerting rules exist in a rule group. Rules within a group are run sequentially at a regular interval, with the same evaluation time.</p><cite><a href="https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/" data-wpel-link="external" rel="external noopener">Recording Rules</a></cite></blockquote>



<p>I read that a while ago, but at the time it wasn&#8217;t clear why it mattered. It seemed that groups were mostly intended to give a collection of recording rules a name. It became clear recently when I tried to set up a recording rule in one group that was using a metric produced by a recording rule in another group.</p>



<p>The expression for the first recording rule was something like this:</p>



<pre class="wp-block-code"><code>(
  sum(rate(http:requests&#91;5m]))
  -
  sum(rate(http:low_latency&#91;5m]))
)
/
(
  sum(rate(http:requests&#91;5m]))
)</code></pre>



<p>The result:</p>



<figure class="wp-block-image size-large"><a href="https://glyphy.com/wp-content/uploads/2022/06/image.png?x58116" data-wpel-link="internal"><img decoding="async" width="1024" height="217" loading="lazy" src="https://glyphy.com/wp-content/uploads/2022/06/image-1024x217.png?x58116" alt="" class="wp-image-740" srcset="https://glyphy.com/wp-content/uploads/2022/06/image-1024x217.png 1024w, https://glyphy.com/wp-content/uploads/2022/06/image-300x63.png 300w, https://glyphy.com/wp-content/uploads/2022/06/image-768x162.png 768w, https://glyphy.com/wp-content/uploads/2022/06/image-1536x325.png 1536w, https://glyphy.com/wp-content/uploads/2022/06/image-2048x433.png 2048w, https://glyphy.com/wp-content/uploads/2022/06/image-900x190.png 900w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><figcaption>Using a recording rule from another group</figcaption></figure>



<p>It&#8217;s showing a ratio of &#8220;slow&#8221; requests as a value from 0 to 1. Compare that graph to one that&#8217;s based on the raw metric, and not the pre-calculated one:</p>



<figure class="wp-block-image size-large"><a href="https://glyphy.com/wp-content/uploads/2022/06/image-1.png?x58116" data-wpel-link="internal"><img decoding="async" width="1024" height="215" loading="lazy" src="https://glyphy.com/wp-content/uploads/2022/06/image-1-1024x215.png?x58116" alt="" class="wp-image-741" srcset="https://glyphy.com/wp-content/uploads/2022/06/image-1-1024x215.png 1024w, https://glyphy.com/wp-content/uploads/2022/06/image-1-300x63.png 300w, https://glyphy.com/wp-content/uploads/2022/06/image-1-768x161.png 768w, https://glyphy.com/wp-content/uploads/2022/06/image-1-1536x323.png 1536w, https://glyphy.com/wp-content/uploads/2022/06/image-1-2048x430.png 2048w, https://glyphy.com/wp-content/uploads/2022/06/image-1-900x189.png 900w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><figcaption>Using the raw metric</figcaption></figure>



<p>The expression is:</p>



<pre class="wp-block-code"><code>(
  sum(rate(http_requests_seconds_count{somelabel="filter"}&#91;5m]))
  -
  sum(rate(http_requests_seconds_bucket{somelabel="filter", le="1"}&#91;5m]))
)
/
(
  sum(rate(http_requests_seconds_count{somelabel="filter"}&#91;5m]))
)</code></pre>



<p>The metrics used here correspond to the pre-calculated ones above. That is, <code>http:requests</code> is <code>http_requests_seconds_count{somelabel="filter"}</code>, and <code>http:low_latency</code> is <code>http_requests_seconds_bucket{somelabel="filter", le="1"}</code>. The graphs are similar, but the one using raw metrics doesn&#8217;t have the strange sharp spikes and drops.</p>



<p>I&#8217;m not sure what&#8217;s going on here exactly, but based on the explanation from the docs it&#8217;s probably a race between the evaluation of the two groups resulting in inconsistent number of samples used for <code>http:requests</code> and <code>http:low_latency</code>. Maybe one has one less sample than the other at the time they&#8217;re evaluated for the first group&#8217;s expression, which I think could show up as spikes.</p>



<p>Whatever the cause the solution is simple: if one recording rule uses metrics produced by another make sure they&#8217;re in the same group.</p>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fprometheus-group-evaluation-order%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dprometheus-group-evaluation-order&amp;action_name=Prometheus%20group%20evaluation%20order&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2022/prometheus-group-evaluation-order/?pk_campaign=feed&#038;pk_kwd=prometheus-group-evaluation-order/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cloudflare blocking Indieweb ring</title>
		<link>https://glyphy.com/a/2022/cloudflare-blocking-indieweb-ring/?pk_campaign=feed&#038;pk_kwd=cloudflare-blocking-indieweb-ring</link>
					<comments>https://glyphy.com/a/2022/cloudflare-blocking-indieweb-ring/?pk_campaign=feed&#038;pk_kwd=cloudflare-blocking-indieweb-ring#comments</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Sat, 04 Jun 2022 12:27:42 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[indieweb]]></category>
		<category><![CDATA[tech]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=745</guid>

					<description><![CDATA[[Edit 2023-05-21] This seems to have been fixed by Cloudflare silently at some point. I was trying to add this site to Indieweb ring last night and found that it couldn&#8217;t validate the presence of the previous/next links, even though they were clearly in the footer of every page. I cleared the WordPress and Cloudflare [&#8230;]<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fcloudflare-blocking-indieweb-ring%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dcloudflare-blocking-indieweb-ring&amp;action_name=Cloudflare%20blocking%20Indieweb%20ring&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p>[Edit 2023-05-21] This seems to have been fixed by Cloudflare silently at some point.</p>



<p>I was trying to add this site to <a href="https://xn--sr8hvo.ws/directory" data-wpel-link="external" rel="external noopener">Indieweb ring</a> last night and found that it couldn&#8217;t validate the presence of the previous/next links, even though they were clearly in the footer of every page. I cleared the WordPress and Cloudflare caches without success.</p>



<p>Since Indieweb ring runs on Glitch, which is a large public service, I suspected that maybe Cloudflare was blocking their traffic. Sure enough, checking my http access logs I couldn&#8217;t find any requests from Glitch, and switching nameservers to my web host resulted in a successful check:</p>


<div class="wp-block-image is-style-default">
<figure class="aligncenter size-large is-resized"><img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/uploads/2022/06/image-2-1024x429.png?x58116" alt="Indieweb ring's status checker log showing two failed checks and one successful one" class="wp-image-746" width="512" height="215" srcset="https://glyphy.com/wp-content/uploads/2022/06/image-2-1024x429.png 1024w, https://glyphy.com/wp-content/uploads/2022/06/image-2-300x126.png 300w, https://glyphy.com/wp-content/uploads/2022/06/image-2-768x322.png 768w, https://glyphy.com/wp-content/uploads/2022/06/image-2-900x377.png 900w, https://glyphy.com/wp-content/uploads/2022/06/image-2.png 1366w" sizes="(max-width: 512px) 100vw, 512px" /></figure>
</div>


<p>This was happening even with the &#8220;Bot Fight&#8221; option set to off, &#8220;Security Level&#8221; set to &#8220;Essentially Off&#8221; and a disabled &#8220;Browser Integrity Check&#8221; option.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>[side note] I love that my autogenerated site identifier for Indieweb ring is a person worried about taking pictures and writing. Accurate.</p>



<p>[Edit 2023-05-21] The webring doesn&#8217;t use emoji anymore 😢</p>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fcloudflare-blocking-indieweb-ring%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dcloudflare-blocking-indieweb-ring&amp;action_name=Cloudflare%20blocking%20Indieweb%20ring&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2022/cloudflare-blocking-indieweb-ring/?pk_campaign=feed&#038;pk_kwd=cloudflare-blocking-indieweb-ring/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Homebrew shell weirdness on an M1 Mac</title>
		<link>https://glyphy.com/a/2022/homebrew-shell-weirdness-on-an-m1-mac/?pk_campaign=feed&#038;pk_kwd=homebrew-shell-weirdness-on-an-m1-mac</link>
					<comments>https://glyphy.com/a/2022/homebrew-shell-weirdness-on-an-m1-mac/?pk_campaign=feed&#038;pk_kwd=homebrew-shell-weirdness-on-an-m1-mac#respond</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Sun, 27 Mar 2022 23:09:15 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[arm64]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[homebrew]]></category>
		<category><![CDATA[macs]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=682</guid>

					<description><![CDATA[I got an M1 mac at work recently and hit a strange issue trying to run the Goland debugger: Error running '&#60;my test>': Debugging programs compiled with go version go1.17.8 darwin/amd64 is not supported. Use go sdk for darwin/arm64. Wait, amd64? I had installed the arm64 go package after the switch to M1, so why [&#8230;]<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fhomebrew-shell-weirdness-on-an-m1-mac%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dhomebrew-shell-weirdness-on-an-m1-mac&amp;action_name=Homebrew%20shell%20weirdness%20on%20an%20M1%20Mac&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p>I got an M1 mac at work recently and hit a strange issue trying to run the Goland debugger:</p>



<pre class="wp-block-preformatted">Error running '&lt;my test>':
Debugging programs compiled with go version go1.17.8 darwin/amd64 is not supported. Use go sdk for darwin/arm64.</pre>



<p>Wait, <code>amd64</code>? I had installed the arm64 go package after the switch to M1, so why did it think I was running on amd64?</p>



<p>Turns out I had used homebrew on my old Intel Mac to install and use a newer version of my shell (bash). Because amd64 homebrew installs amd64 binaries the shell itself was running under amd64 and any app it ran would think the CPU architecture was also amd64. Running <code>arch</code> in the shell confirmed my suspicion.</p>



<p>The solution was to switch to the Mac&#8217;s built-in bash shell, reinstall homebrew, reinstall bash, and set iTerm to use <code>/opt/homebrew/bin/bash</code> instead of the default <code>/bin/bash</code> . I followed <a href="https://blog.smittytone.net/2021/02/07/how-to-migrate-to-native-homebrew-on-an-m1-mac/" data-wpel-link="external" rel="external noopener">these instructions</a> for switching to arm64 homebrew and kept the old Intel homebrew around aliased to <code>oldbrew</code> as suggested to still have access to amd64-only apps.</p>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2022%2Fhomebrew-shell-weirdness-on-an-m1-mac%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dhomebrew-shell-weirdness-on-an-m1-mac&amp;action_name=Homebrew%20shell%20weirdness%20on%20an%20M1%20Mac&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2022/homebrew-shell-weirdness-on-an-m1-mac/?pk_campaign=feed&#038;pk_kwd=homebrew-shell-weirdness-on-an-m1-mac/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Code as a Decision Log</title>
		<link>https://glyphy.com/a/2019/code-as-a-decision-log/?pk_campaign=feed&#038;pk_kwd=code-as-a-decision-log</link>
					<comments>https://glyphy.com/a/2019/code-as-a-decision-log/?pk_campaign=feed&#038;pk_kwd=code-as-a-decision-log#respond</comments>
		
		<dc:creator><![CDATA[Dmitri Vassilenko]]></dc:creator>
		<pubDate>Mon, 16 Sep 2019 14:25:30 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[org]]></category>
		<guid isPermaLink="false">https://glyphy.com/?p=175</guid>

					<description><![CDATA[In re-reading The Wrong Abstraction recently, I&#8217;ve realized that while we often talk about code being an artifact of production, it often functions more as a decision log. In her post Sandi writes about how the Sunk Cost Fallacy plays into the hesitation we as developers feel when encountering perplexing abstractions. Part of the recommendation [&#8230;]<img src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2019%2Fcode-as-a-decision-log%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dcode-as-a-decision-log&amp;action_name=Code%20as%20a%20Decision%20Log&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></description>
										<content:encoded><![CDATA[
<p>In re-reading <a href="https://web.archive.org/web/20210509124931/https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction" data-wpel-link="external" rel="external noopener">The Wrong Abstraction</a> recently, I&#8217;ve realized that while we often talk about code being an <em>artifact of production</em>, it often functions more as a <em>decision log</em>.</p>



<p>In her post Sandi writes about how the <a href="https://en.wikipedia.org/w/index.php?title=Sunk_Cost_Fallacy" data-wpel-link="external" rel="external noopener">Sunk Cost Fallacy</a> plays into the hesitation we as developers feel when encountering perplexing abstractions. Part of the recommendation is to consider that</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8220;It may have been right to begin with, but that day has passed.&#8221;</p>
</blockquote>



<p>I think this is often how we think about organizational decisions. We make new ones all the time, and often they alter or completely reverse those that came before, even if people who made the initial choices aren&#8217;t available for consultation anymore.</p>



<p>I suspect it&#8217;s easier for us to undo organizational decisions than code decisions because the former are made in a more visible and social environment. It&#8217;s easy to skip fixing the wrong abstraction when that choice may only be seen by one reviewer as part of an unrelated change. Perhaps this is where pair or mob programming can help. More eyes on the wrong abstraction at the right time could be all that&#8217;s needed to address it.</p>



<p>Thinking about code as a decision log could also help. Removing the abstraction is just another event. Events are bound to a specific moment in time, and maybe today is the right moment for an event that reverses some of those previous decisions.</p>
<img decoding="async" loading="lazy" src="https://glyphy.com/wp-content/plugins/matomo/app/matomo.php?idsite=1&amp;rec=1&amp;url=https%3A%2F%2Fglyphy.com%2Fa%2F2019%2Fcode-as-a-decision-log%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dcode-as-a-decision-log&amp;action_name=Code%20as%20a%20Decision%20Log&amp;urlref=https%3A%2F%2Fglyphy.com%2Ffeed%2F" style="border:0;width:0;height:0" width="0" height="0" alt="" />]]></content:encoded>
					
					<wfw:commentRss>https://glyphy.com/a/2019/code-as-a-decision-log/?pk_campaign=feed&#038;pk_kwd=code-as-a-decision-log/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: glyphy.com @ 2026-03-13 18:19:47 by W3 Total Cache
-->