<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Hacker News Personal Blogs 2024 | Top 100 Blogs</title><link>https://hn-blogs.kronis.dev/feed-top100.xml</link><description>A collection of blog posts from users of Hacker News, based on RSS feeds.</description><language>en-US</language><lastBuildDate>Sat, 13 Jun 2026 04:03:20 GMT</lastBuildDate><generator>rfeed v1.1.1</generator><docs>https://github.com/svpino/rfeed/blob/master/README.md</docs><item><title>2024 Year in Review</title><link>https://benovermyer.com/blog/2024/12/2024-year-in-review/</link><description>&lt;p&gt;2024 was a year of travel, challenges, endings, and beginnings. I went on five trips, four of which were for recreation and the fifth of which was almost a move to a different state. My fitness and health suffered, and Silver Gryphon Games shut down. However, I began taking music lessons, won an award at work, and regularly ran an RPG campaign for once.&lt;/p&gt;
&lt;h2 id="winning-an-award-at-apiture"&gt;Winning an Award at Apiture&lt;/h2&gt;
&lt;p&gt;At my day job, I won an award for my work on Apiture’s hosted sites service. I continued to be the sole infrastructure person for hosted sites, despite their quantity at the end of the year. My automation for them made it much easier for the designers to spin up new sites without requiring work from the platform engineering team.&lt;/p&gt;
&lt;h2 id="collapse-of-exercise-habit"&gt;Collapse of Exercise Habit&lt;/h2&gt;
&lt;p&gt;My exercise habit fell apart. I started the year strong, going to the gym regularly and focusing on a bodybuilding routine that was slowly starting to show progress. Then, I went to my annual physical, and the doctor was less than enthusiastic about my chosen exercise path. For some reason, I let that discourage me from exercising at all the rest of the year.&lt;/p&gt;
&lt;p&gt;Towards the end of the year, though, I started going for longer walks occasionally. I have it as a daily to-do item, but I'm not consistent in doing it daily yet.&lt;/p&gt;
&lt;h2 id="alcohol-free-months"&gt;Alcohol-Free Months&lt;/h2&gt;
&lt;p&gt;At the beginning of the year I set out to have three full alcohol-free months in 2024. I achieved two full months and two half months. That is sort of a success, I guess, but I think I can do better. Next year will have a more ambitious goal. My interest in going alcohol-free has increased.&lt;/p&gt;
&lt;p&gt;I've attempted to completely quit drinking a couple times before. This is not that, not yet. I can see this evolving into that though. Past attempts were not based on a holistic lifestyle change. Now, going without alcohol is an accessory to other changes I have made or am planning to make.&lt;/p&gt;
&lt;h2 id="the-death-of-silver-gryphon-games"&gt;The Death of Silver Gryphon Games&lt;/h2&gt;
&lt;p&gt;While we tried to resurrect the company, neither Kevin nor I had the time or motivation to work on it. I formally shuttered the business in December. The second editions of both Aether and Ingenium will never be published, sadly. That’s too bad, because the cover art for both was awesome, and they fixed a lot of the problems with the first editions. However, a business needs sustained work in order to function, and that won’t happen for SGG.&lt;/p&gt;
&lt;h2 id="journaling-about-trips"&gt;Journaling About Trips&lt;/h2&gt;
&lt;p&gt;In 2024 I went on three major trips and I promised to journal about all of them. I wrote about the Cancun trip and the cruise, but I did not write about the eclipse trip. I thought I did, but I can’t find a journal entry for it. Two out of three, but not enough to count this goal achieved.&lt;/p&gt;
&lt;p&gt;The journal entries that exist are nice and detailed, though. They'll be a good reminder in days to come.&lt;/p&gt;
&lt;h2 id="journaling-habit"&gt;Journaling Habit&lt;/h2&gt;
&lt;p&gt;While I did keep a regular work journal each week, I didn’t write a regular personal journal. Part of the problem is that I never set up the habit like I did with the work journal; there was no specific trigger. This is something I can fix in 2025.&lt;/p&gt;
&lt;p&gt;I did start keeping a gaming journal just a few days ago. If I keep up with this, then I will have four different journals that I update: a personal journal, a work journal, a gaming journal, and a spiritual journal. Perhaps if I write regularly for all of them, that will start to bleed through into blogging, and I'll update this site more frequently.&lt;/p&gt;
&lt;h2 id="books-read"&gt;Books Read&lt;/h2&gt;
&lt;p&gt;I didn’t read nearly as many books in 2024 as I did in 2023. Also, the variety was less, with 8 different authors in 2024 and 11 in 2023. I’m not sure why, but I imagine losing almost a full month of the year to trips where I didn’t read at all didn’t help.&lt;/p&gt;
&lt;p&gt;My queue of books to read is growing. I have quite a few on my shelves. I may hold off on buying any more books until I've worked through that backlog a bit.&lt;/p&gt;
&lt;h2 id="rpg-campaign"&gt;RPG Campaign&lt;/h2&gt;
&lt;p&gt;In November I started running a Dungeons &amp;amp; Dragons campaign in-person for a few random people. It is now the longest-running regular campaign I’ve ever run. It’s also the most difficult to run, as several of the players remember throwaway comments I make about setting flavor and accept that as canon. This means I have to take notes on everything I decide. On the other hand, though, these notes are making it easier to plan sessions and review what happened in past sessions.&lt;/p&gt;
&lt;p&gt;The next session will be on January 6, after a several-week break for the holidays.&lt;/p&gt;
&lt;h2 id="beginning-of-music-lessons"&gt;Beginning of Music Lessons&lt;/h2&gt;
&lt;p&gt;In December I began taking guitar lessons. This is the first time since I was a teenager that I’ve taken music lessons. I’ve been good about practicing almost every day, too; the only days I haven’t practiced were Christmas Eve and Christmas Day.&lt;/p&gt;
&lt;p&gt;I've been practicing the usual beginner songs from a Hal Leonard book. As I get better, I want to try a few different styles, including Spanish guitar. I have a list of songs to attempt once I get good enough.&lt;/p&gt;
&lt;h2 id="previous-years"&gt;Previous Years&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2023/12/2023-year-in-review/"&gt;2023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2022/12/2022-year-in-review/"&gt;2022&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2021/12/2021-year-in-review/"&gt;2021&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2020/12/2020-year-in-review/"&gt;2020&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2018/12/2018-year-in-review/"&gt;2018&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2017/01/looking-back-on-2016-and-forward-to-2017/"&gt;2016&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2016/01/2015-year-in-review/"&gt;2015&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2013/12/2013-year-in-review/"&gt;2013&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Ben Overmyer's Site</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2024/12/2024-year-in-review/</guid></item><item><title>How I run a coffee club</title><link>http://notes.eatonphil.com/2024-12-31-how-i-run-a-coffee-club.html</link><description>&lt;p&gt;I started the &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;NYC Systems Coffee
Club&lt;/a&gt; in December
of 2023. It's gone pretty well! I regularly get around 20 people each
month. You bring a drink if you feel like it and you hang out with
people for an hour or two.&lt;/p&gt;
&lt;p&gt;There is no agenda, there is no speaker, there is no structure. The
only "structure" is that when the circle of people talking to each
other seems gets too big, I break the circle up into two smaller circles so
we can get more conversations going.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/coffeeclub.png" src="/assets/coffeeclub.png" /&gt;&lt;/p&gt;
&lt;p&gt;People tend to talk in a little circle and then move around over
time. It's basically no different than a happy hour except it is over
a non-alcoholic drink and it's in the morning.&lt;/p&gt;
&lt;p&gt;All I have to do as the organizer is periodically tell people about
the &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;Google Form&lt;/a&gt;
to fill out. I got people to sign up to the list by posting about this
on Twitter and LinkedIn. And then once a month I send an email bcc-ing
everyone on the list and ask them to respond for an invite.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/coffeeclub-invite.png" src="/assets/coffeeclub-invite.png" /&gt;&lt;/p&gt;
&lt;p&gt;The first 20 people to respond get a calendar invite.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/coffeeclub-invite.png" src="/assets/coffee-club-invite.png" /&gt;&lt;/p&gt;
&lt;p&gt;I mention all of this because people ask how they can start a coffee
club in their city. They ask how it works. But it's very simple! One
of the least-effortful ways to bring together people in your city.&lt;/p&gt;
&lt;p&gt;If your city does not have indoor public spaces, you could use a
food court, or a cafe, or a park during months where it is warm.&lt;/p&gt;
&lt;p&gt;For example, the &lt;a href="https://blinsay.com/chc3/"&gt;Cobble Hill Computer Coffee
Club&lt;/a&gt; is one that meets outdoors at a park.&lt;/p&gt;
&lt;p&gt;Good luck! :)&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;How I run a coffee club, a short guide for others who might be interested in running one. It's very simple!&lt;a href="https://t.co/UgRWDQOA3v"&gt;https://t.co/UgRWDQOA3v&lt;/a&gt; &lt;a href="https://t.co/5wYrLW7u6D"&gt;pic.twitter.com/5wYrLW7u6D&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1874213922271879650?ref_src=twsrc%5Etfw"&gt;December 31, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-12-31-how-i-run-a-coffee-club.html</guid></item><item><title>The CP/M emulator runs on Windows, maybe!</title><link>https://blog.steve.fi/the_cp_m_emulator_runs_on_windows__maybe_.html</link><description>&lt;p&gt;Today I made a new release of my &lt;a href="https://github.com/skx/cpmulator" rel="nofollow"&gt;CP/M emulator&lt;/a&gt; and I think that maybe now it will run on Microsoft Windows.  Unfortunately I cannot test it!&lt;/p&gt;

&lt;p&gt;A working CP/M implementation needs to provide facilities for reading input from the console, both reading a complete line of text and individual keystrokes.  These input functions need to handle several different types of input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blocking, waiting for input to become available.&lt;/li&gt;
&lt;li&gt;Non-blocking, returning any pending input if it is available otherwise nothing.&lt;/li&gt;
&lt;li&gt;With echo, so the user can see what they typed.&lt;/li&gt;
&lt;li&gt;Without echo, so the keys are returned by not displayed ot the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the past we used a Unix-specific approach to handle the enabling and disabling of keyboard echoing (specifically we executed the &lt;code&gt;stty&lt;/code&gt; binary to enable/disable echos), but this release adds a more portable solution, based around &lt;a href="https://github.com/nsf/termbox-go" rel="nofollow"&gt;termbox-go&lt;/a&gt; which is the new default, and should allow our emulator to work on Microsoft Windows systems.&lt;/p&gt;

&lt;p&gt;We always had the ability to select between a number of different &lt;em&gt;output&lt;/em&gt; drivers, and as of this release we can now select between multiple &lt;em&gt;input&lt;/em&gt; drivers too - with the new portable option being the default.  This has been tested on MacOS X systems, as well as GNU/Linux, but sadly I don't have access to Windows to test that.&lt;/p&gt;

&lt;p&gt;Fingers crossed it's all good now though, happy new year!&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Tue, 31 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/the_cp_m_emulator_runs_on_windows__maybe_.html</guid></item><item><title>Culture talk: What I learned from building a distributed database that never took off</title><link>https://bytepawn.com/techtalk-scalien.html</link><description>&lt;p&gt;Recently, I delivered a culture talk on Scalien, my old database startup that never took off. I discussed the business, product and some technical challenges and lessons learned from developing ScalienDB, a Paxos-based distributed NoSQL database.&lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/scalien-com.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Tue, 31 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/techtalk-scalien.html</guid></item><item><title>Adding Pinch-To-Zoom to the Chart</title><link>https://littlegreenviper.com/adding-pinch-to-zoom-to-the-chart/</link><description>In this exercise, we&amp;#8217;ll add a &amp;#8220;pinch-to-zoom-in/out&amp;#8221; feature to the chart. It will be very basic, but should work quite well. The Basics The zoom will be X-axis only. It doesn&amp;#8217;t actually make sense to zoom in on the Y-axis, especially since we have the ability to select a bar, and get the exact values ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/adding-pinch-to-zoom-to-the-chart/#more-7111" rel="noopener noreferrer" title="Adding Pinch-To-Zoom to the Chart"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Mon, 30 Dec 2024 21:56:12 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/adding-pinch-to-zoom-to-the-chart/</guid></item><item><title>Modern Portfolio Theory III: The Efficient Frontier in log volatility—return space</title><link>https://bytepawn.com/modern-portfolio-theory-efficient-frontier.html</link><description>&lt;p&gt;In the previous two articles, we explored the coverage of random portfolios in log volatility—return space, both with and without a risk-free asset. We now take the next step, and calculate the Efficient Frontier and Capital Market Line of Markowitz’s theory.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/efficient-frontier-2.png" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 30 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/modern-portfolio-theory-efficient-frontier.html</guid></item><item><title>Optimising my Rust solutions for Advent of Code</title><link>https://nindalf.com/posts/optimising-rust/</link><description>I completed Advent of Code 2024 in Rust. Here are all the tricks I used to optimise my code.</description><author>Krishna's blog</author><pubDate>Sun, 29 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nindalf.com/posts/optimising-rust/</guid></item><item><title>O-1s, H-1Bs, and other ways US Immigration is Broken</title><link>https://caseysoftware.com/blog/o-1s-h-1bs-and-other-ways-us-immigration-is-broken?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=o-1s-h-1bs-and-other-ways-us-immigration-is-broken</link><description>&lt;p&gt;With the big ongoing debate on immigration, it&amp;#8217;s been amazing/horrifying watching people muddle everyone together into one bucket and then&amp;#8230;&lt;/p&gt;
The post &lt;a href="https://caseysoftware.com/blog/o-1s-h-1bs-and-other-ways-us-immigration-is-broken"&gt;O-1s, H-1Bs, and other ways US Immigration is Broken&lt;/a&gt; appeared first on &lt;a href="https://caseysoftware.com"&gt;Caseysoftware&lt;/a&gt;.</description><author>Caseysoftware</author><pubDate>Sat, 28 Dec 2024 18:37:27 GMT</pubDate><guid isPermaLink="true">https://caseysoftware.com/blog/o-1s-h-1bs-and-other-ways-us-immigration-is-broken?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=o-1s-h-1bs-and-other-ways-us-immigration-is-broken</guid></item><item><title>The MMT Perspective on Crypto</title><link>https://bytepawn.com/mmt-crypto.html</link><description>&lt;p&gt;From an MMT (chartalist) perspective, cryptocurrencies lack sovereign backing and fail to establish the enforced demand necessary to function as true currency. Because they rely solely on speculative sentiment rather than legal mandates (like taxation), their values are unsustainable in the long run and risk ultimately collapsing to zero.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/constitution-taxes.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 27 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/mmt-crypto.html</guid></item><item><title>The complete text of "All Watched Over by Machines of Loving Grace"</title><link>http://blog.jgc.org/2024/12/the-complete-text-of-all-watched-over.html</link><description>&lt;p&gt;Richard Brautigan's poem "&lt;a href="https://en.wikipedia.org/wiki/All_Watched_Over_by_Machines_of_Loving_Grace"&gt;All Watched Over by Machines of Loving Grace&lt;/a&gt;" is somewhat well known in tech. circles but I couldn't find a complete PDF of the &lt;a href="https://en.wikipedia.org/wiki/All_Watched_Over_by_Machines_of_Loving_Grace_(poetry_collection)"&gt;original 1967 publication of it (and other poems)&lt;/a&gt; online.&amp;nbsp;&lt;/p&gt;&lt;p&gt;The copyright notice in Brautigan's collection reads:&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;&amp;nbsp;&amp;nbsp;© Copyright 1967 by Richard Brautigan&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;Permission is granted to reprint&lt;br /&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;any of these poems in magazines,&lt;br /&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;books and newspapers if they are&lt;br /&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;given away free.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;I am interpreting "print" as including the availability of a free PDF and making a scan of the complete book available here.&amp;nbsp;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://jgc.org/awobmolg-1967.pdf" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGhScUr9y4lWyAZs6B-j7QtJjfqFmt-VrOpfII9BIJYzoGDGCtdUYR0pffEwSIWwDUrIBaj57cz5Uluj8CG1hzA8Oi6Vyvm9Bc3wcDkAxucLJQJGrQHOOYLfwIE0zc-JsnuKfAkwrxQPe1N1RdLV7uOhfQD84r6oCgMuq39ZxVXOTT3sNgxGv9CA/w532-h640/Screenshot%202024-12-26%20at%2015.24.00.png" width="532" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Thu, 26 Dec 2024 17:34:02 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2024/12/the-complete-text-of-all-watched-over.html</guid></item><item><title>A Reply to Don Geddis</title><link>https://blog.rongarret.info/2024/12/a-reply-to-don-geddis.html</link><description>Don Geddis left a comment on my last post.&amp;nbsp; My reply grew far longer than would reasonably fit into a comment reply so I decided to post it as an article.&amp;nbsp; Don wrote:I wonder if you've considered that perhaps you have more in common with the people who frustrate you, than your current self-image suggests.My reply:I've not just considered it, I will happily concede that I am not as</description><author>Rondam Ramblings</author><pubDate>Thu, 26 Dec 2024 10:12:48 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2024/12/a-reply-to-don-geddis.html</guid></item><item><title>RSS Feeds and Real Time Crawling</title><link>https://www.marginalia.nu/log/a_113_rtc/</link><description>&lt;p&gt;A while back an update went live that, with some caveats, changes the time it takes for an update on a website to reflect in the search engine index from up to 2 months to 1-2 days. Conditions being if the website has an RSS or Atom feed.&lt;/p&gt;
&lt;p&gt;The big crawl job takes about two months, and is run partition by partition, meaning there&amp;rsquo;s typically a slice of the index that is two months stale at any given point in time. To help compensate for this, a new crawler and index partition has been added that focuses on recently updated content.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 26 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_113_rtc/</guid></item><item><title>Picking up volleyball in NYC with Goodrec and New York Urban</title><link>http://notes.eatonphil.com/2024-12-26-volleyball-in-nyc.html</link><description>&lt;p&gt;I was so intimidated to go at first, but it is in fact easy and fun to
start playing beginner volleyball in New York. The people are so
friendly and welcoming that it has been easy to keep playing
consistently every week since I started for the first time this
August. It's been a great workout and a great way to make friends!&lt;/p&gt;
&lt;p&gt;The two platforms I've used to find volleyball games are
&lt;a href="https://www.goodrec.com/"&gt;Goodrec&lt;/a&gt; and &lt;a href="https://www.nyurban.com/"&gt;New York
Urban&lt;/a&gt;. While these platforms may also offer
classes and leagues, I mostly use them to play "pickup" games. Pickup
games are where you show up and join (or get assigned to) a team to
play for an hour or two. Easy to go on your own or with friends.&lt;/p&gt;
&lt;p&gt;I'm not an expert! My only hope with this post is that maybe it makes
trying out volleyball in New York feel a little less intimidating for
you!&lt;/p&gt;
&lt;h3 id="goodrec"&gt;Goodrec&lt;/h3&gt;&lt;p&gt;With Goodrec you have to use their mobile app. Beginner tier is called
"social" on Goodrec. So browse available games until you find one at
the level you want to play. You enroll in (buy a place in) sessions
individually.&lt;/p&gt;
&lt;p&gt;Sessions are between 90-120 minutes long.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/goodrec-social.png" src="/assets/goodrec-social.png" /&gt;&lt;/p&gt;
&lt;p&gt;They ask you not to arrive more than 10 minutes early at the gym. When
you arrive you tell the gym managers (usually in a desk up front
somewhere) you're there for Goodrec and the tier (in case the gym has
multiple level games going on at the same time). Then you wait until
the Goodrec "host" arrives and they will organize everyone into
teams.&lt;/p&gt;
&lt;p&gt;Goodrec hosts are players who volunteer to organize the games. They'll
explain the rules of the game (makes Goodrec very good for beginners)
and otherwise help you out.&lt;/p&gt;
&lt;p&gt;Always say thank you to your host!&lt;/p&gt;
&lt;h3 id="new-york-urban"&gt;New York Urban&lt;/h3&gt;&lt;p&gt;With New York Urban, pickup sessions are called &lt;a href="https://www.nyurban.com/open-play-volleyball"&gt;"open
play"&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is no mobile app, you just use the website to purchase a spot in
a session. The sessions are longer and cheaper than Goodrec. But there
is no host; players self-organize.&lt;/p&gt;
&lt;p&gt;The options are more limited too. You play at one of four high schools
on either a Friday night or on Sunday. And session slots tend to sell
out much more quickly than with Goodrec.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/nyurban-beginner.png" src="/assets/nyurban-beginner.png" /&gt;&lt;/p&gt;
&lt;h3 id="big-city-volleyball"&gt;Big City Volleyball&lt;/h3&gt;&lt;p&gt;You can also check out &lt;a href="https://bigcityvolleyball.com/"&gt;Big City
Volleyball&lt;/a&gt; but I haven't used it yet.&lt;/p&gt;
&lt;h3 id="volo"&gt;Volo&lt;/h3&gt;&lt;p&gt;I haven't ever done Volo but I think I've heard it described as "beer
league". That even some of the beginner tier sessions with Goodrec and
New York Urban are more competitive.&lt;/p&gt;
&lt;p&gt;But also, Volo is built around leagues so you have to get the timing
right. Goodrec's and New York Urban's pickup games make it easy to get
started playing any time of year.&lt;/p&gt;
&lt;h3 id="making-friends"&gt;Making friends&lt;/h3&gt;&lt;p&gt;It was super awkward to go at first! I went by myself. I didn't know
what I was doing. I couldn't remember, and didn't know, many rules. I
didn't have court shoes or knee pads.&lt;/p&gt;
&lt;p&gt;But the Goodrec host system is particularly great for bringing
beginners in and making them feel welcome. You have a great time even
if you're terrible.&lt;/p&gt;
&lt;p&gt;The first game I went to, I tried to hang out afterward to meet people.
But people either came with their SO or with their friends or by
themselves so they all just left immediately or hung out in their
group.&lt;/p&gt;
&lt;p&gt;So you can't just go once and expect to make friends immediately. But
if you keep going at the same place and time regularly week over week,
you'll see familiar faces. Maybe half the people I play with each
week are regulars. If you're friendly you'll start making friends with
these people and eventually start going out to bars with them after
the games.&lt;/p&gt;
&lt;h3 id="improving"&gt;Improving&lt;/h3&gt;&lt;p&gt;Even if you find yourself embarrassingly bad at first, just keep
going! I'm 29, 6'1, 190lbs and from observation the past 5 months,
age, height, and weight have a very indirect relation to playing
ability.&lt;/p&gt;
&lt;p&gt;Most of the people who play are self-taught, especially at the lower
tiers I've played at. But some people played for the school team in
high school or college. These people are fun to play with and you can
learn a lot from them.&lt;/p&gt;
&lt;p&gt;Most people who are self-taught seem to watch YouTube videos like
&lt;a href="https://www.youtube.com/channel/UCoEMagRUvrXELuJZwS4DevA"&gt;Coach
Donny&lt;/a&gt;,
helpful for learning how to serve, set, block, etc. Or they take
"clinics" (classes) with Goodrec or other platforms. (I have no idea
about these, I've never done them before.)&lt;/p&gt;
&lt;p&gt;At first I played 2 hours a week and I was completely exhausted after
the session. Over time it got easier so I started playing 2-3 sessions
a week (6-9-ish hours). With practice and consistency (after about 3-4
months), I started playing Intermediate tier with Goodrec and New York
Urban. And I don't think I'll play Beginner/Social at all anymore.&lt;/p&gt;
&lt;p&gt;I still primarily play for fun and for the workout and to meet
people. But it's also fun to get better!&lt;/p&gt;
&lt;p&gt;I played with one person much better than myself in an Intermediate
session one time and he mentioned he will probably stop playing
Intermediate and only play High Intermediate. He mentioned you get
better when you keep pushing yourself to play with better and better
players. Good advice!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a little post on picking up volleyball in new york.&lt;br /&gt;&lt;br /&gt;It's fun, and a great workout, and you meet interesting people!&lt;a href="https://t.co/jEWHbRWF6C"&gt;https://t.co/jEWHbRWF6C&lt;/a&gt; &lt;a href="https://t.co/ipuIUB1ZnM"&gt;pic.twitter.com/ipuIUB1ZnM&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1872394142212661250?ref_src=twsrc%5Etfw"&gt;December 26, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 26 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-12-26-volleyball-in-nyc.html</guid></item><item><title>I ❤️ triathlons</title><link>https://bytepawn.com/i-love-triathlons.html</link><description>&lt;p&gt;In 2008, I completed my first half-Ironman — then, later that same year, I took on the full Ironman distance at the traditional ExtremeMan in Hungary, finishing in 13 hours and 38 minutes. By 2013, I had knocked my time down to 11 hours and 36 minutes. Since then, I’ve racked up a total of 56 triathlon finishes. 2024 was one of the best years so far, with a record of 10 triathlon races.&lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/cervelo-p5.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 26 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/i-love-triathlons.html</guid></item><item><title>A Cryptographically Secret Santa</title><link>https://specbranch.com/posts/cryptographic-santa/</link><description>&lt;p&gt;Twas about 4-6 weeks before Christmas, and all through the math department,
not a creature was stirring, not even a plucky young undergrad. Cryptography
professors Alice and Bob sat at the elliptically-curved conference table to plan
the department's secret Santa. Mallory, the department secretary, had been given
the task of organizing last year, and somehow managed to get three gifts while
leaving several people disappointed. This year's math department thus resolved
to do their secret Santa without a trusted party.&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Wed, 25 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/cryptographic-santa/</guid></item><item><title>Pareto Hobbies 2025</title><link>https://jodavaho.io/posts/pareto-hobbies-2025.html</link><description>&lt;h1 id="pareto-hobbies-2025"&gt;Pareto Hobbies 2025&lt;/h1&gt;
&lt;p&gt;I decided to plot possible hobbies in 2025 in terms of career potential and personal enjoyment.&lt;/p&gt;
&lt;p&gt;This is a silly thing to do, but it is also a uniquely software-engineering
problem to have - for some reason we seem to be prone to lots of extra
curricular learning, quantified self, and a constant need to optimize ourselves
for our employability.&lt;/p&gt;
&lt;p&gt;As a computer scientist and sometimes hardware hacker, I&amp;rsquo;m most interested in the following things:&lt;/p&gt;
&lt;h2 id="geospatial-ml"&gt;Geospatial ML&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m fascinated by taking spatial data and running predictors on it. This could be predicitng fine-grained weather patterns, predicting traffic, etc.&lt;/p&gt;
&lt;p&gt;At &lt;a href="https://flightscience.ai"&gt;flight science&lt;/a&gt;, this has lots of potential applications, so the usefulness is fairly high. I&amp;rsquo;ve also found that &lt;a href="https://josh.vanderhook.info/publications.html#j2014precag"&gt;kriging is very useful for robotics&lt;/a&gt; though it has been 10 years since I&amp;rsquo;ve applied it.&lt;/p&gt;
&lt;h2 id="quantum-computing"&gt;Quantum Computing&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://blog.google/technology/research/google-willow-quantum-chip/"&gt;recent google announcement&lt;/a&gt; has reinvigorated my old interest in quantum computing. A friend from JPL who I highly respect has said QC is at the equivalent of the vaccuum tube era of computing.&lt;/p&gt;
&lt;p&gt;Given we&amp;rsquo;re only now demonstrating benchmark problems, and that most the
researchers in those areas are probably hardware and quantum PhDs &amp;hellip;. well,
I&amp;rsquo;m a bigshot PhD Computer Scientist, but doubt I could contribute to the field without years of retraining.&lt;/p&gt;
&lt;p&gt;Relevance: Very low.&lt;/p&gt;
&lt;h2 id="radar-processing"&gt;Radar Processing&lt;/h2&gt;
&lt;p&gt;Super-specific interest, but I want to use and SDR to build a homebrew radar and track boats at the in-laws lake cabin. Why? Who knows.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m super comfortable with tracking and estimation, but would need to learn how to extract a &amp;ldquo;measurement&amp;rdquo; from the timing and Doppler shift of the radar return.&lt;/p&gt;
&lt;p&gt;Given I could see myself working in tracking and estimation in the future, this could be a useful skill.&lt;/p&gt;
&lt;h2 id="aws-code-as-infra"&gt;AWS code as infra&lt;/h2&gt;
&lt;p&gt;Not very glamorous, but I hate the AWS console and would like to be able to do everything in code.&lt;/p&gt;
&lt;h2 id="rl-for-planning"&gt;RL for planning&lt;/h2&gt;
&lt;p&gt;This is something that is near and dear to my heart. I have been working in planning systems for a while, and have used &amp;ldquo;RL&amp;rdquo; (POMDP, etc) in the past, but only as a tangential part of the team.&lt;/p&gt;
&lt;p&gt;More generally, the use of end-to-end learned systems for &lt;em&gt;control&lt;/em&gt; at least seems to be an increasingly dominant way of doing business.
With RL+Planning, my goal is to eventually be able to tradeoff classical
estimation and planning with end-to-end systems, and use the best parts of
either for a better-than-either pipeline. At the jobs I&amp;rsquo;ve had I&amp;rsquo;ve seen a
weird false dichotomy that produces decent results once committed, but there&amp;rsquo;s
always this tension that &amp;ldquo;the other way&amp;rdquo; is better, producing weird politics.&lt;/p&gt;
&lt;p&gt;Most recently, a classical planning pipeline was completely broken by an
ML-based estimation of orientation of obstacles. On inspection they were using
quite possibly the worst estimator you could use, and ignoring all kinds of
good data, because (I think) they were counting on perfect &amp;ldquo;measurements&amp;rdquo; from
the ML-based vision system. That kind of thing must happen all the time - get
stuck in a local minimum of &amp;ldquo;tune just a little better&amp;rdquo; when a good filter on
top of a noisy estimator could solve all your problems. There&amp;rsquo;s probably a
million such examples in planning systems too.&lt;/p&gt;
&lt;p&gt;For discrete, well structured problems with noisy data on them, such as we
might encounter in &lt;a href="https://flightscience.ai"&gt;flight science&lt;/a&gt;, I think there&amp;rsquo;s
a lot of potential for RL to be extremely useful.&lt;/p&gt;
&lt;h2 id="deep-learning--large-language-models"&gt;Deep Learning / Large Language Models&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m not super interested in this, but it seems like a good thing to know.&lt;/p&gt;
&lt;h1 id="ratings"&gt;Ratings&lt;/h1&gt;
&lt;p&gt;My interest and career potential vary. I&amp;rsquo;ve rated them on a scale of 1-100, with 100 being the highest.&lt;/p&gt;
&lt;p&gt;This is a stupid way to determine hobbies, but I&amp;rsquo;m doing it anyway.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Item&lt;/th&gt;
					&lt;th&gt;Career relevance&lt;/th&gt;
					&lt;th&gt;Personal Interest&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;RL for planning&lt;/td&gt;
					&lt;td&gt;50&lt;/td&gt;
					&lt;td&gt;40&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;DL/LLM in general&lt;/td&gt;
					&lt;td&gt;20&lt;/td&gt;
					&lt;td&gt;35&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Quantum Computing&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;90&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;AWS Code as infra&lt;/td&gt;
					&lt;td&gt;60&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Radar / signal processing / tracking&lt;/td&gt;
					&lt;td&gt;20&lt;/td&gt;
					&lt;td&gt;80&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Geospatial ML, kriging, weather data&lt;/td&gt;
					&lt;td&gt;80&lt;/td&gt;
					&lt;td&gt;40&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id="results"&gt;Results&lt;/h1&gt;
&lt;p&gt;Here&amp;rsquo;s the plotted results:&lt;/p&gt;
&lt;p&gt;&lt;img alt="pareto hobbies 2025" src="https://jodavaho.io/img/interests_plotted.png" /&gt;&lt;/p&gt;
&lt;p&gt;How do we make a decision on this data? As a planning guy, I should be able to do this!&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll do a &lt;a href="https://en.wikipedia.org/wiki/Pareto_principle"&gt;pareto analysis&lt;/a&gt;, using the reward function of &lt;code&gt;||(career_relevance , personal_interest)||&lt;/code&gt; (i.e., magnitude of the vector).&lt;/p&gt;
&lt;p&gt;So, let&amp;rsquo;s plot the pareto frontier for each:&lt;/p&gt;
&lt;p&gt;&lt;img alt="pareto hobbies 2025" src="https://jodavaho.io/img/interests_dominated.png" /&gt;&lt;/p&gt;
&lt;p&gt;In this case, the outermost circle is the &amp;ldquo;best&amp;rdquo;, and a solution &amp;ldquo;dominates&amp;rdquo; anything inside its circle.
So, according to this, I should focus on Quantum Computing and Geospatial ML. If I had applied scalars to the career relevance and personal interest, those circles would have been ellipses, and the dominance ordering would change. Maybe later.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Tue, 24 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/pareto-hobbies-2025.html</guid></item><item><title>100 Page Python Intro book announcement</title><link>https://learnbyexample.github.io/100-page-python-intro-book-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;100 Page Python Intro&lt;/strong&gt; ebook. This book is a short, introductory guide for the Python programming language. This book is well suited:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As a reference material for Python beginner workshops&lt;/li&gt;
&lt;li&gt;If you have prior experience with another programming language&lt;/li&gt;
&lt;li&gt;If you want a complement resource after reading a Python basics book, watching a video course, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 02-Jan-2025. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/100pagepythonintro/c/YearEndSale"&gt;https://leanpub.com/100pagepythonintro/c/YearEndSale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/100pagepythonintro"&gt;https://learnbyexample.gumroad.com/l/100pagepythonintro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two of my bundles are on sale as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All 13 Books Bundle&lt;/strong&gt; — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/YearEndSale"&gt;Leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books/YearEndSale"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Awesome Regex Bundle&lt;/strong&gt; — $10 (normal price $20), Python, Ruby, JavaScript, BRE/ERE, PCRE and Vim regular expressions
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/regex/c/YearEndSale"&gt;Leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/regex/YearEndSale"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python version updated to &lt;strong&gt;3.13.0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Added more exercises and you can now practice some of them using this &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises"&gt;interactive TUI app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;li&gt;Images centered for EPUB format&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;Check out my &lt;a href="https://learnbyexample.github.io/tips/"&gt;programming tips&lt;/a&gt; covering Python, command line tools and Vim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos"&gt;Vim tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;It's very thorough, written with care, and presented in a way that makes sense. Even as an intermediate Python programmer, I found use in this book.&lt;/p&gt;
&lt;p&gt;— feedback by &lt;a href="https://healeycodes.com/"&gt;Andrew Healey&lt;/a&gt; on an early draft of &amp;quot;100 Page Python Intro&amp;quot; mentioned in &lt;a href="https://news.ycombinator.com/item?id=26082464"&gt;this Hacker News thread&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="interactive-tui-app"&gt;Interactive TUI app&lt;a class="zola-anchor" href="#interactive-tui-app"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I also wrote an &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises"&gt;interactive TUI app&lt;/a&gt; based on some of the exercises from the ebook. Reference solutions are also provided.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot from the interactive TUI app for Python exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_exercises.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Numeric data types&lt;/li&gt;
&lt;li&gt;Strings and user input&lt;/li&gt;
&lt;li&gt;Defining functions&lt;/li&gt;
&lt;li&gt;Control structures&lt;/li&gt;
&lt;li&gt;Importing and creating modules&lt;/li&gt;
&lt;li&gt;Installing modules and Virtual environments&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;li&gt;Debugging&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Tuple and Sequence operations&lt;/li&gt;
&lt;li&gt;List&lt;/li&gt;
&lt;li&gt;Mutability&lt;/li&gt;
&lt;li&gt;Dict&lt;/li&gt;
&lt;li&gt;Set&lt;/li&gt;
&lt;li&gt;Text processing&lt;/li&gt;
&lt;li&gt;Comprehensions and Generator expressions&lt;/li&gt;
&lt;li&gt;Dealing with files&lt;/li&gt;
&lt;li&gt;Executing external commands&lt;/li&gt;
&lt;li&gt;Command line arguments&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/100_page_python_intro/introduction.html"&gt;https://learnbyexample.github.io/100_page_python_intro/introduction.html&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;https://github.com/learnbyexample/100_page_python_intro&lt;/a&gt; for programs, example files, markdown source and other details about the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/100_page_python_intro/issues"&gt;https://github.com/learnbyexample/100_page_python_intro/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 24 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/100-page-python-intro-book-announcement/</guid></item><item><title>Modern Portfolio Theory II: Random portfolio coverage in log volatility—return space with a risk free asset</title><link>https://bytepawn.com/modern-portfolio-theory-random-portfolio-coverage-risk-free-asset.html</link><description>&lt;p&gt;In a previous article I simulated random portfolios using Monte Carlo methods for the 2023 daily closing prices of the 100 stocks constituting the Nasdaq-100. Here I add the risk-free asset to the portfolio and examine how it affects the coverage in log volatility—return space.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/logvolatility-rf-3.png" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 20 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/modern-portfolio-theory-random-portfolio-coverage-risk-free-asset.html</guid></item><item><title/><link>https://mikewarot.blogspot.com/2024/12/i-cant-get-over-feeling-that-im-wasting.html</link><description>&lt;p&gt;I can't get over the feeling that I'm wasting whatever portion of my life I've got left.&lt;/p&gt;&lt;p&gt;In this video - https://www.youtube.com/watch?v=4jgTCayWlwc&lt;/p&gt;&lt;p&gt;"A rant on personal engineering projects" by BPS.shorts&lt;/p&gt;&lt;p&gt;The author makes the point that when you're working commercially on part of a project, the organization always has a way to keep the project going, even if you fail. If the cost goes over, they can usually change the scope or budget to fit it in.&amp;nbsp; If the parts don't integrate well, they can buy a system already integrated, or even the company that has already done it.&lt;/p&gt;&lt;p&gt;However, on a personal project, if any part fails, the whole thing fails. The main hazard is simply not finishing the project, rather than doing any one stage optimally. I think there's a shit-ton of value in this analysis.&lt;/p&gt;&lt;p&gt;Good Quote from Pale Rose in the comments @ https://www.youtube.com/watch?v=4jgTCayWlwc&amp;amp;lc=Ugxjv3J38ajw82_bmtF4AaABAg&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; *Personal projects have a unique requirement that you must optimize for: 'finishing the damn project'. Everything else is secondary*&lt;/p&gt;&lt;span&gt;&lt;a name="more"&gt;&lt;/a&gt;&lt;/span&gt;&lt;p&gt;I want to be a high-?? individual, someone who can get shit done.&amp;nbsp; High agency?&lt;/p&gt;&lt;p&gt;Yep... that's the term, from https://x.com/tferriss/status/1719035575305408551&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;During my podcast interview with @EricRWeinstein, Eric said “high-agency person” in passing, and I asked him to elaborate:&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“When you’re told that something is impossible, is that the end of the conversation, or does that start a second dialogue in your mind, how to get around whoever it is that’s just told you that you can’t do something? So, how am I going to get past this bouncer who told me that I can’t come into this nightclub? How am I going to start a business when my credit is terrible and I have no experience?”"&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Mike's plan for becoming high-agency&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;List the projects I have, maintain said list&lt;/li&gt;&lt;li&gt;Always be working to finish the things on the list, or pruning the list&lt;/li&gt;&lt;li&gt;Always be gathering resources, or using them, never just hording them&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description><author>--Mike--</author><pubDate>Thu, 19 Dec 2024 12:35:44 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2024/12/i-cant-get-over-feeling-that-im-wasting.html</guid></item><item><title>It's my Birthday, here... have a BitGrid</title><link>https://mikewarot.blogspot.com/2024/11/its-my-birthday-here-have-bitgrid.html</link><description>&lt;p&gt;If you've read about von Neuman, and wondered what it was like to bootstrap computing, and if there could be another way.... here's an interesting Birthday Gift from me to you... the BitGrid, and a writeup, on an HN thread&lt;/p&gt;https://news.ycombinator.com/item?id=42115107</description><author>--Mike--</author><pubDate>Thu, 19 Dec 2024 12:31:02 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2024/11/its-my-birthday-here-have-bitgrid.html</guid></item><item><title>On The Road in Iceland!</title><link>https://theroadchoseme.com/iceland-glacier-capped-volcano</link><description>It&amp;#8217;s been a long time since I posted a upate here on The Road Chose Me &amp;#8211; I&amp;#8217;ve been busy with YouTube videos, writing books, speaking at events, writing magazine articles, building a new&amp;#46;&amp;#46;&amp;#46;</description><author>The Road Chose Me</author><pubDate>Wed, 18 Dec 2024 22:29:36 GMT</pubDate><guid isPermaLink="true">https://theroadchoseme.com/iceland-glacier-capped-volcano</guid></item><item><title>How Many Hours Can You Code?</title><link>https://thecodist.com/how-many-hours-can-you-code/</link><description>&lt;p&gt;How many hours a day can you write code, and at what point does the quality of your work go down? Even more important is how many weeks and months of that max effort you can still be effective.&lt;/p&gt;&lt;p&gt;In my life, there have only been three periods where I&lt;/p&gt;</description><author>The Codist</author><pubDate>Wed, 18 Dec 2024 22:03:18 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/how-many-hours-can-you-code/</guid></item><item><title>swyx in 2024 End of Year wraps</title><link>https://www.swyx.io/2024-eoy</link><description>i was involved in 3 end of year-ish recaps today:</description><author>swyx's site RSS Feed</author><pubDate>Tue, 17 Dec 2024 20:03:35 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/2024-eoy</guid></item><item><title>badkeming: a site for kerning so bad it's keming</title><link>http://blog.jgc.org/2024/12/badkeming-site-for-kerning-so-bad-its.html</link><description>&lt;p&gt;I made another silly Tumblr (to go along with &lt;a href="https://moviecode.tumblr.com/"&gt;Movie Code&lt;/a&gt; and &lt;a href="https://lowbackgroundsteel.ai/"&gt;Low Background Steel&lt;/a&gt;). This times it's a Tumblr for all those font disasters and other typographical amusements. Welcome to the wonderful world of &lt;a href="https://badkeming.com"&gt;badkeming.com&lt;/a&gt;:&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://badkeming.com" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="420" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOnAi31wLwP1beQLc-ylj8D2VpWVsJQkuyVcUMViFFpppM7BmbNgrB36PQWpxETwYpIP2PjkoJ-DnsDabdn5UOJoNvy-vh6oPlh2cISzOiqj-vmc-zfHKzSasC8ZamjWl7vwXU5ee2V7Jfj5wuQEBgfyOniA_1_3Z_RuTDFPv3zmTQVugR1HjOZA/w640-h420/badkeming-1.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Submissions gladly accepted.&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Sun, 15 Dec 2024 18:16:23 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2024/12/badkeming-site-for-kerning-so-bad-its.html</guid></item><item><title>Individual Career Plan</title><link>https://bytepawn.com/individual-career-plan.html</link><description>&lt;p&gt;This post provides a practical Individual Career Plan template you can use to guide self-assessment, prioritize your professional development, and set meaningful goals that align with your vision for the future. By outlining clear objectives and action steps, you create a framework for continuous improvement and more informed career decisions. I use this same template for the data organization I lead, filled it out myself, and shared it with others to inspire them to do the same.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Desert road sunset" src="/images/desert-road-sunset.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 15 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/individual-career-plan.html</guid></item><item><title>Why I read history books</title><link>https://bytepawn.com/beugras-kiugras-why-i-read-history-books.html</link><description>&lt;p&gt;Hungary’s rushed entry into World War II and its botched attempt to break away from it show how stale leadership trapped in its own fantasy bubble can steer a nation toward disaster. By “political backtesting” these moments — comparing their underlying factors to the present — today’s citizens can weigh future outcomes, draw practical lessons, we can make more informed decisions about where and how to live.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Kiugras Beugras" src="/images/kiugras-beugras.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 09 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/beugras-kiugras-why-i-read-history-books.html</guid></item><item><title>"All your base are belong to us" introduction to my 2004 MIT Spam Conference talk</title><link>http://blog.jgc.org/2024/12/all-you-base-are-belong-to-us.html</link><description>&lt;p&gt;As I've mentioned before &lt;a href="https://blog.jgc.org/2023/07/how-to-beat-adaptivebayesian-spam.html"&gt;my talk at the 2004 MIT Spam Conference&lt;/a&gt; was about using one machine learning system to beat another. In that case a spammer using machine learning to beat a machine learning spam filter.&lt;/p&gt;&lt;p&gt;The talk started with a short video playing off the &lt;a href="https://en.wikipedia.org/wiki/All_your_base_are_belong_to_us"&gt;All your base are belong to us&lt;/a&gt; meme. I very carefully edited the images to change the text to be about spam and incorporate Paul Graham (who was the instigator of the MIT Spam Conference and an &lt;a href="https://paulgraham.com/spam.html"&gt;early machine learning spam filter pioneer&lt;/a&gt;).&amp;nbsp;&lt;/p&gt;&lt;p&gt;I thought I'd &lt;a href="https://blog.jgc.org/2023/05/bringing-popfile-web-site-back-from-dead.html"&gt;lost the audio of that introduction&lt;/a&gt; and randomly came across it today. So here is the restored introduction to the talk made on a Mac in 2004 (I still haven't found the audio of the actual talk).&lt;/p&gt;

&lt;div style="padding-top: 56.25%;"&gt;
  &lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Sun, 08 Dec 2024 19:28:43 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2024/12/all-you-base-are-belong-to-us.html</guid></item><item><title>Holding Space for Wicked (Part One)</title><link>https://www.swyx.io/wicked</link><description/><author>swyx's site RSS Feed</author><pubDate>Sun, 08 Dec 2024 04:57:10 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/wicked</guid></item><item><title>Disable Slog Messages in Go Tests</title><link>https://boyter.org/posts/golang-slog-disable-tests/</link><description>&lt;p&gt;As much as I like &lt;a href="https://github.com/rs/zerolog"&gt;ZeroLog&lt;/a&gt; for Go logging Slog being added to the standard library fits in with my rule of avoiding 3rd party packages when they are not entirely required hence I have started moving anything I work on over to it.&lt;/p&gt;
&lt;p&gt;One thing I find especially annoying however is log messages inside tests. As such here is a quick code snippet which turns off all slog outputs, but only for tests. Stick it into any &lt;code&gt;_test.go&lt;/code&gt; file to disable logging outputs for that package.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sun, 08 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/golang-slog-disable-tests/</guid></item><item><title>Paper: The Unreasonable Effectiveness of Monte Carlo Simulations in A/B Testing</title><link>https://bytepawn.com/paper-unreasonable-effectiveness-monte-carlo-ab-testing.html</link><description>&lt;p&gt;I run Monte Carlo simulations of content production over random Watts-Strogatz graphs to show various effects relevant to modeling and understanding Randomized Controlled Trials on social networks: the network effect, spillover effect, experiment dampening effect, intrinsic dampening effect, clustering effect, degree distribution effect and the experiment size effect. I will also define some simple metrics to measure their strength. When running experiments these potentially unexpected effects must be understood and controlled for in some manner, such as modeling the underlying graph structure to establish a baseline. &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/paper-monte-carlo-experiments-of-network-effects-in-randomized-controlled-trials.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 05 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/paper-unreasonable-effectiveness-monte-carlo-ab-testing.html</guid></item><item><title>Bringing K/V Context Quantisation to Ollama</title><link>https://smcleod.net/2024/12/bringing-k/v-context-quantisation-to-ollama/</link><description>K/V context cache quantisation has been added to Ollama. This enables significant reductions in VRAM usage, allowing users to realise the potential of expanded context sizes and run larger models at their existing context sizes.</description><author>smcleod.net</author><pubDate>Wed, 04 Dec 2024 22:00:02 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/12/bringing-k/v-context-quantisation-to-ollama/</guid></item><item><title>20 Years of Blogging and I’m still a n00b</title><link>https://caseysoftware.com/blog/20-years-of-blogging-and-im-still-a-n00b?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=20-years-of-blogging-and-im-still-a-n00b</link><description>&lt;p&gt;As of today, I&amp;#8217;ve been blogging for 20 years. Holy crap, that&amp;#8217;s a long time. I remember when I started,&amp;#8230;&lt;/p&gt;
The post &lt;a href="https://caseysoftware.com/blog/20-years-of-blogging-and-im-still-a-n00b"&gt;20 Years of Blogging and I’m still a n00b&lt;/a&gt; appeared first on &lt;a href="https://caseysoftware.com"&gt;Caseysoftware&lt;/a&gt;.</description><author>Caseysoftware</author><pubDate>Wed, 04 Dec 2024 15:16:00 GMT</pubDate><guid isPermaLink="true">https://caseysoftware.com/blog/20-years-of-blogging-and-im-still-a-n00b?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=20-years-of-blogging-and-im-still-a-n00b</guid></item><item><title>A quieter place</title><link>https://blog.bayindirh.io/blog/a-quieter-place/</link><description>&lt;p&gt;While this blog was silent, my life was buzzing with activity. I have been trying to tend to multiple big projects at work and in my personal life, like a juggler trying not to drop anything.&lt;/p&gt;
&lt;p&gt;Not everything went as planned. The blog got sidelined, personal projects lost momentum, and a couple of them are temporarily frozen. On the other hand, I managed to finish a big migration at work. As always, Murphy learned what we were doing and motivated us to finish faster by intervening at the most convenient time with maximum effect (at least from his perspective). A big pile of important things got handled in my personal and work life, keeping me busy or at the edge, or both.&lt;/p&gt;
&lt;p&gt;Personally, I managed to get my &lt;a href="https://notes.bayindirh.io"&gt;digital garden&lt;/a&gt; to grow, write more Go, and love it in the process. I decluttered my life further in digital and physical realms. Decluttering things take a long time when you're trying to handle decades of clutter collected by a past me who didn't understand the consequences of what he's doing. On the other hand, we didn't have these organizational superpowers back in the day, and I didn't know better, to be honest.&lt;/p&gt;
&lt;p&gt;This decluttering process is not limited to my collected cruft. I'm simplifying my digital life and the places I frequent. I'm not using X (formerly Twitter) and not adding new photos to my Instagram account anymore (since I don't want to provide AI training material to Meta, a subject worthy of its separate blog post). Instead, I moved to &lt;a href="https://mastodon.sdf.org/@bayindirh"&gt;Mastodon&lt;/a&gt; and will use another service to share my photography. Recently, I subscribed to a high-quality local newspaper's digital edition.&lt;/p&gt;
&lt;p&gt;These moves are a new iteration of the trend I started by switching to Kagi in 2023 (which again needs a separate blog post), which profoundly affected my daily life. Using a user-centric search engine without ads has an immediate and measurable effect on life quality since you don't mentally filter ads and other SEO spam with every search, which is mentally draining.&lt;/p&gt;
&lt;p&gt;I felt the same after subscribing to the local newspaper. Higher editorial quality, almost no ads, and access to the paper's print version in PDF form made a profound difference in how I consumed the news and felt afterward (i.e., not mentally exhausted).&lt;/p&gt;
&lt;p&gt;The movement from popular, mainstream sites to well-selected, smaller, yet higher-quality environments made me realize how advertisements, feed algorithms, and optimizations for eyes drain our willpower, energy, and capacity to focus and do meaningful work. It's the same with useless discussions with people one doesn't know and doesn't want to agree on anything.&lt;/p&gt;
&lt;p&gt;From now on, I'll be much more selective about where I frequent, who I engage with, and how I spend my mental energy and focus. I'll build a quieter place because I want to be tired for more meaningful reasons when I go to bed.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Mon, 02 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/a-quieter-place/</guid></item><item><title>Modern Portfolio Theory I: Random portfolio coverage in log volatility—return space</title><link>https://bytepawn.com/modern-portfolio-theory-random-portfolio-coverage-in-log-volatility-return-space.html</link><description>&lt;p&gt;I examine the coverage of random portfolios in log volatility—return space using Monte Carlo methods with different randomization techniques for the 2023 daily closing prices of the 100 stocks constituting the Nasdaq-100.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Monte Carlo casino" src="/images/logvolatility-5.png" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 01 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/modern-portfolio-theory-random-portfolio-coverage-in-log-volatility-return-space.html</guid></item><item><title>Will AI Take My Job?</title><link>https://smcleod.net/2024/11/will-ai-take-my-job/</link><description>It's probably not so much AI itself you have to fear.</description><author>smcleod.net</author><pubDate>Sat, 30 Nov 2024 00:00:02 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/11/will-ai-take-my-job/</guid></item><item><title>Festive offers for books on Python, Linux, Regular Expressions, Vim and more!</title><link>https://learnbyexample.github.io/programming-deals-2024/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;Here are some awesome deals for programming books and courses during the 2024 festive season.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="my-ebooks"&gt;My ebooks&lt;a class="zola-anchor" href="#my-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Offers valid till 02-Dec-2024. You can get them on Leanpub:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/FestiveOffer"&gt;All 13 Books bundle&lt;/a&gt; — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/linux-cli-text-processing/c/FestiveOffer"&gt;Linux CLI Text Processing bundle&lt;/a&gt; — $10 (normal price $20), grep, sed, awk, perl and ruby one-liners, GNU coreutils, CLI computing&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/python-bundle/c/FestiveOffer"&gt;Learn by example Python bundle&lt;/a&gt; — $8 (normal price $15), Python introduction, Regular Expressions and Projects&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/py_regex/c/FestiveOffer"&gt;Understanding Python re(gex)?&lt;/a&gt; — FREE (normal price $10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also avail these offers on Gumroad:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;All 13 Books Bundle&lt;/a&gt; — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/FestiveOffer"&gt;Linux CLI Text Processing bundle&lt;/a&gt; — $10 (normal price $20), grep, sed, awk, perl and ruby one-liners, GNU coreutils, CLI computing&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/python-bundle/FestiveOffer"&gt;Learn by example Python bundle&lt;/a&gt; — $8 (normal price $15), Python introduction, Regular Expressions and Projects&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_regex"&gt;Understanding Python re(gex)?&lt;/a&gt; — FREE (normal price $10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p align="center"&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;&lt;img alt="All books bundle" src="/images/books/all_books_bundle.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="indie-creators"&gt;Indie creators&lt;a class="zola-anchor" href="#indie-creators"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mathspp.gumroad.com/l/all-books-bundle/BF24"&gt;7 Python books bundle&lt;/a&gt; — 50% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://driscollis.gumroad.com/"&gt;Python books&lt;/a&gt; — 35% off with &lt;code&gt;BF24&lt;/code&gt; discount code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adamchainz.gumroad.com/"&gt;Ebooks on Django and Git&lt;/a&gt; — 50% off, plus purchasing power parity if applicable
&lt;ul&gt;
&lt;li&gt;see also author's &lt;a href="https://adamj.eu/tech/2024/11/18/django-black-friday-deals-2024/"&gt;blog post&lt;/a&gt; for links to other Django-related deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thepythoncodingplace.thinkific.com/order?ct=20aec16f-ab0e-467f-bc1b-7e98e64d47f4"&gt;The Python Coding Place Membership&lt;/a&gt; — 40% off with &lt;code&gt;black2024&lt;/code&gt; discount code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonmorsels.com/courses/jumpstart/overview/"&gt;Python Jumpstart&lt;/a&gt; — 50% launch discount
&lt;ul&gt;
&lt;li&gt;see also author's &lt;a href="https://treyhunner.com/2024/11/python-black-friday-and-cyber-monday-sales-2024/"&gt;blog post&lt;/a&gt; for links to other Python deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wizardzines.com/"&gt;Wizard Zines&lt;/a&gt; — 50% off with &lt;code&gt;WIZARDPDF&lt;/code&gt; discount code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shrutibalasa.gumroad.com/l/ebooks-combo/BLACKFRIDAY24"&gt;CSS Flex and Grid + Level up with Tailwind CSS&lt;/a&gt; — 60% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/everydayrailsrspec/c/rubyconf2024"&gt;Everyday Rails Testing with RSpec&lt;/a&gt; — 53% off&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="other-deals"&gt;Other deals&lt;a class="zola-anchor" href="#other-deals"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://media.pragprog.com/newsletters/2024-11-20.html"&gt;The Pragmatic Bookshelf&lt;/a&gt; — 40% off on all ebooks and audio books&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mailchi.mp/leanpub/weekly-sale-2024-nov-bf-5388297"&gt;Leanpub Black Friday Sale&lt;/a&gt; — offers for programming books, bundles and courses&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday"&gt;Huge list of awesome deals&lt;/a&gt; — tools, productivity, books, courses, etc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0x90n/InfoSec-Black-Friday"&gt;InfoSec Hack Friday&lt;/a&gt; — InfoSec related software/tools&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blackfridaydeals.dev/"&gt;blackfridaydeals.dev&lt;/a&gt; — Hottest Black Friday Deals for Developers&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 29 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/programming-deals-2024/</guid></item><item><title>AI: The Somnium Files - nirvanA Initiative review</title><link>https://burakku.com/blog/ai-the-somnium-files-nirvana-initiative-review/</link><description>&lt;p&gt;&lt;img alt="AI: The Somnium Files - nirvanA Initiative" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Back agAIn.&lt;/p&gt;
&lt;p&gt;The Somnium Files are back except this time instead of running around as Date Kaname, you’re now given control of two different protagonists, who split the game’s plot into two halves: the past and the present. The past/present split gives the game a more linear presentation than the first game’s non-linear, wilder branching. Otherwise the gist is the same: run around like crazy and investigate a complex and bizarre murder mystery. Tone of the game is also the same: can take itself seriously but trends towards wackier, light-hearted comedy whenever it can.&lt;/p&gt;
&lt;p&gt;Narratively nirvanA Initiative is good. It’s not as good as the original Somnium Files, but it’s good and enjoyable. If you liked the first story, you'll probably like this one too. Granted, some of the elements like the mastermind is weaker and the game is definitely more abstract, but I wouldn’t say that any part of the game ruins the journey. I also give it credit for not just retreading the track that the original game took, so it doesn’t feel like you’re just doing a replay. The characters in Nirvana Initiative, both returning and new, are still very strong and definitely made me want to keep playing through. While I don’t think that either of the new protagonists is as fun and full of a package as Date was, I still liked both of them.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mizuki" src="mizuki.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Some bits of the story definitely feel like retcons, but I found the explanations given to make sense in-universe, so it didn’t really bother me. Not that any of them were so major that they’d be worth getting upset over, even if they left them completely unexplained. I also think that the additional backstory that they gave to some of the characters really rounded up some of the mysteries of the first game, so I’m a-okay with them.&lt;/p&gt;
&lt;p&gt;There are definitely flaws in the storytelling though. Some bits were seemingly brought up to set up another scene without really ever being explained later on. Uchikoshi could’ve definitely done an additional pass through the story to make sure he didn’t introduce something only to drop it in the second scene. There were also some red herrings that seemed to not matter in any way, and felt like they only existed to fuck with you without any explanation why they were there in the first place. Could be that there was some information to explain away these red herrings, but with the game being almost 30 hours long and clues being spread quite thinly throughout that duration, it’s not easy to ascertain that there wasn’t a throwaway line somewhere that explained it. And even if there were, I’d still consider it a demerit that it wasn’t brought up in a bigger way when they were showcased as an indication of some kind of a conspiracy.&lt;/p&gt;
&lt;p&gt;Then there’s the big twist. I wasn’t sure how to feel about the big twist at first, since it initially really felt like an asspull. However, given a bit of time and letting the game explain itself, it did start to mesh together to a point where I appreciated it more. There were also at least some hinting toward it, although I think there could’ve been more (unless I was just stupid and missed all of it), as the indication of the twist really just manifested as a sensation of something being really wrong. I imagine that basically no one managed to guess it correctly during the story. The big twist is also quite different from the big twist of the first game, so it doesn’t feel like just a repeat of the first game. So different that it’s probably why a lot of people seem to be turned away from the game entirely. You either like it or you hate it, whereas I think almost everyone liked the original game’s big twist.&lt;/p&gt;
&lt;p&gt;The gameplay is almost the same as with the previous instalment, with most of the time being spent in the visual novel investigatory sections with Somniums in between. The biggest core change is that they’ve replaced the evidence-showing interrogation sections with virtual reality puzzle-solving sections, where you explore the scene of the crime in a 3D space and create a reconstruction of the events. Sorta like a mini-Somnium with a pop quiz at the end. I quite liked these VR sections, but I would’ve liked to have some of the evidence sections of the previous game too. Would’ve made it feel a bit more like you were doing proper detective work.&lt;/p&gt;
&lt;p&gt;&lt;img alt="VR investigation" src="wet.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Quick-time events are still very much present, much to many people’s chagrin I imagine. They shouldn’t take anyone by surprise at this point, given the amount of Pavlovian conditioning the series has done by playing the same battle music every time you’re expected to hit random buttons in rhythm. I don’t exactly hate the QTE sections, but they’re hardly the best and brightest gameplay available. At best they're a change of pace from the point-and-clicking adventuring. At least the battle choreography for these sections is fun to watch.&lt;/p&gt;
&lt;p&gt;While not drastically different, the Somnium sections seem like a step up from the first game. Not only do the actions feel less like taking stabs in the dark, but there’s also more actual puzzle-solving. Less of “I guessed it right” and more of “I solved the riddle.” Granted, there is still some amount of brute-forcing that you can do and I managed to send one Somnium by guessing the final answer, as my guess was just the correct size to fit the answer box. Still, the amount of variance in the Somnium gameplay mechanics really does make them feel more thought out than previously.&lt;/p&gt;
&lt;p&gt;However, I’m not sure if there really was a need for so many Somnium segments, since a few of them didn’t really move the plot forwards all that much. Maybe they just wanted to break up the visual novel sections a bit more?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Somnium" src="somnium.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;On the technical side, nirvanA Initiative is not much different from the first game. I could’ve done with better anti-aliasing and improved support for using a controller and keyboard simultaneously. The Somnium transition video is also compressed to hell and back. Surely there was no need to squeeze down the game’s install size this much? Thankfully this time they managed to make the ending movie play in the correct language, so there’s been at least some progress. A passing grade.&lt;/p&gt;
&lt;p&gt;Localisation side is good but not perfect. I played through with the Japanese audio track (as one should) and some of the translated jokes really did not land as well in English as they did in the original script. I understand that some of the puns are definitely somewhere between hard and impossible to get right, but there were definitely places where there was a way to deliver the joke better. Some of it might’ve been due to trying to gear the game towards western audiences, with the expectation that the average player wouldn’t really get any references.&lt;/p&gt;
&lt;p&gt;All in all, while not quite as brilliant as the first game, nirvanA Initiative is still a fun and interesting sequel, and one that I would recommend to anyone who liked the first game. If you didn't like it, you won't like this one either. And if you haven't played the original? Go play it instead. It’s a sequel, what’d you expect?&lt;/p&gt;
&lt;p&gt;If they ever make a third game, aI’ll be sure to cobble up that one as well. A day one purchase, most likely.&lt;/p&gt;</description><author>ブラック</author><pubDate>Thu, 28 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/ai-the-somnium-files-nirvana-initiative-review/</guid></item><item><title>1 million page views</title><link>http://notes.eatonphil.com/2024-11-28-1-million-views.html</link><description>&lt;p&gt;I was delighted to notice this morning that this site has recently
passed 1M page views. And since Murat
&lt;a href="https://muratbuffalo.blogspot.com/2017/02/1-million-pageviews.html"&gt;wrote&lt;/a&gt;
about his 1M page view accomplishment at the time, I felt compelled to
now too.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/1m-page-views.png" src="/assets/1m-page-views.png" /&gt;&lt;/p&gt;
&lt;p&gt;I started regularly blogging in 2018. For some reason I decided to
write a blog post every month. And while I have definitely skipped a
month or two here or there, on average I've written 2 posts per month.&lt;/p&gt;
&lt;h3 id="tooling"&gt;Tooling&lt;/h3&gt;&lt;p&gt;Since at least 2018 this site has been built with a static site
generator. I might have used a 3rd-party generator at one point, but
for as long as I can remember most of this site has been built with a
&lt;a href="https://github.com/eatonphil/eatonphil.com/blob/main/notes/scripts/build.py"&gt;little Python
script&lt;/a&gt;
I wrote.&lt;/p&gt;
&lt;p&gt;I used to get so pissed when static site generators would pointlessly
change their APIs and I'd have to make pointless changes. I have not
had to make any significant changes to my build code in many years.&lt;/p&gt;
&lt;p&gt;I hosted the site itself on GitHub Pages for many years. But I wanted
more flexibility with subdomains (ultimately not something I liked)
and the ability to view server-side logs (ultimately not something I
ever do).&lt;/p&gt;
&lt;p&gt;I think this site is hosted on an OVH machine now. But at this point
it is inertia keeping me there. If you have no strong feelings
otherwise, GitHub Pages is perfect.&lt;/p&gt;
&lt;p&gt;I used to use Google Analytics but then they shut down the old
version. The new version was incredibly confusing to use. I could not
find some very basic information. So I moved to Fathom which has been
great.&lt;/p&gt;
&lt;p&gt;I used to track all subscribers in a Google Form and bcc them but this
became untenable eventually after 1000 subscribers due to GMail rate
limits. I currently use MailerLite for subscriptions and sending email
about new posts. But this is an absolutely terrible service. They
proxy all links behind a domain that adblockers hate and they also
visually shorten the URL so you can't copy the text of the URL.&lt;/p&gt;
&lt;p&gt;I just want a service that has a hosted form for collecting
subscribers and a &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; that lets me dump raw HTML and send
that as an email to my subscribers. No branding, no watermarks, no
link proxying. This apparently doesn't exist. I am too lazy to
figure out Amazon SES so I stick with MailerLite for now.&lt;/p&gt;
&lt;h3 id="evolution"&gt;Evolution&lt;/h3&gt;&lt;p&gt;In the beginning I talked about little interpreters in JavaScript,
about programming languages, about Scheme. I was into functional
programming. Over time I moved into little emulators and bytecode
VMs. And for the last four years I became obsessed with databases and
distributed systems.&lt;/p&gt;
&lt;p&gt;I have almost always written about little projects to teach myself a
concept. Writing a &lt;a href="https://notes.eatonphil.com/lua-in-rust.html"&gt;bytecode VM in
Rust&lt;/a&gt;, &lt;a href="https://notes.eatonphil.com/emulating-amd64-starting-with-elf.html"&gt;emulating a
subset of x86 in
Go&lt;/a&gt;,
&lt;a href="https://notes.eatonphil.com/2023-05-25-raft.html"&gt;implementing Raft in
Go&lt;/a&gt;, &lt;a href="https://notes.eatonphil.com/2024-05-16-mvcc.html"&gt;implementing
MVCC isolation levels in
Go&lt;/a&gt;, and so on.&lt;/p&gt;
&lt;p&gt;So many times when I tried to learn a concept I would find blog posts
with only partial code. The post would link to a GitHub repo that, by
the time I got to the post, had evolved significantly beyond what was
described in the post. The repo code had by then become too complex
for me to follow. So I was motivated to write minimal implementations
and walk through the code in its entirety.&lt;/p&gt;
&lt;div class="note"&gt;
  Even today there is not a single post on implementing TCP/IP from
  scratch that walks through entirely working code. (Please, someone
  write this.)
&lt;/div&gt;&lt;p&gt;I have also had a blast writing survey posts such as &lt;a href="https://notes.eatonphil.com/2023-09-21-how-do-databases-execute-expressions.html"&gt;how various
databases execute
expressions&lt;/a&gt;,
&lt;a href="https://notes.eatonphil.com/javascript-implementations.html"&gt;analyzing non-V8 JavaScript
implementations&lt;/a&gt;,
&lt;a href="https://notes.eatonphil.com/parser-generators-vs-handwritten-parsers-survey-2021.html"&gt;how various programming language implementations parse
code&lt;/a&gt;,
and &lt;a href="https://notes.eatonphil.com/whats-the-big-deal-about-key-value-databases.html"&gt;how various database systems build on top of key-value
databases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The last two posts have even each been cited in a research paper
(&lt;a href="https://arxiv.org/pdf/2208.08235"&gt;here&lt;/a&gt; and
&lt;a href="https://www.usenix.org/system/files/atc23-kaufman.pdf"&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id="editing"&gt;Editing&lt;/h3&gt;&lt;p&gt;In terms of quality, my single greatest trick is to read the post out
loud. Multiple times. Notice parts that are awkward or unclear and
rewrite them.&lt;/p&gt;
&lt;p&gt;My second greatest trick is to ask friends for review. Some posts like
&lt;a href="https://notes.eatonphil.com/2024-02-08-an-intuition-for-distributed-consensus-in-oltp-systems.html"&gt;an intuition for distributed
consensus&lt;/a&gt;
and &lt;a href="https://notes.eatonphil.com/2024-07-01-a-write-ahead-log-is-not-a-universal-part-of-durability.html"&gt;a write-ahead log is not a universal part of
durability&lt;/a&gt;
would simply not have been correct or credible without my fantastic
reviewers. And I'm proud to have &lt;a href="https://eatonphil.com/editor.html"&gt;played that
part&lt;/a&gt; a few times in turn.&lt;/p&gt;
&lt;p&gt;We also have a fantastic #writing-and-drafts channel on the &lt;a href="https://eatonphil.com/discord.html"&gt;Software
Internals Discord&lt;/a&gt; where folks
(myself occasionally included) come for post review.&lt;/p&gt;
&lt;h3 id="context"&gt;Context&lt;/h3&gt;&lt;p&gt;I've lost count of the total number of times that these posts have
been on the front page of Hacker News or that a tweet announcing a
post has reached triple digits likes. I think I've had 9 posts on the
front of HN this year. I do know that my single best year for HN was
12 months between 2022-2023 where 20 of my posts or projects were on
the front page.&lt;/p&gt;
&lt;p&gt;Every time a post does well there's a part of me that worries that
I've peaked. But the way to deal with this has been to ignore that
little voice and to just keep learning new things. I haven't stopped
finding things confusing yet, and &lt;a href="https://notes.eatonphil.com/2024-06-14-confusion-is-a-muse.html"&gt;confusion is a phenomenal
muse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And also to, like, go out and meet friends for dinner,
&lt;a href="https://nycsystems.xyz/"&gt;run&lt;/a&gt;
&lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;meetups&lt;/a&gt;, run &lt;a href="https://eatonphil.com/bookclub.html"&gt;book clubs&lt;/a&gt;,
&lt;a href="https://eatonphil.com/chat.html"&gt;chat&lt;/a&gt; with you fascinating internet
strangers, play volleyball, and so on.&lt;/p&gt;
&lt;p&gt;It's always been about &lt;a href="https://notes.eatonphil.com/2024-08-24-obsession.html"&gt;cultivating healthy
obsessions&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="benediction"&gt;Benediction&lt;/h3&gt;&lt;p&gt;In parting, I'll remind you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://notes.eatonphil.com/is-it-worth-writing-about.html"&gt;It is definitely worth writing about&lt;/a&gt;,
whatever "it" is&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/eatonphil/status/1854965419745972394"&gt;You're not writing enough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;And &lt;a href="https://eatonphil.com/call-for-posts.html"&gt;some ideas for posts I want to hear about if you write about them&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a little reflection on writing after noticing I passed 1M page views this morning.&lt;a href="https://t.co/eIlMDVHNht"&gt;https://t.co/eIlMDVHNht&lt;/a&gt; &lt;a href="https://t.co/EKSiiDUz5G"&gt;pic.twitter.com/EKSiiDUz5G&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1862174926104318407?ref_src=twsrc%5Etfw"&gt;November 28, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 28 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-11-28-1-million-views.html</guid></item><item><title>Engineering AI Agents - OpenAI DevDay Singapore talk</title><link>https://www.swyx.io/ai-eng-agents</link><description>this is the tracking doc for my talk on "Engineering AI Agents" for OpenAI DevDay Singapore. I'll add photos and notes and stuff when i'm done. note that the full slides contain a lot more info that i had to cut out of the 10 9 minute talk.</description><author>swyx's site RSS Feed</author><pubDate>Wed, 20 Nov 2024 04:06:58 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/ai-eng-agents</guid></item><item><title>Bloom Filters and SQLite</title><link>https://boyter.org/posts/bloom-filters-sqlite/</link><description>&lt;p&gt;I was talking to someone recently about how searchcode.com works with regards to its bloom filter. The question came up about why it stores the index in memory and not on disk considering how fast disks are these days.&lt;/p&gt;
&lt;p&gt;Now the answer is firstly performance, but the second is that writing all the code to store the index on disk is a lot of work and I didn&amp;rsquo;t feel like working on it. However I was curious&amp;hellip; since I have been using SQLite a lot recently, how about storing the bloom filter on disk using that? It comes with its own indexes for stepping though the filters and is likely out of the box to be better than anything I could come up with quickly.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 20 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/bloom-filters-sqlite/</guid></item><item><title>Bill 212</title><link>http://pxtl.ca/2024/11/20/bill-212/</link><description>&lt;p&gt;So in a fit of pettiness, the Government of Ontario is set to wrap up bike-lanes
in a 50-foot-ball of red tape to prevent their construction.  I asked on Bluesky
what was the best way to complain - if there was a petition I should sign or
something.  Craig Burley recommended I send a &lt;em&gt;fax&lt;/em&gt; of all things.  It's got a
good chance of being physical media in their space, and won't just be in a
slushpile of emails they ignore, or forgettable names on a petition.  So I wrote
up the letters for the Premier and the Minister of Transportation at lunch on
Tuesday and faxed them from my local UPS store.  Also emailed them for good measure.&lt;/p&gt;

&lt;p&gt;They're not my best writing but I was kind of in a rush.&lt;/p&gt;

&lt;p&gt;The bodies of the letters are attached below.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Wed, 20 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2024/11/20/bill-212/</guid></item><item><title>Mirror: An LLM-powered programming-by-example programming language</title><link>https://austinhenley.com/blog/mirrorlang.html</link><description>&lt;a href="https://austinhenley.com/blog/mirrorlang.html"&gt;https://austinhenley.com/blog/mirrorlang.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 17 Nov 2024 20:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/mirrorlang.html</guid></item><item><title>My 2024 New Mac Setup</title><link>https://www.swyx.io/new-mac-setup-2024</link><description>I set up a new Mac for work today. Here's everything I use on a Mac for fullstack web development.</description><author>swyx's site RSS Feed</author><pubDate>Sat, 16 Nov 2024 19:43:47 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/new-mac-setup-2024</guid></item><item><title>Interactive Python Exercises and Quiz</title><link>https://learnbyexample.github.io/interactive-python-exercises/</link><description>&lt;p&gt;Having an interactive program that automatically loads questions and checks the solution is wonderful to have while learning a topic. This &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises"&gt;TUI app&lt;/a&gt; has beginner to intermediate level exercises and multiple-choice questions for Python learners.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Python exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_exercises.png" /&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This app is available on PyPI as &lt;a href="https://pypi.org/project/pythonexercises/"&gt;pythonexercises&lt;/a&gt;. Example installation instructions are shown below, adjust them based on your preferences and OS.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# virtual environment
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; python3&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -m&lt;/span&gt;&lt;span&gt; venv textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cd textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source bin/activate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pip install pythonexercises
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# launch the app
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pythonexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; If you are on Windows, using the &lt;a href="https://en.wikipedia.org/wiki/Windows_Terminal"&gt;Windows Terminal&lt;/a&gt; is recommended. See &lt;a href="https://github.com/learnbyexample/TUI-apps/issues/3#issuecomment-1481488042"&gt;this issue&lt;/a&gt; for Virtual Environment commands and other details.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To run the app without having to enter the virtual environment again, add this alias to &lt;code&gt;.bashrc&lt;/code&gt; (or equivalent):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# you'll have to change the path
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;alias &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;pythonexercises&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/path/to/textual_apps/bin/pythonexercises'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternative to manually managing such virtual environments, you can use &lt;a href="https://github.com/pypa/pipx"&gt;https://github.com/pypa/pipx&lt;/a&gt; instead:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pipx install pythonexercises
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pythonexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As yet another alternative, you can install &lt;code&gt;textual&lt;/code&gt; (see &lt;a href="https://textual.textualize.io/getting_started/"&gt;Textual documentation&lt;/a&gt; for more details), clone this repository and run the &lt;code&gt;python_exercises.py&lt;/code&gt; file. You'll need to install &lt;code&gt;textual[syntax]&lt;/code&gt; to enable syntax highlighting (see &lt;a href="https://textual.textualize.io/widgets/text_area/#syntax-highlighting-dependencies"&gt;documentation&lt;/a&gt; for more details).&lt;/p&gt;
&lt;p&gt;Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines). Here's another screenshot:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Python quiz" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_quiz.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="guide"&gt;Guide&lt;a class="zola-anchor" href="#guide"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/PythonExercises/app_guide.md"&gt;app_guide.md&lt;/a&gt; for instructions.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="ebook"&gt;Ebook&lt;a class="zola-anchor" href="#ebook"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The exercise and quiz questions in this app have been adapted from my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd highly appreciate your feedback. Please file &lt;a href="https://github.com/learnbyexample/TUI-apps/issues"&gt;an issue&lt;/a&gt; if there are bugs, crashes, etc.&lt;/p&gt;
&lt;p&gt;Hope you find this TUI app useful. Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 14 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/interactive-python-exercises/</guid></item><item><title>Getting comfortable with psql</title><link>/2024/11/11/Getting-comfortable-with-psql/</link><description>&lt;p&gt;psql is a CLI editor that ships with Postgres. It&amp;rsquo;s incredibly powerful for working with Postgres, and doesn&amp;rsquo;t take too much of a learning curve to start to get comfortable so you can really feel like an expert working with your database.&lt;/p&gt;
&lt;p&gt;Just a rundown of a few things to get you started:&lt;/p&gt;
&lt;p&gt;Once connected in psql you can get an idea of all utility commands available with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sql"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;\&lt;/span&gt;&lt;span style="color: #f92672;"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A handy thing I use all the time is &lt;code&gt;\d&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;\d&lt;/code&gt; will describe the tables within database. You can also add a table/index/etc. onto it to describe that specific table such as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sql"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;\&lt;/span&gt;d accounts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are a number of options you can set in your psqlrc (config) file to customize your CLI experience. But you can also toggle those when directly working in psql.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\timing&lt;/code&gt; will give you the time it took to run your query&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\x auto&lt;/code&gt; will autoformat your text output&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\pset pager 0&lt;/code&gt; turns off your pager or 1 to turn it back on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oh and for editing a query in your editor of choice. Make sure you set your $EDITOR enviroment variable to the editor of your choice, though the only right choice is vim:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sql"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;\&lt;/span&gt;e
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Just a few things to get you started working with psql.&lt;/p&gt;
&lt;video class="video-shortcode" controls="controls" preload="auto"&gt;
&lt;source alt="Psql basics" src="https://craigkerstiens.b-cdn.net/psql_basics.mp4" type="video/mp4" /&gt;
Your browser does not support video
&lt;/video&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Mon, 11 Nov 2024 17:57:56 GMT</pubDate><guid isPermaLink="true">/2024/11/11/Getting-comfortable-with-psql/</guid></item><item><title>Exploring Postgres's arena allocator by writing an HTTP server from scratch</title><link>http://notes.eatonphil.com/2024-11-06-exploring-postgress-arena-allocator-writing-http-server-scratch.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/exploring-postgress-arena-allocator-writing-http-server-scratch"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 06 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-11-06-exploring-postgress-arena-allocator-writing-http-server-scratch.html</guid></item><item><title>Notes on binary soup</title><link>https://www.marginalia.nu/log/a_112_slop_ideas/index.md/</link><description>&lt;p&gt;I recently put together a small library called &lt;a href="https://github.com/MarginaliaSearch/SlopData"&gt;Slop&lt;/a&gt;, for intermediate on-disk data representation for the search engine, replacing a few ad-hoc formats I had in place before.&lt;/p&gt;
&lt;p&gt;This post isn&amp;rsquo;t so much an attempt to convince anyone else to use this library, as it makes trade-offs catering to a fairly niche use case, but to explore some of its design ideas, as it all came together very nicely, in the hopes that other libraries can draw ideas from it.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 05 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_112_slop_ideas/index.md/</guid></item><item><title>Effective unemployment and social media</title><link>http://notes.eatonphil.com/2024-11-05-effective-unemployment-and-social-media.html</link><description>&lt;p&gt;Being unemployed can be incredibly depressing. So much
rejection. Everything seems to be out of your control. Everything
except for one thing: what you produce.&lt;/p&gt;
&lt;p&gt;You might know that repeatedly posting on social media that you are
looking for work is ineffective. That it looks (or at least feels)
worse each time you say so. But there is at least one major caveat to
this.&lt;/p&gt;
&lt;p&gt;Every single time you create something and share it publicly is a
chance to also reiterate that you are looking for work. And people
actually appreciate and value this!&lt;/p&gt;
&lt;p&gt;Whether you write a blog post or build some project, you are seen as
working on yourself and contributing to the community. Positive
things! And it is no problem at all to learn with each new post you
write and each new project you publish that you are also looking for
work.&lt;/p&gt;
&lt;p&gt;Moreover, dynamics of the internet and social media basically require
that you be regularly producing something new. Either regularly
producing a new version of some existing project or regularly
producing new projects (or blog posts) entirely.&lt;/p&gt;
&lt;p&gt;What you did a week ago is old news on social media. What will you do
next week?&lt;/p&gt;
&lt;p&gt;This could itself feel depressing except for that it's probably
actually a fairly healthy thing for yourself anyway! It is a
motivation to keep your skills sharp as time goes on.&lt;/p&gt;
&lt;p&gt;So while you're unemployed and able to muster the motivation, write
about things that are interesting to you! Build projects that intrigue
you. Leave a little note on every post and project that you are
looking for work. And share every post and project on social media.&lt;/p&gt;
&lt;p&gt;You'll expose yourself to opportunities and referrals. And even if no
post or project "takes off" you will still be working on yourself and
contributing back knowledge to the community.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a short post on some ideas for effective unemployment and social media.&lt;a href="https://t.co/jmiJCOe2Nk"&gt;https://t.co/jmiJCOe2Nk&lt;/a&gt; &lt;a href="https://t.co/pK9AySNdHR"&gt;pic.twitter.com/pK9AySNdHR&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1853800075564109880?ref_src=twsrc%5Etfw"&gt;November 5, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 05 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-11-05-effective-unemployment-and-social-media.html</guid></item><item><title>Moving in Big Cities: Lugg vs Clutter</title><link>https://www.swyx.io/lugg-vs-clutter</link><description>&gt; 2026 update: used Lugg to move a sectional sofabed couch and ended up regretting it - it cost $222.51 one way (44min labor pickup 55min labor dropoff, which i think was fraud, no way dropoff cost more than pickup) and total base fare 38 + 14.5 fee. for the two way move price i could have simply ordered a new couch and had a nice new couch.</description><author>swyx's site RSS Feed</author><pubDate>Fri, 01 Nov 2024 11:12:31 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/lugg-vs-clutter</guid></item><item><title>Checking linearizability in Go</title><link>http://notes.eatonphil.com/2024-10-31-checking-linearizability-in-go.html</link><description>&lt;p&gt;&lt;!-- -*- mode: markdown -*- --&gt;&lt;/p&gt;
&lt;p&gt;You want to check for strict consistency
(&lt;a href="https://jepsen.io/consistency/models/linearizable"&gt;linearizability&lt;/a&gt;)
for your project but you don't want to have to &lt;a href="https://github.com/jepsen-io/"&gt;deal with the
JVM&lt;/a&gt;. &lt;a href="https://github.com/anishathalye/porcupine"&gt;Porcupine&lt;/a&gt;,
used by a number of real-world systems like etcd and TiDB, has you
covered!&lt;/p&gt;
&lt;p&gt;Importantly, neither Jepsen projects nor Porcupine can &lt;em&gt;prove&lt;/em&gt;
linearizability. They can only help you &lt;em&gt;build confidence&lt;/em&gt; that you
aren't obviously &lt;em&gt;violating&lt;/em&gt; linearizability.&lt;/p&gt;
&lt;p&gt;The Porcupine README is pretty good but doesn't give complete working
code, so I'm going to walk through checking linearizability of a
distributed register. And then we'll tweak things a bit by checking
linearizability for a distributed key-value store.&lt;/p&gt;
&lt;p&gt;But rather than implementing a distributed register and implementing a
distributed key-value store, to keep this post concise, we're just
going to imagine that they exist and we'll come up with some example
histories we might see.&lt;/p&gt;
&lt;p&gt;Code for this post can be found on
&lt;a href="https://github.com/eatonphil/linearizability-playground"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="boilerplate"&gt;Boilerplate&lt;/h3&gt;&lt;p&gt;Create a new directory and &lt;code&gt;go mod init lintest&lt;/code&gt;. Let's add the
imports we need and a helper function for generating a visualization
of a history, in &lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;log&amp;quot;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/anishathalye/porcupine&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;visualizeTempFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LinearizationInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CreateTemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;failed to create temp file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Visualize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;visualization failed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;wrote visualization to %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="a-distributed-register"&gt;A distributed register&lt;/h3&gt;&lt;p&gt;A distributed register is like a distributed key-value store but
there's only a single key.&lt;/p&gt;
&lt;p&gt;We need to tell Porcupine what the inputs and outputs for this system
are. And we'll later describe for it how an idealized version of this
system should behave as it receives each input; what output the
idealized version should produce.&lt;/p&gt;
&lt;p&gt;Each time we send a command to the distributed register it will
include an operation (to get or to set the register). And if it is a
set command it will include a value.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;quot;get&amp;quot; and &amp;quot;set&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The register is an integer register.&lt;/p&gt;
&lt;p&gt;Now we will define a model for Porcupine which, again, is the
idealized version of this system.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;registerModel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateAny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputAny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputAny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputAny&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputAny&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stateAny&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;readCorrectValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readCorrectValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unexpected operation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The step function accepts anything because it has to be able to model
any sort of system with its different inputs and outputs and current
state. So we have to handle casting from the &lt;code&gt;any&lt;/code&gt; type to what we
know are the inputs and outputs and state. And finally we actually do
the state change and return the new state as well as if the given
output matches what we know it should be.&lt;/p&gt;
&lt;h3 id="an-invalid-history"&gt;An invalid history&lt;/h3&gt;&lt;p&gt;Now we've only defined the idealized version of this system. Let's
pretend we have some real-world implementation of this. We might have
two clients and they might issue concurrent get and set requests.&lt;/p&gt;
&lt;p&gt;Every time we stimulate the system we will generate a new history that
we can validate with Porcupine against our model to see if the history
is linearizable.&lt;/p&gt;
&lt;p&gt;Let's imagine these two clients concurrently set the register to some
value. Both sets succeed. Then both clients read the register. And
they get different values. Here's what that history would look like
modeled for Porcupine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 sets the register to 100. The request starts at t0 and ends at t2.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* end state at t2 is 100 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 sets the register to 200. The request starts at t3 and ends at t4.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="cm"&gt;/* end state at t3 is 200 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 reads the register. The request starts at t5 and ends at t6.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 reads the register. The request starts at t7 and ends at t8. Reads a stale value!&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CheckOperationsVerbose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registerModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;visualizeTempFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registerModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected operations to be linearizable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we build and run this code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tidy&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;anishathalye&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;anishathalye&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;anishathalye&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.1.6&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lintest&lt;/span&gt;
&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;visualization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v27m749d0sj89h9ydfq0f0940000gn&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;463308000.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="nl"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;operations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linearizable&lt;/span&gt;

&lt;span class="n"&gt;goroutine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;phil&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lintest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mh"&gt;0x394&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Porcupine caught the stale value. Open that HTML file to see
the visualization.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/bad-register-history.png" src="/assets/bad-register-history.png" /&gt;&lt;/p&gt;
&lt;h3 id="a-valid-history"&gt;A valid history&lt;/h3&gt;&lt;p&gt;Let's say we fix the bug so now there's no stale read. The new history
would look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 sets the register to 100. The request starts at t0 and ends at t2.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* end state at t2 is 100 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 sets the register to 200. The request starts at t3 and ends at t4.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="cm"&gt;/* end state at t3 is 200 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 reads the register. The request starts at t5 and ends at t6.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 reads the register. The request starts at t7 and ends at t8.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild, rerun &lt;code&gt;lintest&lt;/code&gt; (it should exit successfully now), and open
the visualization.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/good-register-history.png" src="/assets/good-register-history.png" /&gt;&lt;/p&gt;
&lt;p&gt;Great! Now let's make things a little more complicated by modeling a
distributed key-value store rather than a distributed register.&lt;/p&gt;
&lt;h3 id="distributed-key-value"&gt;Distributed key-value&lt;/h3&gt;&lt;p&gt;The inputs of this system will be slightly more complex. They will
take a &lt;code&gt;key&lt;/code&gt; along with the &lt;code&gt;operation&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;quot;get&amp;quot; and &amp;quot;set&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And when we model the distributed key-value store with the state and
output at each step being a &lt;code&gt;map[string]int&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;kvModel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;int&lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateAny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputAny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputAny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputAny&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="n"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputAny&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stateAny&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;newState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;int&lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;newState&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;newState&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input.key&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newState&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;readCorrectValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input.key&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input.key&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readCorrectValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;Unexpected operation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now the history gets slightly more complex because we are now
working with some specific key. But we'll otherwise use the same
history as before.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 set key `a` to 100. The request starts at t0 and ends at t2.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 set key `a` to 200. The request starts at t3 and ends at t4.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 read key `a`. The request starts at t5 and ends at t6.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 read key `a`. The request starts at t7 and ends at t8.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run. Open the visualization.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/good-kv-history.png" src="/assets/good-kv-history.png" /&gt;&lt;/p&gt;
&lt;p&gt;And there we go!&lt;/p&gt;
&lt;h3 id="what's-next"&gt;What's next&lt;/h3&gt;&lt;p&gt;These are just a few simple examples that are not hooked up to a real
system. But it still seemed useful to show how you model one or two
simple different systems and check a history with Porcupine.&lt;/p&gt;
&lt;p&gt;Another aspect of Porcupine I did not cover is partitioning the state
space. The
&lt;a href="https://pkg.go.dev/github.com/anishathalye/porcupine#Model"&gt;docs&lt;/a&gt;
say:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Implementing the partition functions can greatly improve
performance. If you're implementing the partition function, the
model Init and Step functions can be per-partition. For example, if
your specification is for a key-value store and you partition by
key, then the per-partition state representation can just be a
single value rather than a map.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Perhaps that, and hooking this up to some "real" system, would be a
good next step.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a short tutorial on using Porcupine to check for linearizability (without needing to deal with the JVM).&lt;a href="https://t.co/kqeBz2jX76"&gt;https://t.co/kqeBz2jX76&lt;/a&gt; &lt;a href="https://t.co/teXvlp2zcv"&gt;pic.twitter.com/teXvlp2zcv&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1852143540131844109?ref_src=twsrc%5Etfw"&gt;November 1, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 31 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-10-31-checking-linearizability-in-go.html</guid></item><item><title>My Art And Color-After Tiling</title><link>https://thecodist.com/my-art-and-color-after-tiling/</link><description>&lt;p&gt;I make &lt;a href="https://digcon.art/?ref=thecodist.com"&gt;generative art&lt;/a&gt; with Swift and use tiling in many pieces. &lt;a href="https://en.wikipedia.org/wiki/Truchet_tiles?ref=thecodist.com"&gt;Truchet tiles&lt;/a&gt; are generally arranged randomly and contain everything appearing in the final image. What I do differently is to separate the layout of tiles from colorizing the image. I call this technique &amp;quot;Color-After Tiling.&amp;quot;&lt;/p&gt;&lt;p&gt;For&lt;/p&gt;</description><author>The Codist</author><pubDate>Tue, 29 Oct 2024 21:39:02 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/my-art-and-color-after-tiling/</guid></item><item><title>The Unreasonable Effectiveness of Monte Carlo Simulations in A/B Testing</title><link>https://bytepawn.com/unreasonable-effectiveness-monte-carlo-ab-testing.html</link><description>&lt;p&gt;The article illustrates how Monte Carlo simulations serve as a powerful method for exploring statistical concepts in A/B testing, enhancing understanding, improving experimental design and analysis.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Monte Carlo casino" src="https://www.1000lonelyplaces.com/wp-content/uploads/2017/03/man-and-woman-playing-Roulette-1024x680.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 20 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/unreasonable-effectiveness-monte-carlo-ab-testing.html</guid></item><item><title>How I Defeated An MMO Game Hack Author</title><link>https://thecodist.com/how-i-defeated-an-mmo-game-hack-author/</link><description>&lt;p&gt;In the late 2000&amp;apos;s, I worked at a niche MMO game company. We had a small team, not a lot of money, but a loyal audience. It was a game of skill without any of the usual powerups and unreality, and the players enjoyed the challenge.&lt;/p&gt;&lt;p&gt;Then, one&lt;/p&gt;</description><author>The Codist</author><pubDate>Sat, 19 Oct 2024 20:25:59 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/how-i-defeated-an-mmo-game-hack-author/</guid></item><item><title>The future of Postgres?</title><link>/2024/10/18/The-future-of-Postgres/</link><description>&lt;p&gt;I&amp;rsquo;m often asked what do I think the &lt;em&gt;future for Postgres&lt;/em&gt; holds, and my answer has been mostly the same for probably 8 years now, maybe even longer. You see for Postgres itself stability and reliability is core. So where does the new stuff come from if it&amp;rsquo;s not in the stable core&amp;hellip; &lt;strong&gt;extensions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Extensions within Postgres are unlike most other databases allowing you to modify or well extend the standard Postgres behavior. You can build other storage backends, new types, etc. Postgres itself ships with a number of extensions within the &amp;ldquo;contrib&amp;rdquo;. The list of contrib extensions hasn&amp;rsquo;t changed any time recently, but even contrib is a small sampling of what is possible. Beyond core there is a whole world of extensions, I want dig into just a smalls sampling starting with a few in core&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.crunchydata.com/blog/tentative-smarter-query-optimization-in-postgres-starts-with-pg_stat_statements"&gt;pg_stat_statements&lt;/a&gt;&lt;/strong&gt; is to me the most useful extension that exists. It records what queries were run, how long they took, and a number of other details about the queries. A key extension for managing performance of your database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.postgresql.org/docs/current/auto-explain.html"&gt;auto_explain&lt;/a&gt;&lt;/strong&gt; another one in contrib that is helpful for performance. For queries that run over a certain period of time will automatically log the explain plan–helpful for performance debugging.&lt;/p&gt;
&lt;p&gt;pg_prewarm useful to prewarming the cache ahead of a failover.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s jump out of contrib a little bit now. Of course that isn&amp;rsquo;t everything that ships with Postgres there is more, explore for yourself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/citusdata/citus"&gt;Citus&lt;/a&gt;&lt;/strong&gt; is one of the (to date) more advanced extensions ever created. Citus turns postgres into a sharded, distributed, horizontally scalable database. Citus is especially built to work well for B2B style multi-tenant apps, and now after being acquired years ago part of Microsoft.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/paradedb/paradedb"&gt;Pg_search&lt;/a&gt;&lt;/strong&gt; extends Postgres to support elastic-quality full text search directly within Postgres. I often say Postgres can do just about everything and be pretty capable. Things like time series and search it&amp;rsquo;s about 80% as good of some of the best in class options out there, but pg_search takes it further making it a full competitor to elastic, but Postgres.&lt;/p&gt;
&lt;p&gt;Those are some bigger ones, but you&amp;rsquo;ve also got a lot of small focused ones as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/citusdata/pg_cron"&gt;Pg_cron&lt;/a&gt;&lt;/strong&gt; is incredibly handy. Created originally by &lt;a href="https://twitter.com/marcoslot"&gt;@marcoslot&lt;/a&gt; while at Citus, it&amp;rsquo;s a small extension that does what it sounds like run scheduled jobs within Postgres. It is now a standard across all the major cloud providers. We leverage it heavily at &lt;a href="https://twitter.com/crunchydata"&gt;@crunchydata&lt;/a&gt;, but went a little further and built a UI on top so you don&amp;rsquo;t have to worry about crontab styling of the jobs.&lt;/p&gt;
&lt;p&gt;Just yesterday at Crunchy Data we released &lt;strong&gt;&lt;a href="https://github.com/CrunchyData/pg_parquet"&gt;pg_parquet&lt;/a&gt;&lt;/strong&gt;. There have been some questions about what about other extensions that sort of do similar but not exact but also do a lot of other things. This allows you to seamless copy to and from Parquet files with Postgres. It follows the linux philosophy of small sharp tools. Marco described the why extremely well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We wanted to create a light-weight implementation of Parquet that does not pull a multi-threaded library into every postgres process. When you get to more complex features, a lot of questions around trade-offs, user experience, and deployment model start appearing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is a whole lot of extensions out there. There are big robust ones that have been around forever (yeah I&amp;rsquo;m talking about you PostGIS), there are ones in contrib, there are ones that are literally created as jokes. Some try to do a lot others as I mentioned very narrow focus like pg_cron. Regardless of whether you believe in large complex extensions that do a lot or small ones that do one thing but do it really well, if you want to know where the future of Postgres is I can&amp;rsquo;t imagine a world where extensions do not play a heavy roll.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Fri, 18 Oct 2024 19:50:56 GMT</pubDate><guid isPermaLink="true">/2024/10/18/The-future-of-Postgres/</guid></item><item><title>Generating Diagrams with with AI / LLMs</title><link>https://smcleod.net/2024/10/generating-diagrams-with-with-ai-/-llms/</link><description>Generating diagrams with AI / LLMs</description><author>smcleod.net</author><pubDate>Tue, 08 Oct 2024 04:00:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/10/generating-diagrams-with-with-ai-/-llms/</guid></item><item><title>Intuitions for Distributed Consensus by Phil Eaton</title><link>http://notes.eatonphil.com/2024-10-08-intuitions-for-distributed-consensus.html</link><description>&lt;p&gt;This is an external talk of mine. Click
&lt;a href="https://www.youtube.com/watch?v=-mikZ93wpUQ"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 08 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-10-08-intuitions-for-distributed-consensus.html</guid></item><item><title>I Finally Bought My Dream Airplane</title><link>https://miscdotgeek.com/i-finally-bought-my-dream-airplane/</link><description>&lt;p&gt;Aviation has been a love of mine since I was a very little person. Living in Nevada, seeing posters and ads for the Reno Air Races, specifically the Texans, transfixed me. I was hooked. As a teenager, I got an RC glider and dabbled in RC gliders and planes until my early 20&amp;#8217;s. Later in &amp;#8230; &lt;/p&gt;
&lt;p&gt;&lt;a class="more-link btn" href="https://miscdotgeek.com/i-finally-bought-my-dream-airplane/" rel="noopener noreferrer" target="_self"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://miscdotgeek.com/i-finally-bought-my-dream-airplane/" rel="noopener noreferrer" target="_self"&gt;I Finally Bought My Dream Airplane&lt;/a&gt; appeared first on &lt;a href="https://miscdotgeek.com" rel="noopener noreferrer" target="_self"&gt;MiscDotGeek&lt;/a&gt;.&lt;/p&gt;</description><author>MiscDotGeek</author><pubDate>Mon, 07 Oct 2024 04:52:53 GMT</pubDate><guid isPermaLink="true">https://miscdotgeek.com/i-finally-bought-my-dream-airplane/</guid></item><item><title>Early Stage Company Offsites</title><link>https://www.swyx.io/early-offsites</link><description>&gt; this post was mostly dictated off the top of my head with Wispr AI</description><author>swyx's site RSS Feed</author><pubDate>Mon, 07 Oct 2024 02:38:50 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/early-offsites</guid></item><item><title>A simple telnet based Taboo game in async Python</title><link>https://bytepawn.com/simple-taboo-server-in-python.html</link><description>&lt;p&gt;I built a simple Python-based Taboo game server using &lt;code&gt;asyncio&lt;/code&gt; to provide a customizable multiplayer experience via Telnet. &lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/taboo.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 06 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/simple-taboo-server-in-python.html</guid></item><item><title>Why I Use Swift To Make Generative Art</title><link>https://thecodist.com/why-i-use-swift-to-make-generative-art/</link><description>&lt;p&gt;Now that I am retired from programming for a living, I make generative art (not AI; see my post &lt;a href="https://artasartist.com/what-is-generative-art/?ref=thecodist.com"&gt;What Is Generative Art?&lt;/a&gt;) every day. I belong to a discord community of generative artists, yet I stick out because I am the only person using Swift as my chosen language.&lt;/p&gt;</description><author>The Codist</author><pubDate>Fri, 04 Oct 2024 18:47:41 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/why-i-use-swift-to-make-generative-art/</guid></item><item><title>AI Strategy in 2024 for the Rest of Us</title><link>https://bytepawn.com/ai-strategy-in-2024.html</link><description>&lt;p&gt;My review of the AI landscape in late 2024, and what the road ahead looks like for those of us not building foundational models. &lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/ai-strategy-2024.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 04 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/ai-strategy-in-2024.html</guid></item><item><title>How Talking Over A Wall Changed My Direction As A Programmer</title><link>https://thecodist.com/how-talking-over-a-wall-changed-my-direction-as-a-programmer/</link><description>&lt;p&gt;I started my programming career in October 1981 at a large defense contractor (GD). At the time, my goal was to work for a couple of years and then continue my education with a Ph.D. in Chemistry (I had already been accepted).&lt;/p&gt;&lt;p&gt;The office I worked in was a&lt;/p&gt;</description><author>The Codist</author><pubDate>Wed, 02 Oct 2024 21:21:51 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/how-talking-over-a-wall-changed-my-direction-as-a-programmer/</guid></item><item><title>Ad hoc tools for gathering prompt context</title><link>https://austinhenley.com/blog/promptcontext.html</link><description>&lt;a href="https://austinhenley.com/blog/promptcontext.html"&gt;https://austinhenley.com/blog/promptcontext.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Tue, 01 Oct 2024 06:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/promptcontext.html</guid></item><item><title>Phrase Matching in Marginalia Search</title><link>https://www.marginalia.nu/log/a_111_phrase_matching/</link><description>&lt;p&gt;Marginalia Search now properly supports phrase matching. This not only permits a more robust implementation of quoted search queries, but also helps promote results where the search terms occur in the document exactly in the same order as they do in the query.&lt;/p&gt;
&lt;p&gt;This is a write-up about implementing this change. This is going to be a relatively long post, as it represents about 4 months of work.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also happy and grateful to announce that the nlnet people reached out after the run of &lt;a href="../a_107_nlnext"&gt;the grant&lt;/a&gt; was over and asked me if I had more work in the pipe, and agreed to fund this change as well!&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 30 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_111_phrase_matching/</guid></item><item><title>Build a serverless ACID database with this one neat trick (atomic PutIfAbsent)</title><link>http://notes.eatonphil.com/2024-09-29-build-a-serverless-acid-database-with-this-one-neat-trick.html</link><description>&lt;p&gt;Delta Lake is an open protocol for serverless ACID databases. Due to
its simplicity, scalability, and the number of open-source
implementations, it's quickly becoming the DuckDB of serverless
transactional databases for analytics workloads. Iceberg is a
contender too, and is similar in many ways. But since Delta Lake is
simpler (simple != better) that's where we'll focus in this post.&lt;/p&gt;
&lt;p&gt;Delta Lake has one of the most accessible database papers I've read
(&lt;a href="https://www.vldb.org/pvldb/vol13/p3411-armbrust.pdf"&gt;link&lt;/a&gt;). It's
kind of like the
&lt;a href="https://github.com/xoreaxeaxeax/movfuscator"&gt;movfuscator&lt;/a&gt; of
databases.&lt;/p&gt;
&lt;p&gt;Thanks to its simplicity, in this post we'll implement a Delta
Lake-inspired serverless ACID database in 500 lines of Go code with
zero dependencies. It will support creating tables, inserting rows
into a table, and scanning all rows in a table. All while allowing
concurrent readers and writers and achieving &lt;a href="https://jepsen.io/consistency"&gt;snapshot
isolation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are other critical parts of Delta Lake we'll ignore: updating
rows, deleting rows, checkpointing the transaction metadata log,
compaction, and probably much more I'm not aware of. We must start
somewhere.&lt;/p&gt;
&lt;p&gt;All code for this post is &lt;a href="https://github.com/eatonphil/otf"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="delta-lake-basics"&gt;Delta Lake basics&lt;/h3&gt;&lt;p&gt;Delta Lake writes immutable data files to blob storage. It stores the
names of new data files for a transaction in a metadata file. It
handles concurrency (i.e. achieves snapshot isolation) with an atomic
PutIfAbsent operation on the metadata file for the transaction.&lt;/p&gt;
&lt;p&gt;This method of concurrency control works because the metadata files
follow a naming scheme that includes the transaction id in the file
name. When a new transaction starts, it finds all existing metadata
files and picks its own transaction id by adding 1 to the largest
transaction id it sees.&lt;/p&gt;
&lt;p&gt;When a transaction goes to commit, writing the metadata file will
fail if another transaction has already picked the same transaction
id.&lt;/p&gt;
&lt;p&gt;If a transaction does no writes and creates no tables, the transaction
does not attempt to write any metadata file. Snapshot isolation!&lt;/p&gt;
&lt;p&gt;Let's dig into the implementation.&lt;/p&gt;
&lt;h3 id="boilerplate"&gt;Boilerplate&lt;/h3&gt;&lt;p&gt;Let's give ourselves some nice assertion methods, a debug method, and
a uuid generator. In &lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;encoding/json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;io&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;slices&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;strings&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s '%v' != '%v'&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--debug&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[DEBUG]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// https://datatracker.ietf.org/doc/html/rfc4122#section-4.4&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not open /dev/random: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not read 16 bytes from /dev/random: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected 16 bytes from /dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Set bit 6 to 0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^(&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Set bit 7 to 1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Set version&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^(&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^(&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^(&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%x-%x-%x-%x-%x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Is that uuid method correct? Hopefully. Efficient? No. But it's
preferable to avoid dependencies in pedagogical projects.&lt;/p&gt;
&lt;p&gt;Moving on.&lt;/p&gt;
&lt;h3 id="blob-storage-requirements"&gt;Blob storage requirements&lt;/h3&gt;&lt;p&gt;As mentioned above, the basic requirement is that we support
atomically writing some bytes to a location if the location doesn't
already exist.&lt;/p&gt;
&lt;p&gt;On top of that we also need the ability to list locations by prefix,
and the ability to read the bytes at some location.&lt;/p&gt;
&lt;p class="note"&gt;
  We'll diverge from Delta Lake in how we name files on disk. For one,
  we'll keep all files in the same directory with a fixed prefix for
  metadata and another table name prefix for each data file. This
  simplifies the implementation of &lt;code&gt;listPrefix&lt;/code&gt; a bit.
  &lt;br /&gt;
  &lt;br /&gt;
  However, this also diverges from Delta Lake in that transactions
  will represent all tables. In Delta Lake that is not so. Delta Lake
  has a per-table transaction log. Only transactions that read and
  write the same table in Delta Lake achieve snapshot isolation.
&lt;/p&gt;&lt;p&gt;So let's set up an interface to describe these requirements:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;objectStorage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Must be atomic.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;putIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;listPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And this is literally all we need to get ACID transactions. That's crazy!&lt;/p&gt;
&lt;h4 id="atomic-put-and-cloud-blob-storage"&gt;Atomic Put and cloud blob storage&lt;/h4&gt;&lt;p&gt;We could implement the atomic &lt;code&gt;putIfAbsent&lt;/code&gt; part of this interface in
2024 using &lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/08/amazon-s3-conditional-writes/"&gt;conditional
writes&lt;/a&gt;
on S3. Or we could implement this interface with the &lt;code&gt;If-None-Match&lt;/code&gt;
&lt;a href="https://learn.microsoft.com/en-us/rest/api/storageservices/specifying-conditional-headers-for-blob-service-operations"&gt;header&lt;/a&gt;
on Azure Cloud Storage. Or we could implement this interface with the
&lt;code&gt;x-goog-if-generation-match&lt;/code&gt;
&lt;a href="https://cloud.google.com/storage/docs/xml-api/put-object"&gt;header&lt;/a&gt; on
Google Cloud Storage.&lt;/p&gt;
&lt;p&gt;Indeed a good exercise for the reader would be to implement this
interface for other blob storage providers and see your serverless
cloud database in action!&lt;/p&gt;
&lt;p&gt;But the simplest method of all is to implement it on the filesystem,
which is what we'll do next.&lt;/p&gt;
&lt;h3 id="a-filesystem-blob-store"&gt;A filesystem blob store&lt;/h3&gt;&lt;p&gt;If we had a server we could implement atomic &lt;code&gt;putIfAbsent&lt;/code&gt; with a
mutex. But we're serverless baby. Thankfully, POSIX &lt;a href="https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html"&gt;supports atomic
link&lt;/a&gt;
which will fail if the new name is already a file.&lt;/p&gt;
&lt;p&gt;So we'll just create a temporary file and write out all
bytes. Finally, we link the temporary file to the permanent name we
intended. For cleanliness (not correctness), if there is an error at
any point, we'll remove the temporary file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newFileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;putIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_WRONLY&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_CREATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;bufSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;toWrite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;bufSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;toWrite&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  &lt;a href="https://news.ycombinator.com/item?id=41702593"&gt;yencabulator&lt;/a&gt;
  on HN pointed out that an earlier version of this post had a buggy
  implementation of &lt;code&gt;putIfAbsent&lt;/code&gt; (that attempted to manage
  atomicity solely via &lt;code&gt;O_EXCL | O_CREAT&lt;/code&gt;) would leave
  around potentially bad metadata files if the &lt;code&gt;os.Remove&lt;/code&gt;
  call ever failed.
  &lt;br /&gt;
  &lt;br /&gt;
  The &lt;code&gt;link&lt;/code&gt; approach works around that because the file is
  already fully and correctly written by the time we do the link.
&lt;/p&gt;&lt;p&gt;&lt;code&gt;listPrefix&lt;/code&gt; and &lt;code&gt;read&lt;/code&gt; are minimal wrappers around filesystem APIs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;listPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Readdirnames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It is worth talking a bit about reading a directory though. Go doesn't
provide a nice iterator API for us and I didn't want to implement this
as callbacks with
&lt;a href="https://pkg.go.dev/path/filepath#WalkDir"&gt;&lt;code&gt;path/filepath.WalkDir&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We could use &lt;a href="https://pkg.go.dev/os#File.ReadDir"&gt;&lt;code&gt;os.File.ReadDir&lt;/code&gt;&lt;/a&gt;
but it allocates for all files in the directory. Sure, in a
pedagogical project we don't worry about millions of files. But the
&lt;code&gt;ReadDir&lt;/code&gt; API, the error cases in particular, also isn't much simpler
than &lt;a href="https://pkg.go.dev/os#File.Readdirnames"&gt;&lt;code&gt;Readdirnames&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  What's more, even though we iterated through batches of directory
  entries, and did prefix filtering before accumulating, we still could
  have considered returning an iterator here ourselves. It seems
  possible and likely that the number of data files grows quite large in
  a production system. But I was lazy.
&lt;/p&gt;&lt;p&gt;It would be nice if Go introduced an actual iterator API for
reading a directory. :)&lt;/p&gt;
&lt;h4 id="delta-lake-and-stale-reads"&gt;Delta Lake and stale reads&lt;/h4&gt;&lt;p&gt;In any case the ACID properties of Delta Lake (and Iceberg) don't
depend on being able to read up-to-date data.&lt;/p&gt;
&lt;p&gt;This is because concurrent (or stale) transactions that &lt;em&gt;write&lt;/em&gt; will
&lt;em&gt;fail on commit&lt;/em&gt;. And also because all files written (even metadata
files) are immutable.&lt;/p&gt;
&lt;p&gt;Since all data is immutable, we will always be able to read at least a
consistent snapshot of data. But we will never be able to get
SERIALIZABLE &lt;strong&gt;read-only&lt;/strong&gt; transactions. This is just how Delta Lake
and Iceberg work. And it is a &lt;a href="https://jepsen.io/consistency"&gt;similar&lt;/a&gt;
or better consistency level to what any major SQL database &lt;a href="https://github.com/ept/hermitage"&gt;gives you
by default&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You'll see what I mean later on when we implement transaction commits.&lt;/p&gt;
&lt;h3 id="transaction-boilerplate"&gt;Transaction boilerplate&lt;/h3&gt;&lt;p&gt;Now that we've got a blob storage abstraction and a filesystem
implementation of it, let's start sketching out what a client and what
a transaction looks like.&lt;/p&gt;
&lt;p&gt;In Delta Lake, a transaction consists of a list of actions. An action
might be to define a table's schema, or to add a data file, or to
remove a data file, etc. In this post we'll only implement the first
two actions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DataobjectAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadataAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// an enum, only one field will be non-nil&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;DataobjectAction&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadataAction&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TODO: Support object removal.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// DeleteDataobject *DataobjectAction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These fields are all exported (i.e. capitalized, if you're not
familiar with Go) because we will be writing them to disk when the
transaction commits as the transaction's metadata.&lt;/p&gt;
&lt;p&gt;In fact &lt;code&gt;Action&lt;/code&gt;s and the transaction's id will be the only parts of
the transaction we write to disk. Everything else will be in-memory
state.&lt;/p&gt;
&lt;p&gt;For our convenience we will track in memory a history of all previous
actions, a mapping of table columns, and a mapping of unflushed data
by table.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Both are mapping table name to a list of actions on the table.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Mapping tables to column names.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Mapping table name to unflushed/in-memory rows. When rows&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// are flushed, the dataobject that contains them is added to&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// `tx.actions` above and `tx.unflushedDataPointer[table]` is&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// reset to `0`.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Only the current &lt;code&gt;transaction&lt;/code&gt; will ever have
&lt;code&gt;transaction.previousActions&lt;/code&gt; filled out. &lt;code&gt;transaction.tables&lt;/code&gt; will be
populated when the transaction starts by reading through
&lt;code&gt;transaction.previousActions&lt;/code&gt; for &lt;code&gt;ChangeMetadataAction&lt;/code&gt;s, and we will
also add onto it when we create a table in the current transaction.&lt;/p&gt;
&lt;p&gt;We will append to &lt;code&gt;transaction.Actions&lt;/code&gt; every time we write a new data
file and every time we create a new table.&lt;/p&gt;
&lt;p&gt;We will add rows to &lt;code&gt;transaction.unflushedData&lt;/code&gt; for a table until
&lt;code&gt;transaction.unflushedDataPointer&lt;/code&gt; for that table reaches
&lt;code&gt;DATAOBJECT_SIZE&lt;/code&gt; upon which time we will write that data to disk and
add a &lt;code&gt;DataobjectAction&lt;/code&gt; entry to &lt;code&gt;transaction.Actions&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="client-boilerplate"&gt;Client boilerplate&lt;/h3&gt;&lt;p&gt;A &lt;code&gt;client&lt;/code&gt; will consist of an &lt;code&gt;objectStorage&lt;/code&gt; implementation and a
possibly empty &lt;code&gt;*transaction&lt;/code&gt;. Empty meaning there is no current
transaction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;objectStorage&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Current transaction, if any. Only one transaction per&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// client at a time. All reads and writes must be within a&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// transaction.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;objectStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;errExistingTx&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Existing Transaction&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No Transaction&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;errTableExists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Table Exists&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;errNoTable&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No Such Table&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="client-or-database?"&gt;Client or database?&lt;/h4&gt;&lt;p&gt;In a previous version of my code I named this &lt;code&gt;client&lt;/code&gt; struct
&lt;code&gt;database&lt;/code&gt;. But that's misleading. There is no central database. There
is just the client and the blob storage.&lt;/p&gt;
&lt;p&gt;Clients work with transactions directly and only when attempting to
commit does the blob storage abstraction let the client know if the
transaction succeeded or not.&lt;/p&gt;
&lt;h3 id="starting-a-transaction"&gt;Starting a transaction&lt;/h3&gt;&lt;p&gt;When we start a transaction, we will first read all existing
transactions from disk and accumulate the actions from each prior
transaction.&lt;/p&gt;
&lt;p&gt;We will interpret &lt;code&gt;ChangeMetadataAction&lt;/code&gt;s and materialize them into a
current view of all tables.&lt;/p&gt;
&lt;p&gt;And we will assign a transaction ID to this transaction to be 1
greater than the largest existing transaction ID we see.&lt;/p&gt;
&lt;p&gt;Again it doesn't matter if the &lt;code&gt;listPrefix&lt;/code&gt; call we use returns an
up-to-date list. Notably on blob storage there are few guarantees
about LIST operations recency. The Delta Lake paper mentions this too.&lt;/p&gt;
&lt;p&gt;Out-of-date transactions attempting to write will be caught when we go
to commit the transaction. Out-of-date transactions attempting only to
read will still read a consistent snapshot.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errExistingTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;logPrefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_log_&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;txLogFilenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logPrefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;txLogFilename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;txLogFilenames&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txLogFilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oldTx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;oldTx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Transaction metadata files are sorted&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// lexicographically so that the most recent&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// transaction (i.e. the one with the largest&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// transaction id) will be last and tx.Id will end up&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 1 greater than the most recent transaction ID we&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// see on disk.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oldTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oldTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// Store the latest version of&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// each table in memory for&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// easy lookup.&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;mtd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadata&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mtd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Columns&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unsupported action: %v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we're set.&lt;/p&gt;
&lt;h3 id="creating-a-table"&gt;Creating a table&lt;/h3&gt;&lt;p&gt;When we create a table, we need to add a &lt;code&gt;ChangeMetadataAction&lt;/code&gt; to the
transactions &lt;code&gt;Actions&lt;/code&gt;. And we also want to add the table info to the
in-memory &lt;code&gt;transaction.tables&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;We don't do any of this durably. The change here will be written to
disk on commit (if the transaction succeeds).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errTableExists&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Store it in the in-memory mapping.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// And also add it to the action history for future transactions.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadataAction&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Easy peasy. Now for the fun part, writing data!&lt;/p&gt;
&lt;h3 id="writing-a-row"&gt;Writing a row&lt;/h3&gt;&lt;p&gt;This is the next area where we'll diverge from Delta Lake. For the
sake of zero dependencies we are going to store data in-memory as an
array of array of &lt;code&gt;any&lt;/code&gt;. And when we later write rows to disk we'll
write them as JSON. A real Delta Lake implementation would store data
in-memory in Apache Arrow format, and write to disk as Parquet.&lt;/p&gt;
&lt;p&gt;In line with Delta Lake though we will buffer data in memory until we
get 64K rows. When we get 64K rows for a particular table we will
flush all those rows to disk. (When we go to commit a transaction we
will flush any outstanding rows.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTable&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Try to find an unflushed/in-memory dataobject for this table&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flushRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now let's implement flushing.&lt;/p&gt;
&lt;h3 id="flushing-a-data-object"&gt;Flushing a data object&lt;/h3&gt;&lt;p&gt;Recall that data objects in Delta Lake (and Iceberg) are
immutable. Once we've got enough data to write a data object, we give
it a unique name, write it to disk, and add a &lt;code&gt;AddObjectAction&lt;/code&gt; to the
transaction's list of &lt;code&gt;Actions&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Len&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flushRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// First write out dataobject if there is anything to write out.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_table_%s_%s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then record the newly written data file.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;DataobjectAction&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Reset in-memory pointer.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's it for writing data! Let's now look at reading data.&lt;/p&gt;
&lt;h3 id="scanning-a-table"&gt;Scanning a table&lt;/h3&gt;&lt;p&gt;We're going to make scanning mildly more complicated than it needed to
be in pedagogical code because we'll have &lt;code&gt;client.scan()&lt;/code&gt; return an
iterator rather than an array with all rows.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;scanIterator&lt;/code&gt; will first read from in-memory (unflushed)
data. And then it will read through every data object for the table
that is still a part of this transaction. We will know which data
objects are still a part of this transaction by reading through all
&lt;code&gt;AddDataobject&lt;/code&gt; actions. A future version of this project would also
eliminate data object files from the list by observing
&lt;code&gt;DeleteDataobject&lt;/code&gt; actions. But we don't do that in this post.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;scanIterator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;allActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;scanIterator&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;unflushedRowsLen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;scanIterator&lt;/code&gt; needs to track where we are in in-memory rows, in
data objects, and within a particular data object.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scanIterator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// First we iterate through unflushed rows.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedRowsLen&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then we move through each dataobject.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dataobjectsPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// And within each dataobject we iterate through rows.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the &lt;code&gt;scanIterator&lt;/code&gt; will be driven by a &lt;code&gt;next()&lt;/code&gt; method that goes
through in-memory data first and then through what's on disk.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;readDataobject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_table_%s_%s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// returns (nil, nil) when done&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;scanIterator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Iterate through in-memory rows first.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRowsLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRowPointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRowPointer&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// If we've gotten through all dataobjects on disk we're done.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectsPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectsPointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readDataobject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectsPointer&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's it for scanning a table! The final piece of the puzzle is
committing a transaction.&lt;/p&gt;
&lt;h3 id="committing-a-transaction"&gt;Committing a transaction&lt;/h3&gt;&lt;p&gt;When we commit a transaction we must flush any remaining data. A
read-only transaction (one which has no &lt;code&gt;Actions&lt;/code&gt;) is immediately
done. There is no concurrency check.&lt;/p&gt;
&lt;p&gt;Otherwise we will serialize transaction state and attempt to
atomically &lt;code&gt;putIfAbsent&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The only way this will fail is if there is another concurrent writer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Flush any outstanding data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flushRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Read-only transaction, no need to do a concurrency check.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_log_%020d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// We won't store previous actions, they will be recovered on&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// new transactions. So unset them. Honestly not totally&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// clear why.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unimplemented&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is the crux of Delta Lake. It's simple. And honestly it's a bit
shocking. Real Delta Lake does support automatic retries in some
cases. But primarily you are limited to a single writer per table,
even if the writers are writing non-conflicting rows. Iceberg is
basically the same here, it's just how metadata is tracked that
differs.&lt;/p&gt;
&lt;p class="note"&gt;
  As mentioned in another note above, our implementation is actually
  stricter than Delta Lake since it manages all table transaction logs
  together. This means you can get snapshot isolation across all
  tables (which Delta Lake doesn't support) but it will mean
  significantly more contention and failed write transactions.
&lt;/p&gt;&lt;p&gt;The Delta Lake and Iceberg folks apparently wanted to avoid
FoundationDB (i.e. the Snowflake architecture, which is mentioned in
the Delta Lake paper) so much that they'd give up row-level
concurrency to be mostly serverless.&lt;/p&gt;
&lt;p&gt;Is it worth it? Dunno. Delta Lake and Iceberg are getting massive
adoption. Many very smart people have worked, and continue to work, on
both. Moreover it is apparently what the market wants. Every
database-like product is implementing, or is planning to implement,
Delta Lake or Iceberg.&lt;/p&gt;
&lt;h3 id="trying-it-out"&gt;Trying it out&lt;/h3&gt;&lt;p&gt;Let's add a test in &lt;code&gt;main_test.go&lt;/code&gt; to see what happens with concurrent
writers. Follow the comments and debug logs for details:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;testing&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestConcurrentTableWriters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MkdirTemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;test-database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newFileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Have c2Writer start up a transaction.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start first c2 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2] new tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// But then have c1Writer start a transaction and commit it first.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start first c1 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] new tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not create x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] Created table&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Joey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write first row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write second row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not commit tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] Committed tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Now go back to c2 and write data.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not create x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2] Created table&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Holly&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write first row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;concurrent commit must fail&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2] tx not committed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Try it out:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;otf
&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;tidy
&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-run&lt;span class="w"&gt; &lt;/span&gt;TestConcurrentTableWriters&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--debug
&lt;span class="go"&gt;[DEBUG] [c2] new tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] new tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] Created table&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] Committed tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2] Created table&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2] tx not committed&lt;/span&gt;
&lt;span class="go"&gt;PASS&lt;/span&gt;
&lt;span class="go"&gt;ok      otf 0.311s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's pretty cool.&lt;/p&gt;
&lt;p&gt;And what about a reader and concurrent writer? Observe that the reader
always reads a snapshot. Follow the comments again for detail:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestConcurrentReaderWithWriterReadsSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MkdirTemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;test-database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newFileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c2Reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// First create some data and commit the transaction.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start first c1 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Started tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not create x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Created table&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Joey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write first row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write second row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not commit tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Committed tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Now start a new transaction for more edits.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start second c1 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Starting new write tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Before we commit this second write-transaction, start a&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// read transaction.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start c2 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Started tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Write and commit rows in c1.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Ada&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write third row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Wrote third row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Scan x in read-only transaction&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not scan x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Started scanning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not iterate x scan&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Done scanning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Got row in reader tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Joey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected two rows&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Scan x in c1 write transaction&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not scan x in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Started scanning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not iterate x scan in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Done scanning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Got row in tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Ada&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Since this hasn't been serialized to JSON, it's still an int not a float.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Joey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected three rows&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Writer committing should succeed.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not commit second tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Committed tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Reader committing should succeed.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not commit read-only tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Committed tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-run&lt;span class="w"&gt; &lt;/span&gt;TestConcurrentReaderWithWriterReadsSnapshot&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--debug
&lt;span class="go"&gt;[DEBUG] [c1Writer] Started tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Created table&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Committed tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Starting new write tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Started tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Wrote third row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Started scanning&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Got row in reader tx [Joey 1]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Got row in reader tx [Yue 2]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Done scanning&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Started scanning&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Got row in tx [Ada 3]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Got row in tx [Joey 1]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Got row in tx [Yue 2]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Done scanning&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Committed tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Committed tx&lt;/span&gt;
&lt;span class="go"&gt;PASS&lt;/span&gt;
&lt;span class="go"&gt;ok      otf 0.252s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Sweet.&lt;/p&gt;
&lt;h3 id="what's-next?"&gt;What's next?&lt;/h3&gt;&lt;p&gt;As mentioned, we didn't touch a lot of things. Handling updates and
deletes, transaction log checkpoints, data object compaction, etc.&lt;/p&gt;
&lt;p&gt;Take a close look at the &lt;a href="https://www.vldb.org/pvldb/vol13/p3411-armbrust.pdf"&gt;Delta Lake
paper&lt;/a&gt; and the
&lt;a href="https://github.com/delta-io/delta/blob/master/PROTOCOL.md"&gt;Delta Lake
Spec&lt;/a&gt; and
see what you can do!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Build a serverless ACID database with this one neat trick.&lt;br /&gt;&lt;br /&gt;(New blog post)&lt;a href="https://t.co/rHgfKSPY6q"&gt;https://t.co/rHgfKSPY6q&lt;/a&gt; &lt;a href="https://t.co/1hmjsxIk6w"&gt;pic.twitter.com/1hmjsxIk6w&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1840474893491560777?ref_src=twsrc%5Etfw"&gt;September 29, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 29 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-09-29-build-a-serverless-acid-database-with-this-one-neat-trick.html</guid></item><item><title>Techtalk: Why are A/B tests the gold standard of causal inference?</title><link>https://bytepawn.com/techtalk-ab-testing.html</link><description>&lt;p&gt;Recently, I delivered a techtalk on A/B testing to an audience of non-technical Product Managers and experienced Data Scientists. &lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/abtest-google-slides.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 29 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/techtalk-ab-testing.html</guid></item><item><title>200 articles</title><link>https://bytepawn.com/200-articles.html</link><description>&lt;p&gt;A review and introspect on the second 100 articles written on Bytepawn. &lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/200.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 28 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/200-articles.html</guid></item><item><title>ChatGPT's critique of OpenAI CEO Sam Altman's vision of the AI future</title><link>https://bytepawn.com/sam-altman-the-intelligence-age.html</link><description>&lt;p&gt;A few days ago OpenAI CEO Sam Altman published an article titled The Intelligence Age. I used OpenAI's new &lt;code&gt;o1-mini&lt;/code&gt; model to critique Sam Altman's writing of its own future. &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/outlook-2024-1.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 27 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/sam-altman-the-intelligence-age.html</guid></item><item><title>Be someone who does things</title><link>http://notes.eatonphil.com/2024-09-23-be-someone-who-does-things.html</link><description>&lt;p&gt;I &lt;a href="https://notes.eatonphil.com/2024-08-24-obsession.html"&gt;wrote last
month&lt;/a&gt; that
&lt;em&gt;what you want to do&lt;/em&gt; is one of the most useful motivations in life. I
want to follow that up by saying that the only thing more important
than wanting to do something is to &lt;em&gt;actually&lt;/em&gt; do something.&lt;/p&gt;
&lt;p&gt;The most valuable trait you can develop for yourself is to be
consistent. It is absolutely something you can develop. And moreover
it's kind of hard to believe that for anyone it is innate.&lt;/p&gt;
&lt;p&gt;I meet so many people who say they want to do things. And I ask them
what they're doing to get there and they get flustered. This is
completely understandable.&lt;/p&gt;
&lt;p&gt;I meet so many students who feel overwhelmed by what everyone else is
doing. This is also understandable.&lt;/p&gt;
&lt;p&gt;But it doesn't matter what anyone else is doing. It doesn't matter
where anyone else is at. It matters where you are at. Compete with
yourself before you compete with anyone else. What matters is that you
get into a habit of consistently working on little goals.&lt;/p&gt;
&lt;p&gt;If you pick something that is too complex, break it down. Keep on
breaking problems or ideas down until you find a problem or idea you
can solve.&lt;/p&gt;
&lt;p&gt;Then keep on finding new problems to solve. Move on in complexity over
time as you can and want to.&lt;/p&gt;
&lt;p&gt;Don't worry about getting things perfect. Who can discredit you for
doing your best? What shame is there when you're being earnest? The
only thing that makes sense to feel bad about is not &lt;em&gt;trying to do&lt;/em&gt;
what you &lt;em&gt;genuinely wanted to do&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And this doesn't have to be about projects or ideas outside of
work. There may be things you want to do at work like improving
documentation or writing better tests or adding new checks to code or
blogging or interviewing customers or working with another team.&lt;/p&gt;
&lt;p&gt;Like I said in
&lt;a href="https://notes.eatonphil.com/2024-08-24-obsession.html"&gt;Obsession&lt;/a&gt;,
don't worry about what you do daily. That is too frequent to think
about. Instead think about what you're doing once a month.&lt;/p&gt;
&lt;p&gt;Make time once a month to publish a post or complete a small
project. Whatever you want to do, I am confident you can find some
small version of it that you could commit to doing once a month. Be
consistent!&lt;/p&gt;
&lt;p&gt;If a month is too often, pick a longer freqency. Find whatever cadence
and whatever size of project that allows you to be consistent.&lt;/p&gt;
&lt;p&gt;When you're consistent over the course of months I think you'll be
astounded at what you accomplish in a year.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Shorter post tonight, may add to this later on.&lt;br /&gt;&lt;br /&gt;Be someone who does things. And do these (little) things consistently.&lt;a href="https://t.co/oVb6Sz8eEK"&gt;https://t.co/oVb6Sz8eEK&lt;/a&gt; &lt;a href="https://t.co/kNrZQ4pvTN"&gt;pic.twitter.com/kNrZQ4pvTN&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1838378171005128910?ref_src=twsrc%5Etfw"&gt;September 24, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 23 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-09-23-be-someone-who-does-things.html</guid></item><item><title>The sorry state of Java deserialization</title><link>https://www.marginalia.nu/log/a_110_java_io/</link><description>&lt;p&gt;I&amp;rsquo;ve been on a bit of a frustration-driven quest to solve a problem I frequently encounter
working on the search engine, that is, reading data from disk.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;d think this would be a pretty basic thing, but doing this in a way that is half-way performant is surprisingly hard and requires avoiding basically all the high level tools at your disposal.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a common sentiment that modern hardware is fast, so this may not matter, but we aren&amp;rsquo;t speaking a 30% performance hit, the the question is how many orders of magnitude you&amp;rsquo;re willing to forego.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sun, 22 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_110_java_io/</guid></item><item><title>How I manage my 1v1s</title><link>https://bytepawn.com/how-i-manage-my-1v1s.html</link><description>&lt;p&gt;I detail my structured approach to managing one-on-one meetings within a 40-person data team, emphasizing people management principles like radical transparency, tailored meeting cadences, and strategies to navigate common managerial challenges to foster a supportive and productive work environment. &lt;br /&gt;&lt;br /&gt; &lt;img alt="Meeting distribution" src="/images/meeting-distribution.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 22 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/how-i-manage-my-1v1s.html</guid></item><item><title>Arrogant programmers and Enshittification - A New Understanding</title><link>https://mikewarot.blogspot.com/2024/09/arrogant-programmers-and.html</link><description>&lt;p&gt;
  A few weeks ago, I
  &lt;a href="https://news.ycombinator.com/item?id=41265143"&gt;replied&lt;/a&gt; to
  &lt;a href="https://news.ycombinator.com/item?id=41263143" target="_blank"&gt;a thread on Hacker News&lt;/a&gt;
  about a "leaked" talk by former Google CEO&amp;nbsp;&amp;nbsp;Eric Schmidt. The key
  phrase I tried to pick apart was
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
  So imagine a non-arrogant programmer that actually does what you want
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;
  I &lt;i&gt;had assumed&lt;/i&gt; he was referring to situations similar to my own
  experience as a software developer interacting with customers back in the
  1980s. Programming was still a relatively new thing, and most people didn't
  understand how data was stored and processed. I considered it my job to probe
  what they really wanted to have, and figure out how I could meet their needs
  by
  &lt;a href="https://en.wikipedia.org/wiki/Impedance_matching" target="_blank"&gt;impedance matching&lt;/a&gt;
  them to what was feasible. This smoothing over of their wants and needs with
  reality made everyone happy, in the end.
&lt;/p&gt;
&lt;hr /&gt;A few days ago, I watched &lt;a href="https://www.youtube.com/watch?v=4EmstuO0Em8" target="_blank"&gt;Cory Doctorow's presentation at DefCon32&lt;/a&gt;
about the ongoing&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Enshittification" target="_blank"&gt;Enshittification&lt;/a&gt;&amp;nbsp;of everything. He delivered, as usual for him, a passionate and fact
stuffed critique of the way things are going, and how we got there. It's been
quietly flowing through my
&lt;a href="http://www.programmersstone.com/" rel="nofollow" target="_blank"&gt;mapper mind&lt;/a&gt;, bumping up against all of the other chunks of knowledge I've recently
pondered.
&lt;div&gt;
  &lt;hr /&gt;
  &lt;p&gt;
    This morning, I was reading a
    &lt;a href="https://news.ycombinator.com/item?id=41609099" target="_blank"&gt;thread&lt;/a&gt;
    about Thea Lim's
    &lt;a href="https://thewalrus.ca/collapse-of-self-worth-in-the-digital-age/" target="_blank"&gt;The Collapse of Self-Worth in the Digital Age&lt;/a&gt;.&amp;nbsp;
  &lt;/p&gt;
  &lt;p&gt;She helped me see that though I have had my self-worth tied up in work, there was a deep cause of disastisfaction once I was out of the work force. I've always been good at explaining complex things in a way others can understand. (Impedance matching, again!)

  This bumped against some other things I was pondering, and &lt;b&gt;then the penny dropped&lt;/b&gt;
  &lt;/p&gt;
  &lt;p&gt;&lt;i&gt;I was wrong about what Eric Schmidt meant, and I see that now.&lt;/i&gt;&lt;/p&gt;
  &lt;p&gt;
    I had made a
    &lt;a href="https://en.wikipedia.org/wiki/Category_mistake" target="_blank"&gt;category error&lt;/a&gt;
    and assumed he was just trying to get good results, or meet some weird quirk
    of his design sense, like Steve Jobs is said to have done, and was getting
    frustrated. Oh boy, was I ever wrong.&lt;br /&gt;&lt;br /&gt;Assuming that Schmidt was
    talking bout overriding ethical concerns is a way better fit for the amount
    of emotion this seems to hold for him.
  &lt;/p&gt;
  &lt;p&gt;Cory's right. We really do have to hold the line against this shit.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;So there you have it... a brief journey through the thought process of a mapper, with a side of self-discovery&lt;/p&gt;
&lt;/div&gt;</description><author>--Mike--</author><pubDate>Sat, 21 Sep 2024 20:44:39 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2024/09/arrogant-programmers-and.html</guid></item><item><title>Apple M0110 Keyboard CPU replacement - part #1</title><link>https://mikewarot.blogspot.com/2024/09/apple-m0110-keyboard-cpu-replacement.html</link><description>&lt;p&gt;My friend Jim bought an Apple Macintosh at the Vintage Computer Festival Midwest on Saturday. It's got a bad keyboard. The keyboard has a bad Intel 8021 CPU.&lt;br /&gt;&lt;br /&gt;I need to replace that chip. It's going to take wayyyy more time that I'm used to dumping into a project successfully.&amp;nbsp; This is part of my process to push myself forward.&lt;/p&gt;&lt;p&gt;So far I've:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;Searched for an 8048 series disassembler, found none&lt;/li&gt;&lt;li&gt;Downloaded the &lt;a href="http://bitsavers.informatik.uni-stuttgart.de/components/intel/8048/1980_MCS-48_Users_Manual.pdf"&gt;8048 series manual&lt;/a&gt; from BitSavers&lt;/li&gt;&lt;li&gt;Decided to write an 8021 specific disassembler in pascal&lt;/li&gt;&lt;li&gt;Started a Lazarus project in my C:\res21 folder&lt;/li&gt;&lt;li&gt;Published the above to an &lt;a href="https://github.com/mikewarot/res21" target="_blank"&gt;MIT licensed GitHub repository&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Let's see how this goes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;</description><author>--Mike--</author><pubDate>Tue, 10 Sep 2024 23:02:49 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2024/09/apple-m0110-keyboard-cpu-replacement.html</guid></item><item><title>Go’s New Iterators Smell a Little Funny, but They’re Still Good</title><link>https://donatstudios.com/Go-Iterators-Smell-Funny</link><description>&lt;p&gt;Go's new Iterators were &lt;a href="https://tip.golang.org/doc/go1.23"&gt;released on August 13th with Go 1.23&lt;/a&gt;. They're pretty useful.&lt;/p&gt;
&lt;p&gt;They've had just a minute to marinate. In some parts of the community they've been  an unpopular addition, however I don't particularly mind them. The old &lt;a href="https://go.dev/tour/methods/21"&gt;alternatives&lt;/a&gt; seem a little more &amp;quot;Go&amp;quot; to me, but the new iterators are fine.&lt;/p&gt;
&lt;p&gt;One thing however I am mildly disappointed about is that there isn't just a &lt;em&gt;generic&lt;/em&gt; &lt;code&gt;iterable&lt;/code&gt; type similar to the generic &lt;code&gt;comparable&lt;/code&gt;. It would be nice to have an imaginary &lt;code&gt;iterable&lt;/code&gt; type that could accept maps, slices, arrays, and iterators. As I understand it, iterators combined with &lt;a href="https://pkg.go.dev/slices@master#All"&gt;&lt;code&gt;slices.All&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pkg.go.dev/maps@master#All"&gt;&lt;code&gt;maps.All&lt;/code&gt;&lt;/a&gt; are the blessed alternative to that. It works, I'm using it, but I don't love it.&lt;/p&gt;
&lt;p&gt;All that's neither here nor there, however, and not what I'm here to talk about.&lt;/p&gt;
&lt;p&gt;The part that smells funny to me is that &lt;code&gt;range&lt;/code&gt; now accepts any already existing function that happens to fulfill &lt;a href="https://pkg.go.dev/iter#Seq"&gt;either of the following interfaces&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func(func(any) bool)
func(func(any,any) bool)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It would be less weird if that wasn't also a little troublesome.&lt;/p&gt;
&lt;h2&gt;Example time!&lt;/h2&gt;
&lt;p&gt;The first interface, known as &lt;a href="https://pkg.go.dev/iter@master#Seq"&gt;&lt;code&gt;Seq&lt;/code&gt;&lt;/a&gt; takes a &lt;code&gt;yield&lt;/code&gt; method as an argument that itself accepts any single parameter. The second interface, known as &lt;a href="https://pkg.go.dev/iter@master#Seq2"&gt;&lt;code&gt;Seq2&lt;/code&gt;&lt;/a&gt; similarly takes a &lt;code&gt;yield&lt;/code&gt; method that accepts any two parameters as essentially a key/value tuple. &lt;/p&gt;
&lt;p&gt;Note that &lt;code&gt;yield&lt;/code&gt; is not a magic keyword as in many languages, but a passed-in callable. This is very akin to Go allowing you to name receivers rather than magically defining &lt;code&gt;this&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At first blush to the uninitiated, this iterator looks fine. It seemingly &lt;a href="https://go.dev/play/p/xL4fNb0CLDF"&gt;runs fine&lt;/a&gt; as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;package main

import "fmt"

func mightBeAnIterator(f func(string) bool) {
    f("foo")
    f("bar")
    f("baz")
}

func main() {
    for x := range mightBeAnIterator {
        fmt.Println(x)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;foo
bar
baz&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What's the problem then?&lt;/h2&gt;
&lt;p&gt;The problem is iterators MUST handle early exit of the loop manually. With the current interface fulfilling &amp;quot;iterator&amp;quot;, if we just add what seems a harmless &lt;code&gt;break&lt;/code&gt; to our loop as follows, suddenly the code working moments ago panics.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;    for x := range mightBeAnIterator {
        fmt.Println(x)
        break
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;foo
panic: runtime error: range function continued iteration after function for loop body returned false&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Somewhat surprisingly, as Go exits the loop, it allows the iterator to continue executing. In doing so, however, it signals the iterator through the result of the &lt;code&gt;yield&lt;/code&gt;ing method that it should return. This is to allow for teardown. For example, imagine an iterator that opens files or a socket; continuing execution allows it to close them before returning.&lt;/p&gt;
&lt;p&gt;However, Go will panic if the signal to return is not honored and the &lt;code&gt;yield&lt;/code&gt; function is then invoked again by the iterator.&lt;/p&gt;
&lt;p&gt;To prevent our example iterator from panic-ing, we simply have to listen for the signal to exit, as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func mightBeAnIterator(f func(string) bool) {
    if !f("foo") {
        return
    }
    if !f("bar") {
        return
    }
    if !f("baz") {
        return
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;a href="https://go.dev/play/p/53k6sUUCyqK"&gt;when run&lt;/a&gt;, now we receive simply&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;foo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would imagine the reason for the panic is to prevent iterators from accidentally running wild. &lt;/p&gt;
&lt;p&gt;To my personal taste, I would have strongly preferred honoring the exit signal to be &lt;strong&gt;optional&lt;/strong&gt;, and following superfluous &lt;code&gt;yield&lt;/code&gt; calls being quietly ignored and returning false. &lt;/p&gt;
&lt;p&gt;I can certainly imagine cases where an iterator would want to finish regardless of early exits by its consumer. While still possible, it's awkward to implement.&lt;/p&gt;
&lt;p&gt;We are certainly in a place where, should they decide to go back on the panic behavior, it would not break any existing code. That's not a terrible place to be.&lt;/p&gt;
&lt;p&gt;All this is to say it smells a little funny that such a simple and likely somewhat common interface has this magic range logic available to it.&lt;/p&gt;
&lt;p&gt;Expecting the implementers to implement it correctly and &lt;code&gt;panic&lt;/code&gt;-ing if they don't smells kind of &lt;a href="https://www.dreamsongs.com/RiseOfWorseIsBetter.html"&gt;Worse is Better&lt;/a&gt; to me. While one could complicate Go's iterator interface to enforce stricter guarantees around safe iteration, doing so would add unnecessary complexity and detract from Go's philosophy of simplicity. &lt;/p&gt;
&lt;p&gt;It's one of the rare occasions where I wish interfaces had to be intentionally met. It's strange to me that anything that smells like an iterator can be passed to &lt;code&gt;range&lt;/code&gt;, even if it doesn't implement the intended handshake.&lt;/p&gt;
&lt;p&gt;It smells funny, but that's probably OK. It almost certainly falls into the case of &amp;quot;things that will never be a real problem in the real world&amp;quot;. I look forward to the oncoming brave new world of libraries expecting iterators.&lt;/p&gt;</description><author>Donat Studios</author><pubDate>Fri, 06 Sep 2024 02:28:12 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/Go-Iterators-Smell-Funny</guid></item><item><title>scc vs stto a code counter head to head</title><link>https://boyter.org/posts/scc-stto-head-to-head/</link><description>&lt;p&gt;A work colleague (Dazza!) messaged me this morning about a new code counter with some pretty serious performance claims. Not only was it claiming to be accurate it was claiming to have a 38x speed improvement over tools such as tokei!&lt;/p&gt;
&lt;p&gt;&lt;img alt="benchmark" src="https://boyter.org/static/scc-stto/IMG_2669.png" /&gt;&lt;/p&gt;
&lt;p&gt;Code counters are something I am interested in so I did some digging to find the repository &lt;a href="https://github.com/mainak55512/stto"&gt;https://github.com/mainak55512/stto&lt;/a&gt; Interesting it is written in Go.&lt;/p&gt;
&lt;p&gt;My first though about the times was that all it was measuring is the startup time of a binary in whatever it’s written in to Go. No surprise Go is slower as it has GC and a runtime. However this is not the case here. That said, improving the startup time of scc for times below 20 ms is something I never worried about. There are two main reasons for that, use and human perception.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 05 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/scc-stto-head-to-head/</guid></item><item><title>Breaking Bell's Inequality with Monte Carlo Simulations in Python</title><link>https://bytepawn.com/quantum-entanglement-bell-inequality-python.html</link><description>&lt;p&gt;The article explains the Bell inequality using Monte Carlo simulations in Python, and shows how non-local action-at-a-distance can be used to break it with entangled qubits.&lt;br /&gt;&lt;br /&gt; &lt;img alt="John Stewart Bell" src="/images/bell-game.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 01 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/quantum-entanglement-bell-inequality-python.html</guid></item><item><title>Two Rules of AI Business and Startups That Ignore Them</title><link>https://svedic.org/programming/two-rules-of-ai-business-and-startups-that-ignore-them</link><description>These rules are not new, and they are not mine; I stole them from Andrew Ng and Benedict Evans, two men with a huge following. Still, a large majority of AI entrepreneurs and engineers don’t pay attention to them, maybe &amp;#8230; &lt;a href="https://svedic.org/programming/two-rules-of-ai-business-and-startups-that-ignore-them"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Svedic.org</author><pubDate>Sat, 31 Aug 2024 16:23:15 GMT</pubDate><guid isPermaLink="true">https://svedic.org/programming/two-rules-of-ai-business-and-startups-that-ignore-them</guid></item><item><title>Evaluating your AI models in the wild</title><link>https://austinhenley.com/blog/aiinthewild.html</link><description>&lt;a href="https://austinhenley.com/blog/aiinthewild.html"&gt;https://austinhenley.com/blog/aiinthewild.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 25 Aug 2024 00:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/aiinthewild.html</guid></item><item><title>Obsession</title><link>http://notes.eatonphil.com/2024-08-24-obsession.html</link><description>&lt;p&gt;In your professional and personal life, I don't believe there is a stronger motivation than having something in mind and the desire to do it. Yet the natural way to deal with a desire to do something is to justify why it's not possible.&lt;/p&gt;
&lt;p&gt;"I want to read more books but nobody reads books these days so how could I."&lt;/p&gt;
&lt;p&gt;"I want to write for a magazine but I have no experience writing professionally."&lt;/p&gt;
&lt;p&gt;"I want to build a company someday but how could someone of my background."&lt;/p&gt;
&lt;p&gt;Our official mentors, our managers, through a combination of well-intentioned defeatism and well-intentioned lack of accomplishment themselves, among other things, are often unable to process big goals or guide you toward them.&lt;/p&gt;
&lt;p&gt;I've been one of these managers myself. In fact I have, to my immense regret, tried too often to convince people to do what is practical rather than what they want to do. Or to do what I judged they were capable of doing rather than what they wanted to do.&lt;/p&gt;
&lt;p&gt;In the best cases, my listener had the self-confidence to ignore me. They did what they wanted to do anyway. In the worst case, again to my deep regret, I've been a well-intentioned part of derailing someone's career for years.&lt;/p&gt;
&lt;p&gt;So I don't want to convince anyone of anything anymore. If I start trying to convince someone by accident, I try to catch myself. I try to avoid sentences like "I think you should …". Instead "Here is something that's worked for me: …" or "Here is what I've heard works well for other people: …".&lt;/p&gt;
&lt;p&gt;Nobody wants to be convinced. But intelligent people will change their mind when exposed to new facts or different ideas. Being convinced is a battle of will. Changing one's mind is a purely personal decision.&lt;/p&gt;
&lt;p&gt;There are certainly people with discipline who can grind on things they hate doing and eventually become experts at it. But more often I see people grind on things they hate only to become depressed and give up.&lt;/p&gt;
&lt;p&gt;For most of us, our best hope is (healthy) obsession. And obsession, in the sense I'm talking about, does not come from something you are ambivalent about or hate. Obsession can only come when you're doing something you actually want to do.&lt;/p&gt;
&lt;p&gt;For big goals or big changes, you need regular commitment weekly, monthly, yearly. Over the course of years. And only obsession makes that work not actually feel like work. Obsession is the only thing that makes discipline not feel like discipline.&lt;/p&gt;
&lt;p&gt;That big goals take years to accomplish need not be scary. Obsession doesn't mean you can't pivot. There is quite a lot to gain by committing to something regularly over the course of years even if you decide to stop and commit from then on to something else. You will learn a good deal.&lt;/p&gt;
&lt;p&gt;And healthy obsession to me is more specifically measurable on the order of weeks, not hours or days. Healthy obsession means you're still building healthy personal and professional relationships. You're still taking care of yourself, emotionally and physically.&lt;/p&gt;
&lt;p&gt;I do not have high expectations for people in general. This seems healthy and reasonable. But as I meet more people and observe them over the years, I am only more convinced of the vast potential of individuals. Individuals are almost universally underestimated.&lt;/p&gt;
&lt;p&gt;I think you can do almost anything you want to do. If you commit to do doing it.&lt;/p&gt;
&lt;p&gt;I'll end this with a personal story.&lt;/p&gt;
&lt;p&gt;Until 11th grade, I hated school. I hated the rigidity. Being forced to be somewhere for hours and to follow so many rules. I skipped so many days of school I'm embarrassed by it. I'd never do homework at home. I never studied for tests. I got Bs and Cs in the second-tier classes. I was in the orchestra for 6 years and never practiced at home. I was not cool enough to be a "bad kid" but I did not understand the system and had no discipline whatsoever.&lt;/p&gt;
&lt;p&gt;I found out at the end of 10th grade that I could actually afford college if I got into a good enough school that paid full needs-based tuition. It sounded significantly better than the only other option that seemed obvious, joining the military as a recruit. I realized and decided that if I wanted to get into a good school I needed to not half-ass things.&lt;/p&gt;
&lt;p&gt;Somehow, I decided to only do things I could become obsessed with. And I decided to be obsessed in the way that I wanted, not to do what everyone else did (which I basically could not do since I had no discipline). If we covered a topic in class, I'd read news about it or watch movies about it. I'd get myself excited about the topic in every way I could.&lt;/p&gt;
&lt;p&gt;It basically worked out. I ended high school in the top 10% of the class (up from top 40% or something). I got into a good liberal arts college that paid the entirety of my tuition. But I remained a basically lazy and undisciplined person. I never stayed up late studying for a test. I dropped out after a year and a half for family reasons.&lt;/p&gt;
&lt;p&gt;But I've now spent the last 10 years in my spare time working on compiler projects, interpreter projects, parser projects, database projects, distributed systems projects. I've spent the last 6 years consistently publishing at least one blog post per month.&lt;/p&gt;
&lt;p&gt;I didn't want to work the way everyone else worked. I wanted to be obsessed about what I worked on.&lt;/p&gt;
&lt;p&gt;Obsession has made all of this into something I now barely register as doing. It's allowed me to continue adding activities like organizing book clubs and meetups to the list of things I'm up to. Up until basically this year I could have in good faith said I am a very lazy and undisciplined person. But obsession turned me into someone with discipline.&lt;/p&gt;
&lt;p&gt;Obsession became about more than just the tech. It meant trying to fully understand the product, the users, the market. It meant thinking more carefully about product documentation, user interfaces, company messaging. Obsession meant reflecting on how I treat my coworkers, and how my coworkers feel treated by others in general. Obsession meant wanting an equitable and encouraging work environment for everyone.&lt;/p&gt;
&lt;p&gt;And, as I said, it's about healthy obsession. I didn't really understand the "healthy" part until a few years ago. But I'm now convinced that the "healthy" part is as important as the "obsession" part. To go to the gym regularly. To play pickup volleyball. To cook excellent food. To read fiction and poetry and play music. To serve the community. To be friendly and encouraging to all people. To meet new people and build better genuine friendships.&lt;/p&gt;
&lt;p&gt;And in the context of work, "healthy obsession" means understanding you can't do everything, even while you care about everything. It means accepting that you make mistakes and that you do your best; that you try to do better and learn from mistakes the next time.&lt;/p&gt;
&lt;p&gt;It's got to be sustainable. And we can develop a healthy obsession while we have quite a bit of fun too. :)&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote an essay on my mistakes trying to convince people to do something, on doing what you want to do, and on obsession.&lt;br /&gt;&lt;br /&gt;Ended with a personal note on developing healthy discipline, and having fun. :)&lt;a href="https://t.co/4WWdtU6AhL"&gt;https://t.co/4WWdtU6AhL&lt;/a&gt; &lt;a href="https://t.co/lBw7zlqWeq"&gt;pic.twitter.com/lBw7zlqWeq&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1827373730781147241?ref_src=twsrc%5Etfw"&gt;August 24, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sat, 24 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-08-24-obsession.html</guid></item><item><title>Objective-C is just, like, a leaky abstraction over C</title><link>https://blog.metaobject.com/2024/08/objective-c-is-just-like-leaky.html</link><description>Recently I came across this 10 year old post by Robert Atkins: &lt;a href="https://frabjousdei.net/post/64096448121/objective-c-is-like-jimi-hendrix"&gt;Objective C is like Jimi Hendrix&lt;/a&gt;. It is about reconciling admiration for Objective-C by the "old-timers"  with the newcomers' somewhat less enthusiastic response.&lt;p&gt;

His very cogent insight was that Objective-C, like Jimi Hendrix, introduced new concepts that were somewhat revolutionary (""mind blowing") at the time, but are now taken for granted:

&lt;blockquote&gt;
So if you'e new to Objective-C and, as I am, struggling to come to terms with the fact that it's one great big leaky abstraction on top of C, put yourself in the shoes of an 80s C programmer and remember you get to use these neat "modern" features in a systems programming language.
&lt;/blockquote&gt;

As one of the very early, pre-NeXT, adopters of Objective-C, I have a slightly different take:
That implicit "despite" is actually very much a "because" for me.&lt;p&gt;

The modern features in Objective-C such as a dynamic messaging, a runtime with introspection and intercession etc. were not new at the time, and they were not really "mind-blowing".  LISP and Smalltalk had had them for a long time.  But so far having these features had required large, complex runtime environments that were very distant from the rest of the machine, and usually quite isolated from the rest of the machine.  Still are, to this day.  You don't program your computer with LISP or Smalltalk.  Your computer runs a separate LISP or Smalltalk computer that you can then program on its own terms and in its own world.  (And attempts to bring machines closer to these languages mostly did not succeed).&lt;p&gt;

Objective-C provided these features with the slimmest of a sliver of an extension to a portable PDP-11 macro assembler.&lt;p&gt;

Now &lt;em&gt;that&lt;/em&gt; was mind-blowing!&lt;p&gt;


And it goes beyond that:  there actually was, at some point, Objective-Assembler.  Objective-C was never intended to be "a language" like Java or Rust. The Objective part of Objective-C is a glue layer, an integration mechanism that can be added to any language.  There was Objective-FORTRAN, Objective-Pascal etc.  Helge Hess once put it almost perfectly:  Objective-C is COM with language support.  Or SOM. Or whatever interop mechanism they come up with again (Swift "library evolution", I am looking at you, can we have Objective-Swift?).&lt;p&gt;

So it wasn't just mind-blowing, it also was, is, and remains incredibly useful. &lt;p&gt;

So useful, in fact, that this slimmest sliver of an extension gradually replaced the portable PDP-11 macro assembler it was sitting on top of for most day-to-day use. So much so, that in the Apple developer ecosystem, the much larger C part started to be regarded as a completely separate language that most devs never dared touch.&lt;p&gt;

Of course the fact that this is doable shouldn't be surprising, after all the Objective part is modeled after Smalltalk, and Smalltalk is a complete programming language.  But Smalltalk requires a fairly large VM to run, typically coded in C or in Squeak's case, C plus BCPL-encoded-in-Smalltalk that gets automatically translated to C.  Objective-C and Objective-Assembler showed that you don't actually need all that, just a tiny messaging function on top of bare machine is not just sufficient, it's also faster, integrates better and is easier to implement.  Less is indeed more.&lt;p&gt;

All that doesn't mean that Objective-C isn't an ad-hoc car crash of two languages with overlapping functionality and syntax, and the safety of C's memory model and Smalltalk's type system.  That this disaster area works better in practice than most other languages tells you all you need to know about the state of PL design.&lt;p&gt;

A more principled, if irreverent, exploration of the same approach can be found in Ian Piumarta's &lt;a href="https://www.piumarta.com/papers/colas-whitepaper.pdf"&gt;COLA&lt;/a&gt;  (COmbined Lambda Architecture). He basically discovered the same principles, and came up with something that's even cooler, though at this point less practical. &lt;p&gt;

My favorite bit (it's a tight field) is how he manages to build a message-dispatcher that is itself invoked by message-send.  How does he resolve the infinite recursion inherent in the self-referential definition?  By pre-populating a single cache.&lt;p&gt;

Ian is also a really great speaker:&lt;p&gt;



Alas this video of his Stanford EE380 &lt;a href="https://web.stanford.edu/class/ee380/Abstracts/070214.html"&gt;talk&lt;/a&gt; is only 240p, so the text is illegible, but fortunately there are &lt;a href="http://piumarta.com/papers/EE380-2007-slides.pdf"&gt;slides&lt;/a&gt; that you can follow along.&lt;p&gt;

In Objective-S, the procedural/OO part is really based on just that sliver of an extension.  The C part is removed (yes, a real "Objective-C without the C"), replaced by a few type declarations for indicating primitive types, special stores for raw memory access when needed and a generalization of message-sending that subsumes calling C functions.&lt;p&gt;</description><author>metablog</author><pubDate>Thu, 22 Aug 2024 10:54:10 GMT</pubDate><guid isPermaLink="true">https://blog.metaobject.com/2024/08/objective-c-is-just-like-leaky.html</guid></item><item><title>Vim Reference Guide book announcement</title><link>https://learnbyexample.github.io/vim-reference-guide-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;Vim Reference Guide&lt;/strong&gt; ebook. This is intended as a concise learning resource for beginner to intermediate level Vim users. It has more in common with cheatsheets than a typical text book. Topics like Regular Expressions and Macros have more detailed explanations and examples due to their complexity. I hope this guide would make it much easier for you to discover Vim features and learning resources than my own blundering experience.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 31-Aug-2024. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/vim_reference_guide"&gt;https://learnbyexample.gumroad.com/l/vim_reference_guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/vim_reference_guide/c/new_vim_release"&gt;https://leanpub.com/vim_reference_guide/c/new_vim_release&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two of my bundles are on sale as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_vim_release"&gt;All Books Bundle&lt;/a&gt; is $15 (normal price $32) — all my 13 programming ebooks&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/new_vim_release"&gt;Linux CLI Text Processing&lt;/a&gt; is 50% OFF — grep, sed, awk, perl and ruby one-liners, coreutils, cli computing&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Updated ebook for &lt;strong&gt;Vim version 9.1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Corrected typos&lt;/li&gt;
&lt;li&gt;Some of the examples, descriptions and external links were updated&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;Visit &lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos"&gt;this playlist&lt;/a&gt; for video demos on most of the topics from the ebook.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Got several suggestions and feedback when &lt;a href="https://news.ycombinator.com/item?id=30684232"&gt;my submission about this book&lt;/a&gt; reached the front page of Hacker News.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Great job on this! — rendall&lt;/p&gt;
&lt;p&gt;Hi, great work releasing this! Trying to explain vim concisely is always an interesting challenge and I had a great time reading your attempt in this book. I always find it really interesting on how people try to group certain vim functions in a way that makes sense to people that don't use vim. I think you cover that idea pretty well in your 'Vim philosophy and features' section whilst not making it overly abstract and keeping it relatable. — doix&lt;/p&gt;
&lt;p&gt;Neat stuff! One piece of feedback is that I would include &amp;quot;+p and &amp;quot;+yy in the copy and paste section. — mrpotato&lt;/p&gt;
&lt;p&gt;I learnt regular expression by reading your books, thank you for the great work. — LamJH&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A comment from another &lt;a href="https://news.ycombinator.com/item?id=31931804"&gt;Hacker News thread&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I stumbled upon your vi post a few days ago, really like the style. Keep it up!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="vim-prank"&gt;Vim prank&lt;a class="zola-anchor" href="#vim-prank"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Did you know that Vim has an &lt;em&gt;easy&lt;/em&gt; mode? It can be rather hard to use for those already familiar with Vim modes. I wrote a &lt;a href="https://learnbyexample.github.io/mini/vim-prank/"&gt;blog post&lt;/a&gt; about this mode, which was interesting enough to reach the front page of Hacker News!&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Insert mode&lt;/li&gt;
&lt;li&gt;Normal mode&lt;/li&gt;
&lt;li&gt;Command-line mode&lt;/li&gt;
&lt;li&gt;Visual mode&lt;/li&gt;
&lt;li&gt;Regular Expressions&lt;/li&gt;
&lt;li&gt;Macro&lt;/li&gt;
&lt;li&gt;Customizing Vim&lt;/li&gt;
&lt;li&gt;CLI options&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/vim_reference/"&gt;https://learnbyexample.github.io/vim_reference/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;https://github.com/learnbyexample/vim_reference&lt;/a&gt; for markdown source and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/vim_reference/issues"&gt;https://github.com/learnbyexample/vim_reference/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 20 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/vim-reference-guide-announcement/</guid></item><item><title>What's the big deal about Deterministic Simulation Testing?</title><link>http://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html</link><description>&lt;p&gt;Bugs in distributed systems are hard to find, largely because systems
interact in chaotic ways. And even once you've found a bug, it can be
anywhere from simple to impossible to reproduce it. It's about as far
away as you can get from the ideal test environment: property testing
a pure function.&lt;/p&gt;
&lt;p&gt;But what if we could write our code in a way that we can isolate the
chaotic aspects of our distributed system during &lt;i&gt;testing&lt;/i&gt;: run
multiple systems communicating with each other on a &lt;i&gt;single
thread&lt;/i&gt; and control all randomness in each system? And property
test this single-threaded version of the distributed system with
controlled randomness, all the while injecting faults (fancy term for
unhappy path behavior like errors and latency) we might see in the
real-world?&lt;/p&gt;
&lt;p&gt;Crazy as it sounds, people actually do this. It's called Deterministic
Simulation Testing (DST). And it's become more and more popular with
startups like FoundationDB, Antithesis, TigerBeetle, Polar Signals,
and WarpStream; as well as folks like Tyler Neely and Pekka Enberg,
talking about and making use of this technique.&lt;/p&gt;
&lt;p&gt;It has become so popular to talk about DST in my corner of the world
that I worry it risks coming off sounding too magical and maybe a
little hyped. It's worth getting a better understanding of both the
benefits and the limitations.&lt;/p&gt;
&lt;p&gt;Thank you to &lt;a href="https://www.linkedin.com/in/alexmillerdb/"&gt;Alex Miller&lt;/a&gt;
and &lt;a href="https://www.linkedin.com/in/will-wilson-330276112/"&gt;Will Wilson&lt;/a&gt;
for reviewing a version of this post.&lt;/p&gt;
&lt;h3 id="randomness-and-time"&gt;Randomness and time&lt;/h3&gt;&lt;p&gt;A big source of non-determinism in business logic is the use of random
numbers—in your code or your transitive dependencies or your language
runtime or your operating system.&lt;/p&gt;
&lt;p&gt;Crucially, DST does not imply you can't have randomness! DST merely
assumes that you have a global seed for all randomness in your program
and that the simulator controls the seed. The seed may change across
runs of the simulator.&lt;/p&gt;
&lt;p&gt;Once you observe a bad state as a result of running the simulation on
a random seed, you allow the user to enter the same seed again. This
allows the user to recreate the entire program run that led to that
observed bad state. Allows the user to debug the program trivially.&lt;/p&gt;
&lt;p&gt;Another big source of non-determinism is being dependent on time. As
with randomness, DST does not mean you can't depend on time. DST means
you must be able to control the clock during the simulation.&lt;/p&gt;
&lt;p&gt;To "control" randomness or time basically means you support dependency
injection, or the old-school alternative to dependency injection
called &lt;i&gt;passing the dependency as an explicit parameter&lt;/i&gt;. Rather
than referring to a global clock or a global seed, you need to be able
to receive a clock or a seed from someone.&lt;/p&gt;
&lt;p&gt;For example we might separate the operation of an application into the
language's &lt;code&gt;main()&lt;/code&gt; entrypoint and an actual application &lt;code&gt;start()&lt;/code&gt;
entrypoint.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app.pseudocode&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# lots of business logic that might depend on time or do random things&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The application entrypoint is where we must be able to
swap out a real clock or real random seed for one controlled by our
simulator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# sim.pseudocode&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;app.pseudocode&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;sim_clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_sim_clock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;sim_seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sim_seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Bad execution at seed: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sim_seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's look at another example.&lt;/p&gt;
&lt;h3 id="converting-an-existing-function"&gt;Converting an existing function&lt;/h3&gt;&lt;p&gt;Let's say that we had a helper method that kept calling a function
until it succeeded, with backoff.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# retry.pseudocode&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is a single source of nondeterminism here and it's where we
generate a seed. We could parameterize the seed, but since we want to
call &lt;code&gt;time.sleep()&lt;/code&gt; and since in DST we control the time, we can just
parameterize &lt;code&gt;time&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# retry.psuedocode&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can write a little simulator to test this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# sim.psuedocode&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;retry.pseudocode&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;sim_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;sim_time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;promise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retry_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;sim_time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="n"&gt;assert_expect_failure_and_expected_time_elapse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Found logical error with seed: &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This demonstrates a few critical aspects of DST. First, the simulator
itself depends on randomness. But allows the user to provide a seed so
they can replay a simulation that discovers a bug. The controlled
randomness in the simulator is what lets us do property testing.&lt;/p&gt;
&lt;p&gt;Second, the simulation workload must be written by the user. Even when
you've got a platform like Antithesis that gives you an environment
for DST, it's up to you to exercise the application.&lt;/p&gt;
&lt;p&gt;Now let's get a little more complex.&lt;/p&gt;
&lt;h3 id="a-single-thread-and-asynchronous-io"&gt;A single thread and asynchronous IO&lt;/h3&gt;&lt;p&gt;The determinism of multiple threads can only be controlled at the
operating system or emulator or hypervisor layer. Realistically, that
would require third-party systems like Antithesis or
&lt;a href="https://github.com/facebookexperimental/hermit"&gt;Hermit&lt;/a&gt; (which, don't
get excited, is not actively developed and hasn't worked on any
interesting program of mine) or &lt;a href="https://rr-project.org/"&gt;rr&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These systems transparently transform multi-threaded code into single
threaded code. But also note that Hermit and rr have only limited
ability to do fault injection which, in addition to deterministic
execution, is a goal of ours. And you can't run them on a mac. And
&lt;a href="https://github.com/rr-debugger/rr/issues/1373"&gt;can't&lt;/a&gt;
&lt;a href="https://github.com/facebookexperimental/hermit?tab=readme-ov-file#support"&gt;run&lt;/a&gt;
them on ARM.&lt;/p&gt;
&lt;p&gt;But we can, and would like, to write a simulator without writing a new
operating system or emulator or hypervisor, and without a third-party
system. So we must limit ourselves to writing code that can be
collapsed into a single thread. Significantly, since using blocking IO
would mean an entire class of concurrency bugs could not be discovered
while running the simulator in a single thread, we must limit
ourselves to asynchronous IO.&lt;/p&gt;
&lt;p&gt;Single threaded and asynchronous IO. These are already two big limitations.&lt;/p&gt;
&lt;p&gt;Some languages like Go are entirely built around transparent
multi-threading and blocking IO. Polar Signals
&lt;a href="https://www.polarsignals.com/blog/posts/2024/05/28/mostly-dst-in-go"&gt;solved&lt;/a&gt;
this for DST by compiling their application to WASM where it would run
on a single thread. But that wasn't enough. Even on a single thread,
the Go runtime intentionally schedules goroutines randomly. So Polar
Signals forked the Go runtime to control this randomness with an
environment variable. That's kind of crazy. Resonate took &lt;a href="https://github.com/resonatehq/resonate/blob/268c588e302f13187309e4b37636d19595d42fa1/internal/kernel/scheduler/coroutine.go"&gt;another
approach&lt;/a&gt;
that also looks cumbersome. I'm not going to attempt to describe
it. Go seems like a difficult choice of a language if you want to do
DST.&lt;/p&gt;
&lt;p&gt;Like Go, Rust has no builtin async IO. The most mature async IO
library is tokio. The tokio folks attempted to provide a
tokio-compatible &lt;a href="https://github.com/tokio-rs/simulator"&gt;simulator&lt;/a&gt;
implementation with all sources of nondeterminism removed. From what I
can tell, they did not at any point fully
&lt;a href="https://github.com/tokio-rs/tokio/issues/1845"&gt;succeed&lt;/a&gt;. That repo
has now been replaced with a "this is very experimental" tokio-rs
project called &lt;a href="https://github.com/tokio-rs/turmoil"&gt;turmoil&lt;/a&gt; that
provides deterministic execution plus network fault injection. (But
not disk fault injection. More on that later.) It isn't surprising
that it is difficult to provide deterministic execution for an IO
library that was not designed for it. tokio is a large project with
many transitive dependencies. They must all be combed for
non-determinism.&lt;/p&gt;
&lt;p&gt;On the other hand, Pekka has &lt;a href="https://github.com/penberg/hiisi/blob/main/hiisi-server/src/io/generic.rs"&gt;already
demonstrated&lt;/a&gt;
for us how we might build a simpler Rust async IO library that is
designed to be simulation tested. He modeled this on the TigerBeetle
design King and I
&lt;a href="https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue"&gt;wrote&lt;/a&gt;
about two years ago.&lt;/p&gt;
&lt;p&gt;So let's sketch out a program that does buggy IO and let's look at how
we can apply DST to it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# readfile.pseudocode&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;read_buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_maybe_allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;

    &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_maybe_allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our simulator, we will provide a mocked out IO system and we will
randomly inject various errors while asserting pre- and
post-conditions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# sim.psuedocode&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;readfile.pseudocode&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;sim_disk_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sim_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eof&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;partial_read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_in_range_inclusive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_disk_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partial_read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;partial_read&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_disk_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partial_read&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;partial_read&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;sim_io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sim_fd&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;out_buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;somefile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_bytes_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sim_disk_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Found logical error with seed: &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And with this simulator we would have eventually caught our partial
read bug! In our original program when we wrote:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;      &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_maybe_allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We should have written:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;      &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_maybe_allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;n_read&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Great! Let's get a little more complex.&lt;/p&gt;
&lt;h3 id="a-distributed-system"&gt;A distributed system&lt;/h3&gt;&lt;p&gt;I already mentioned in the beginning that the gist of deterministic
simulation testing a distributed system is that you get all of the
nodes in the system to run in the same process. This would be
basically impossible if you wanted to test a system that involved your
application plus Kafka plus Postgres plus Redis. But if your system is
a self-contained distributed system, such as one that embeds a Raft
library for high availability of your application, you can actually
run multiple nodes into the same process!&lt;/p&gt;
&lt;p&gt;For a system like this, our simulator might look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# sim.pseudocode&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;distsys-node.pseudocode&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;sim_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# Inject random failure.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bad write'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# Inject random latency.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

      &lt;span class="n"&gt;n_written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assert_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n_written&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# Inject random failure.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bad read'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# Inject random latency.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;sim_io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# Inject random failure.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bad open'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# Inject random latency.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sim_fd&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;all_ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6002&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;distsys&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;distsys&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;distsys&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_in_range_inclusive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;assert_valid_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Crash a process every so often&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_in_range_inclusive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Found logical error with seed: &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I'm completely hand waving here to demonstrate the broader point and
not any specific testing strategy for a specific distributed
system. The important points are that these three nodes run in the
same process, on different ports.&lt;/p&gt;
&lt;p&gt;We control disk IO. We control network IO. We control how time
elapses. We run a deterministic simulated workload against the three
node system while injecting disk, network, and process faults.&lt;/p&gt;
&lt;p&gt;And we are constantly checking for an invalid state. When we get the
invalid state, we can be sure the user can easily recreate this
invalid state.&lt;/p&gt;
&lt;h3 id="other-sources-of-non-determinism"&gt;Other sources of non-determinism&lt;/h3&gt;&lt;p&gt;Within some error margin, most CPU instructions and most CPU behavior are
considered to be deterministic. There are, however, certain CPU
instructions that are &lt;a href="https://cs.stackexchange.com/questions/132842/under-which-conditions-a-given-program-is-deterministic-on-x86-64-machines/132856#132856"&gt;definitely
not&lt;/a&gt;. Unfortunately
that might
&lt;a href="https://github.com/facebookexperimental/hermit/issues/34"&gt;include&lt;/a&gt;
system calls. It might also
&lt;a href="https://stackoverflow.com/a/8171032"&gt;include&lt;/a&gt; malloc. There is very
little to trust.&lt;/p&gt;
&lt;p&gt;If we &lt;a href="https://antithesis.com/blog/deterministic_hypervisor/"&gt;ignore&lt;/a&gt;
Antithesis, people doing DST seem not to worry about these smaller
bits of nondeterminism. Yet it's generally agreed that DST is still
worthwhile anyway. The intuition here is that every bit of
non-determinism you can eliminate makes it that much easier to
reproduce bugs when you find them.&lt;/p&gt;
&lt;p&gt;Put another way: determinism, even among DST practitioners, remains a spectrum.&lt;/p&gt;
&lt;h3 id="considerations"&gt;Considerations&lt;/h3&gt;&lt;p&gt;As you may have noticed already from some of the pseudocode, DST is not a panacea.&lt;/p&gt;
&lt;h4 id="consideration-1:-edges"&gt;Consideration 1: Edges&lt;/h4&gt;&lt;p&gt;First, because you must swap out non-deterministic parts of your code,
you are not actually testing the entirety of your code. You are
certainly encouraged to keep the deterministic kernel large. But there
will always be the non-deterministic edges.&lt;/p&gt;
&lt;p&gt;Without a system like Antithesis which gives you an entire
deterministic machine, you can't test your whole program.&lt;/p&gt;
&lt;p&gt;But even with Antithesis you cannot test the &lt;i&gt;integration&lt;/i&gt; between your
system and external systems. You must mock out the external systems.&lt;/p&gt;
&lt;p&gt;It's also worth noting that there are many areas where you could
inject simulation. You could do it at a high-level RPC and storage
layer. This would be simpler and easier to understand. But then you'd
be omitting testing and error-handling of lower-level errors.&lt;/p&gt;
&lt;h4 id="consideration-2:-your-workload(s)"&gt;Consideration 2: Your workload(s)&lt;/h4&gt;&lt;p&gt;DST is dependent on your creativity and thoroughness of your workload
as much as any other type of test or benchmark.&lt;/p&gt;
&lt;p&gt;Just as you wouldn't depend on one single benchmark to qualify your
application, you may not want to depend on a single simulated
workload.&lt;/p&gt;
&lt;p&gt;Or as Will Wilson put it for me:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The biggest challenge of DST in my experience is that tuning all the
random distributions, the parameters of your system, the workload,
the fault injection, etc. so that it produces interesting behavior
is very challenging and very labor intensive. As with fuzzing or
PBT, it's terrifyingly easy to build a DST system that appears to be
doing a ton of testing, but actually never explores very much of the
state space of your system. At FoundationDB, the vast majority of
the work we put into the simulator was an iterative process of
hunting for what wasn't being covered by our tests and then figuring
out how to make the tests better. This process often resembles
science more than it does engineering.&lt;/p&gt;
&lt;p&gt;Unfortunately, unlike with fuzzing, mere branch coverage in your
code is usually a pretty poor signal for the kinds of systems you
want to test with DST. At Antithesis we handle this with &lt;a href="https://antithesis.com/docs/best_practices/sometimes_assertions.html"&gt;Sometimes
assertions&lt;/a&gt;, at FDB we did something pretty similar, and I assume
TigerBeetle and others have their own version of this. But of course
the ultimate figure of merit is whether your DST system is finding
100% of your bugs. It's quite difficult to get to the point that it
does. The truly ambitious part of Antithesis isn't the hypervisor,
but the fact that we also aim to solve the much harder "is my DST
working?" problem with minimal human guidance or supervision.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="consideration-3:-your-knowledge-of-what-you-mocked"&gt;Consideration 3: Your knowledge of what you mocked&lt;/h4&gt;&lt;p&gt;When you mock out the behavior of disk or network IO, the benefits of
DST are tied to your understanding of the spectrum of behavior that
may happen in the real world.&lt;/p&gt;
&lt;p&gt;What are all possible error conditions? What are the extreme latency
bounds of the original method? What about corruption or misdirected
IO?&lt;/p&gt;
&lt;p&gt;The flipside here is that only in deterministic simulation testing can
you configure these crazy scenarios to happen at a &lt;i&gt;configurable
regularity&lt;/i&gt;. You can kick off a set of runs that have especially
high IO latency or especially high corrupt reads/writes. Joran and I
&lt;a href="https://tigerbeetle.com/blog/2023-07-11-we-put-a-distributed-database-in-the-browser"&gt;wrote&lt;/a&gt;
a year ago about how the TigerBeetle simulator does exactly this.&lt;/p&gt;
&lt;h4 id="consideration-4:-non-reproducible-seeds-as-code-changes"&gt;Consideration 4: Non-reproducible seeds as code changes&lt;/h4&gt;&lt;p&gt;Critically, the reproducibility of DST only helps so long as your &lt;i&gt;code
doesn't change&lt;/i&gt;. As soon as your code changes, the seed may no longer
even get you to the state where the bug was exhibited. So the
reproducibility of DST means more that it may help you convert the
seed simulation run into an integration test that describes the
precise scenario even as the code changes.&lt;/p&gt;
&lt;h4 id="consideration-5:-time-and-compute"&gt;Consideration 5: Time and compute&lt;/h4&gt;&lt;p&gt;Because of Consideration 4, you need to keep rerunning the simulator
not just to keep finding new seeds and new histories but because the
new seeds and new histories may change every time you make changes to
code.&lt;/p&gt;
&lt;h3 id="what-about-jepsen?"&gt;What about Jepsen?&lt;/h3&gt;&lt;p&gt;Jepsen does limited process and network fault injection while testing
for linearizability. It's a fantastic project.&lt;/p&gt;
&lt;p&gt;However, it represents only a subset of what is possible with
Deterministic Simulation Testing (if you actually put in the effort
described above to get there).&lt;/p&gt;
&lt;p&gt;But even more importantly, Jepsen has nothing to do with deterministic
execution. If Jepsen finds a bug and your system can't do
deterministic execution, you may or may not be able to reproduce that
Jepsen bug.&lt;/p&gt;
&lt;p&gt;Here's another Will Wilson
&lt;a href="https://antithesis.com/blog/is_something_bugging_you/"&gt;quote&lt;/a&gt; for you
on Jepsen and FoundationDB:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Anyway, we did [Deterministic Simulation Testing] for a while and
found all of the bugs in the database. I know, I know, that’s an
insane thing to say. It’s kind of true though. In the entire history
of the company, I think we only ever had one or two bugs reported by
a customer. Ever. Kyle Kingsbury aka “aphyr” didn’t even bother
testing it with Jepsen, because he didn’t think he’d find anything.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;The degree to which you can place faith in DST alone, and not time
spent in production, has limits. However, it certainly does no harm to
employ DST. And, barring the considerations described above, will
likely make the kernel of your product significantly more
stable. Furthermore, everyone who uses DST knows about these
considerations. But I think it's worthwhile to list them out to help
folks who do not know DST to build an intuition for what it's
excellent at.&lt;/p&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4fFDFbi3toc"&gt;"Testing Distributed Systems w/ Deterministic Simulation" by Will Wilson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.polarsignals.com/blog/posts/2024/05/28/mostly-dst-in-go"&gt;(Mostly) Deterministic Simulation Testing in Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/madsim-rs/madsim"&gt;Magical Deterministic Simulator for distributed systems in Rust&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a new post talking through the basics, considerations, and limitations of Deterministic Simulation Testing.&lt;a href="https://t.co/9Fp5ytL7Wz"&gt;https://t.co/9Fp5ytL7Wz&lt;/a&gt; &lt;a href="https://t.co/xRE6FOwc0P"&gt;pic.twitter.com/xRE6FOwc0P&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1825851204632445377?ref_src=twsrc%5Etfw"&gt;August 20, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 20 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html</guid></item><item><title>2024 Aug Quick Summary</title><link>https://jodavaho.io/now/2024-aug.html</link><description>&lt;p&gt;I&amp;rsquo;m lucky to have found a new job that is essentially my dream position. I&amp;rsquo;m
now a full-time Rust programmer, but still working on a full planning stack.
It&amp;rsquo;s a very small startup, and I&amp;rsquo;m completely stoked.&lt;/p&gt;
&lt;p&gt;This summer has been beautiful, and we&amp;rsquo;ve been lucky to have a lot of time with family and friends.&lt;/p&gt;
&lt;p&gt;My only side project at the moment is a Cyberpunk-like rule system for a
tabletop RPG, which is heavily inspired by the old R Talsorian Cyberpunk 2020
rules, and is designed so that the mechanics directly affect roleplay and
player decisions, and any player decision should play out with the mechanics.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/now/2024-aug.html</guid></item><item><title>Quantum Entanglement, Locality, Determinism</title><link>https://bytepawn.com/quantum-entanglement-locality-determinism.html</link><description>&lt;p&gt;This blog post explores foundational quantum mechanics concepts such as superposition, entanglement, and the challenges posed by locality and determinism, while highlighting the historical efforts of Einstein and Bohm to reconcile these phenomena with hidden variable theories.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Double slit experiment" src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/Double-slit.svg/800px-Double-slit.svg.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 18 Aug 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/quantum-entanglement-locality-determinism.html</guid></item><item><title>Ranking the Hugo and Nebula Award Winners and Nominees</title><link>https://blog.nawaz.org/posts/2024/Aug/ranking-the-hugo-and-nebula-award-winners-and-nominees/</link><description>&lt;p&gt;I grew up reading science fiction. Although I enjoyed the genre a great
deal, most of my reading was confined to Isaac Asimov and Arthur C.
Clarke (with a dash of Ray&amp;nbsp;Bradbury).&lt;/p&gt;
&lt;p&gt;By the end of my college years, I had read most of the fiction from
those two …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Mon, 12 Aug 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2024/Aug/ranking-the-hugo-and-nebula-award-winners-and-nominees/</guid></item><item><title>Using ESPHome Without the Home Assistant Addon</title><link>https://www.petekeen.net/esphome-without-the-addon/</link><description>&lt;p&gt;The &amp;quot;blessed&amp;quot; flow for using ESPHome is the Home Assistant ESPHome Addon.
        This works well!
        It has a nice editor and it takes care of some housekeeping tasks for you.
        If you don't already have a comfortable development workflow it's a very nice way to start.&lt;/p&gt;
        &lt;p&gt;If you &lt;em&gt;do&lt;/em&gt; already have a working style that doesn't involve a web UI and a browser editor you can still use ESPHome, you just have to handle those housekeeping tasks yourself.&lt;/p&gt;
        &lt;h2 id="fundamentals" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-without-the-addon/#fundamentals"&gt;Fundamentals&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;I think it can be helpful to step back and take a look at the fundamentals of a piece of software like ESPHome before diving headlong into the deep pool of non-standard workflows.&lt;/p&gt;
        &lt;p&gt;At it's base level ESPHome is a microcontroller firmware generator.
        That is, it reads your YAML config, generates a bunch of C++ files and config files, and then using a compiler and some helper programs it generates a binary program that your microcontroller (usually but not always an ESP32) can run.&lt;/p&gt;
        &lt;p&gt;ESPHome also has a few very useful helpers.
        First, it can do seamless over the air (OTA) updates once any ESPHome firmware has been installed on a device.&lt;/p&gt;
        &lt;p&gt;Second, it has a pretty powerful web-based UI and configuration editor.&lt;/p&gt;
        &lt;p&gt;Third, ESPHome ships with a &amp;quot;native&amp;quot; binary protocol it can use to talk to Home Assistant (&lt;a href="https://www.petekeen.net/esphome-server-in-python"&gt;previously&lt;/a&gt;) complete with Noise-based symmetric key encryption.&lt;/p&gt;
        &lt;p&gt;Lastly, it can be used as a Home Assistant addon, which as I said earlier takes care of a few things for you.
        The OTA update functionality requires a pre-shared key to validate that updates are coming from a known source.
        The addon takes care of generating that and the Noise secret and sharing these keys with Home Assistant so you don't have to care about them.&lt;/p&gt;
        &lt;h2 id="esphome-on-a-macbook%3F!" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-without-the-addon/#esphome-on-a-macbook%3F!"&gt;ESPHome on a Macbook?!&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;My ESPHome workflow doesn't involve the web UI or the addon at all.
        Instead, I install ESPHome on my Macbook with &lt;code&gt;homebrew&lt;/code&gt; and manage the OTA and HA secret keys with 1Password and a small helper script.
        The script and all of my ESPHome configs live in &lt;a href="https://github.com/keenfamily-us/esphome-configs/tree/main"&gt;this public GitHub repo&lt;/a&gt;.&lt;/p&gt;
        &lt;p&gt;This is the script as it exists today:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-bash"&gt;#!/bin/bash
        
        set -x
        set -eo pipefail
        
        trap &amp;quot;rm common/device_base.yaml&amp;quot; EXIT
        
        op inject --in-file common/device_base.yaml.in --out-file common/device_base.yaml
        
        command=$1
        shift
        
        if [ $# -eq 0 ]; then
        configs=&amp;quot;*.yaml&amp;quot;
        else
        configs=&amp;quot;$@&amp;quot;
        fi
        
        esphome $command $configs
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;All this is really doing is using 1Password's &lt;code&gt;op inject&lt;/code&gt; tool to generate a file with my configured secrets, runs &lt;code&gt;esphome&lt;/code&gt;, and makes sure to clean up the generated file with that &lt;code&gt;trap&lt;/code&gt; line.
        The top of &lt;code&gt;device_base.yaml.in&lt;/code&gt; looks like this:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-yaml"&gt;substitutions:
        wifi_ssid: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_WIFI_SSID&amp;quot;
        wifi_password: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_WIFI_PASSWORD&amp;quot;
        fallback_ssid_password: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_FALLBACK_SSID_PASSWORD&amp;quot;
        home_assistant_encryption_key: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_HOME_ASSISTANT_ENCRYPTION_KEY&amp;quot;
        ota_password: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_OTA_PASSWORD&amp;quot;
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;All of those are just &lt;code&gt;text&lt;/code&gt; entries in the &lt;code&gt;ESPHome Secrets&lt;/code&gt; rich text item, but again they can be whatever you want.
        If you decide to make them &lt;code&gt;password&lt;/code&gt; type entries I believe you'd need to add &lt;code&gt;--reveal&lt;/code&gt; to the &lt;code&gt;op inject&lt;/code&gt; command, but I'm not 100% certain on that.&lt;/p&gt;
        &lt;p&gt;This differs in kind of a fundamental way from the way the web UI / addon work, in so far as the addon will create and manage &lt;em&gt;unique&lt;/em&gt; OTA and HA keys for each device.
        My setup instead uses two keys shared among all of my devices.
        I don't see this as a significant risk because I don't use esphome devices in higher security contexts (i.e. my door locks are not running esphome), but your threat model is likely different than mine so you should make your own decisions.
        Nothing is stopping you from using unique keys for every device with this setup, you just have more secrets to manage in 1Password.&lt;/p&gt;
        &lt;h2 id="workflow" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-without-the-addon/#workflow"&gt;Workflow&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;My workflow looks like:&lt;/p&gt;
        &lt;pre&gt;&lt;code&gt;$ &amp;lt;edit whatever.yaml&amp;gt;
        $ ./build.sh run whatever.yaml
        # a bunch of compiler output and then logging from the device itself
        $ git add whatever.yaml &amp;amp;&amp;amp; git commit -m 'updates' &amp;amp;&amp;amp; git push origin main
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;There aren't many hard edges with this setup.
        You can put whatever you want into &lt;code&gt;common&lt;/code&gt; and you can organize your devices however you want.&lt;/p&gt;
        &lt;p&gt;One exception is that &lt;code&gt;secrets.yaml&lt;/code&gt; has some confusing implicit behavior, so I just commit an empty one and use a different file for my secrets.&lt;/p&gt;
        &lt;p&gt;Updating ESPHome is not something I do on a regular basis, but when I do it's basically just &lt;code&gt;brew upgrade esphome &amp;amp;&amp;amp; ./build.sh&lt;/code&gt;.&lt;/p&gt;
        &lt;p&gt;The process to add a new ESPHome device to Home Assistant is also fairly streamlined.
        All you do is attach the ESP32 device to your computer with USB, create a new yaml file, and run &lt;code&gt;./build.sh run &amp;lt;your new file&amp;gt;.yaml&lt;/code&gt;.
        ESPHome will pick up that there's a serially attached device without firmware and handle flashing the new firmware to it.&lt;/p&gt;
        &lt;p&gt;Once ESPHome is running on the new device it should show up in the Home Assistant integrations page and something that can be added.
        Clicking the accept button will open a config flow where you can paste your Home Assistant key, and then it should work just like any other device.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Mon, 12 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/esphome-without-the-addon/</guid></item><item><title>Why I don't live in Hungary</title><link>https://bytepawn.com/why-i-dont-live-in-hungary.html</link><description>&lt;p&gt;In February 2016, I made a life-changing decision to leave Hungary and seek opportunities abroad, starting in London and later moving to Dubai. This essay explores the multitude of reasons behind my choice and why I have no immediate plans to return to Hungary.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Orban no migration" src="/images/no-migration.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Wed, 07 Aug 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/why-i-dont-live-in-hungary.html</guid></item><item><title>“How to let go: one life ends while another begins”</title><link>https://jakeseliger.com/2024/08/06/how-to-let-go-one-life-ends-while-another-begins/</link><description>&amp;#8220;How to let go: one life ends while another begins. I&amp;#8217;m seven months pregnant with our daughter as Jake&amp;#8217;s life comes to a close. How do I walk into an uncertain future without him?&amp;#8220;</description><author>The Story's Story</author><pubDate>Tue, 06 Aug 2024 21:11:16 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/08/06/how-to-let-go-one-life-ends-while-another-begins/</guid></item><item><title>“No Salt”</title><link>https://jakeseliger.com/2024/08/05/no-salt/</link><description>This is by my brother, Sam. I arrived to Arizona late Saturday, after learning that my brother has only a few days left before cancer ends him. Jake’s wife, Bess, confessed that she had neither the willpower or the energy to take care of the post-death rituals—in this case, cremation, followed by a celebration of [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Tue, 06 Aug 2024 01:23:22 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/08/05/no-salt/</guid></item><item><title>Starting hospice. The end</title><link>https://jakeseliger.com/2024/08/04/starting-hospice-the-end/</link><description>I’m entering hospice. It’s time, and realistically past time. The squamous cell carcinoma tumors are growing, and the two doses of spot radiation I got on June 10 and 12 have utterly destroyed whatever quality of life I had. This weekend, a nurse came by and did some planning with Bess and me. Our extensive [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Mon, 05 Aug 2024 03:47:49 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/08/04/starting-hospice-the-end/</guid></item><item><title>How to solve it</title><link>https://bytepawn.com/how-to-solve-it.html</link><description>&lt;p&gt;I discuss Polya's &lt;em&gt;How to Solve it&lt;/em&gt; approach to mathematical problem solving and the 2010 paper &lt;em&gt;Teaching general problem-solving skills is not a substitute for teaching math&lt;/em&gt;.&lt;br /&gt;&lt;br /&gt; &lt;img alt="How to solve it" src="/images/how-to-solve-it.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 01 Aug 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/how-to-solve-it.html</guid></item><item><title>How I program in 2024</title><link>http://akkartik.name/post/programming-2024</link><description>&lt;p&gt;
I talk a lot here about &lt;a href="https://akkartik.name/freewheeling"&gt;using computers freely&lt;/a&gt;, how to select programs to use, how to decide if a program is trustworthy infrastructure one can safely depend on in the long term. I also spend my time building such infrastructure, because there isn't a lot of it out there. As I do so, I'm always acutely aware that &lt;em&gt;I'm just not very good at it.&lt;/em&gt; At best I can claim I try to compensate for limited means with good, transparent intentions.

&lt;p&gt;
I just spent &lt;a href="https://akkartik.name/post/2024-06-05-devlog"&gt;a month&lt;/a&gt; of my free time, off and on, &lt;a href="https://akkartik.name/post/2024-07-22-devlog"&gt;rewriting the core&lt;/a&gt; of a program I've been using and incrementally modifying for 2 years. I've been becalmed since. Partly this is the regular cadence of my subconscious reflecting on what just happened, what I learned from it, taking some time to decide where to go next. But I'm also growing aware this time of a broader arc in my life:

&lt;!-- more --&gt;

&lt;ul&gt;

&lt;li&gt; Back in 2015 I was suspicious of abstractions and big on tests and version control. Code seemed awash in bad abstractions, while tests and versions seemed like the key advances of the 2000s. I thought our troubles stemmed from bad incentives, using abstractions too much, and not using tests and versions enough. &lt;a href="https://github.com/akkartik/mu1"&gt;Mu1&lt;/a&gt; was an attempt at designing a platform with tests and &lt;a href="https://akkartik.name/post/wart-layers"&gt;layers&lt;/a&gt; (more like versions, less like abstractions) as foundational constraints influencing everything else.

&lt;p&gt;
&lt;li&gt; In 2017 I started reworking Mu1 into the current &lt;a href="https://github.com/akkartik/mu"&gt;Mu&lt;/a&gt;. &lt;a href="https://github.com/akkartik/mu/tree/main/linux/bootstrap#readme"&gt;At the start&lt;/a&gt; I used all my new ideas for tests and layers. But over time I stopped using them. Mu today has tons of tests, but they are conventional tests, and I never got around to porting over my infrastructure for layers.

&lt;p&gt;
&lt;li&gt; In 2022 I started working on &lt;a href="https://akkartik.name/freewheeling-apps"&gt;Freewheeling Apps&lt;/a&gt;. I started out with no tests, got frustrated at some point and wrote thorough tests for a core piece, the text editor. But I struggled to find ways to test the rest, and also found I was getting by fine anyway.

&lt;p&gt;
&lt;li&gt; Now it's 2024, and a month ago I deleted all my tests. I also started radically reworking my text editor, in a way that would have made me worried about merge conflicts with other Freewheeling Apps. In effect I stopped thinking about version control. Giving up tests and versions, I ended up with a much better program. The cognitive dissonance is now impossible to ignore.

&lt;/ul&gt;

&lt;p&gt;
After mulling it over for a few days, I think my current synthesis on &lt;a href="https://akkartik.name/freewheeling"&gt;programming durable things&lt;/a&gt; is:

&lt;ol&gt;

&lt;li&gt; Building durably for lots of people is too hard, just don't even try. Be ruled by what you know well, who you know well and &lt;a href="https://en.wikipedia.org/wiki/Dunbar%27s_number"&gt;Dunbar's number&lt;/a&gt;.
&lt;li&gt; Most software out there is incurably infected by incentives to serve lots of people in the short term. Focus as far as possible on software without lots of logos on the website, stuff that is easy to build, has few dependencies, definitely doesn't auto-update. Once you filter by these restrictions, the amount of durable software humanity has created so far is tiny.
&lt;li&gt; Small changes in context (people/places/features you want to support) often radically change how well a program fits its context. Our dominant milieu of short-termism doesn't prepare us for this fact.
&lt;li&gt; Given this tiny body of past work and low coverage per program, any new program you decide to build is quite likely striking out into the unknown in some way or other. You often won't know &lt;em&gt;quite&lt;/em&gt; what you're doing in some direction or other. (In my example above, I was trying to insert special "drawing lines" in a text editor. Questions that raised: can the cursor lie on a drawing? Can I try to draw in one line while the cursor is on another? Drawings are taller than text lines. Can a drawing be partially visible at top of screen? Can I draw on a partially visible drawing? My answers to these questions were sub-optimal for a long time, leading to hacks piled on hacks.)
&lt;li&gt; Types, abstractions, tests, versions, state machines, immutability, formal analysis, all these are tools available to us in unfamiliar terrain. Use them to taste.
&lt;li&gt; You'll inevitably end up over-using some of these tools, the ones you gravitate towards. The ideal quantity to use these tools is tiny, much more miniscule than any of us is trained to think by our dominant milieu of short-termism. The excess is &lt;em&gt;tech debt.&lt;/em&gt; It keeps us from noticing that a program is unnecessarily complex, less durable than it could be, harder to change when the context shifts.
&lt;li&gt; When your understanding of the context stabilizes, there's value in throwing away vast swathes of a program, and redoing it from scratch.
&lt;li&gt; Before you set out to rewrite, you have to spend some time importing everything into your brain at once. Everything you want from the program, all the scenarios the program has to cater to. This is hard. The goal is to get to a point where you can build everything all at once.
&lt;li&gt; Build everything all at once.

&lt;/ol&gt;

&lt;p&gt;
In my case, tests and versions actively hindered getting to the end of this evolution. Tests let me forget concerns. Version control kept me attached to the past. Both were counter-productive. It took a major reorientation to let go of them.

&lt;p&gt;
All the software I've written in my life &amp;mdash; and all my Freewheeling Apps so far &amp;mdash; are at level 6 in this trajectory. Only &lt;a href="https://akkartik.name/post/2024-07-22-devlog"&gt;the output of the past month&lt;/a&gt; feels like it might have gotten to level 9. We'll see.

&lt;p&gt;
It seems likely that a program can grow so complex it becomes impossible to import into memory in level 8. That seems to describe most software so far, certainly most software written by more than a couple of people. Even my text editor, small as it is, was daunting enough I spent much of the month girding myself to face the terror.

&lt;p&gt;
Not all software necessarily &lt;em&gt;needs&lt;/em&gt; to get to level 9. I think many of my Freewheeling Apps are simple enough and evolve slowly enough that they would stabilize to a bug-free state with just a handful of people using them, regardless of my initial design choices. Particularly now that I know how to streamline one complex piece at their core. Still, it's good to be aware of how things &lt;em&gt;might&lt;/em&gt; be improved, if it becomes worthwhile.

&lt;p&gt;
One thing that feels definitely useful in getting to level 9 is &lt;a href="https://www.youtube.com/watch?v=rX0ItVEVjHc"&gt;data-oriented design&lt;/a&gt;. It's not a tool you can blindly apply but a way of thinking you have to grow into, to look past immediate data structure choices at the big picture of how your program accesses data. Just don't let tools like &lt;a href="https://en.wikipedia.org/wiki/Entity_component_system"&gt;ECS&lt;/a&gt; blind you to the essential intellectual activity.

&lt;p&gt;
These levels are probably not quite right. I'm probably under-estimating tools I have less experience with.

&lt;p&gt;
I wonder what levels lie beyond these.

&lt;p&gt;
(I last wrote some thoughts on how I program &lt;a href="https://akkartik.name/post/four-repos"&gt;back in 2019&lt;/a&gt;. It's nice to see signs of evolution.)</description><author>Kartik Agaram</author><pubDate>Wed, 31 Jul 2024 21:52:44 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/programming-2024</guid></item><item><title>Less Coffee, Better Sleep</title><link>https://www.marginalia.nu/log/a_109_sleep2/</link><description>&lt;p&gt;As an experiment, I&amp;rsquo;ve reduced my coffee-intake to a single cup a day for about a week now. It&amp;rsquo;s made an enormous difference in sleep, mood and energy. I get tired at night, fall asleep quickly, and wake up refreshed.&lt;/p&gt;
&lt;p&gt;As mentioned previously in the context of &lt;a href="https://www.marginalia.nu/log/86-sleep/"&gt;morning sunlight exposure&lt;/a&gt;&amp;mdash;another thing that&amp;rsquo;s aided my sleeping habits, but is somewhat less practical to sustain as it requires fair weather&amp;mdash;I&amp;rsquo;ve always been slow to get going in the morning, active at night, bad at getting to bed at sane hours. Tired when I should be awake, and awake when I should be tired.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 31 Jul 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_109_sleep2/</guid></item><item><title>Automatic Cash Management</title><link>https://www.petekeen.net/automatic-cash-management/</link><description>&lt;h2 id="the-setup" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/automatic-cash-management/#the-setup"&gt;The Setup&lt;/a&gt;&lt;/h2&gt;
        &lt;ul&gt;
        &lt;li&gt;One &lt;a href="https://www.fidelity.com/trading/the-fidelity-account"&gt;Fidelity Brokerage Account&lt;/a&gt; (&amp;quot;brokerage&amp;quot;)&lt;/li&gt;
        &lt;li&gt;One &lt;a href="https://www.fidelity.com/cash-management/fidelity-cash-management-account/overview"&gt;Fidelity Cash Management Account&lt;/a&gt; (&amp;quot;CMA&amp;quot;)&lt;/li&gt;
        &lt;li&gt;One &lt;a href="https://www.fidelity.com/cash-management/visa-signature-card"&gt;Fidelity 2% Cash Back Credit Card&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;One &lt;a href="https://creditcards.chase.com/cash-back-credit-cards/freedom/unlimited"&gt;Chase Freedom Unlimited Credit Card&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;One &lt;a href="https://www.chase.com/personal/credit-cards/amazon"&gt;Amazon Prime Visa&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;h2 id="how-money-flows" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/automatic-cash-management/#how-money-flows"&gt;How Money Flows&lt;/a&gt;&lt;/h2&gt;
        &lt;ul&gt;
        &lt;li&gt;Payroll direct deposited into brokerage&lt;/li&gt;
        &lt;li&gt;All non-mortgage recurring expenses are paid with the Fidelity card&lt;/li&gt;
        &lt;li&gt;All Amazon expenses are paid with the Amazon card&lt;/li&gt;
        &lt;li&gt;All other expenses are paid with the Chase card&lt;/li&gt;
        &lt;li&gt;Bills autopay from brokerage (credit cards, insurance, billpay to household vendors)&lt;/li&gt;
        &lt;li&gt;Debit cards and live paper checks written against CMA&lt;/li&gt;
        &lt;/ul&gt;
        &lt;!--more--&gt;
        &lt;h2 id="why" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/automatic-cash-management/#why"&gt;Why&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;We use a brokerage account because it lets us keep cash and investments in the same account.
        All of our cash, including working capital and reserves, sits in the brokerage's &lt;strong&gt;core position&lt;/strong&gt;.
        Our core position is &lt;a href="https://fundresearch.fidelity.com/mutual-funds/summary/316341304"&gt;FZFXX&lt;/a&gt;, a Federal money market fund that pays ~5% interest as of July 2024.&lt;/p&gt;
        &lt;p&gt;We use the Fidelity credit card because it pays 2% cash back when it's set up to deposit rewards into a Fidelity account.
        It is also one of the only cards I've seen that can be set to automatically cash out deposits.
        Ours is set to deposit into the brokerage account.
        We only use this card for recurring expenses.
        Non-recurring expenses go on the Chase card because in our experience Chase's fraud detection and customer support is considerably better than Fidelity/Elan.&lt;/p&gt;
        &lt;p&gt;We use the Amazon Prime credit card for all Amazon expenses. This pays 5% back on Amazon purchases which makes it worth it for us. Your milage may vary.&lt;/p&gt;
        &lt;p&gt;We have the CMA so we don't expose the brokerage account number every time we write a paper check.
        This is probably overly paranoid and is the only significant complication in the entire system.&lt;/p&gt;
        &lt;p&gt;The CMA has &amp;quot;self-funded overdraft protection&amp;quot; turned on which automatically transfers from the brokerage account into the CMA to fund checks and debit card transactions.&lt;/p&gt;
        &lt;p&gt;The brokerage core position is not FDIC insured.
        &lt;strong&gt;I don't care about FDIC insurance&lt;/strong&gt;.
        FZFXX is composed of ultra short term US Treasury bills.
        If Treasuries are suddenly not liquid enough to withdraw our money our society has much bigger problems.&lt;/p&gt;
        &lt;p&gt;The CMA's core position &lt;em&gt;is&lt;/em&gt; FDIC insured, and the CMA is almost a full brokerage account, but we don't use it as the centerpiece account for two reasons.
        First, the CMA core position pays shit for interest.
        Second, the CMA cannot have margin turned on.
        We'll talk about why that's important in the next post.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Wed, 31 Jul 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/automatic-cash-management/</guid></item><item><title>Automatic Investing</title><link>https://www.petekeen.net/automatic-investing/</link><description>&lt;h2 id="the-setup" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/automatic-investing/#the-setup"&gt;The Setup&lt;/a&gt;&lt;/h2&gt;
        &lt;ul&gt;
        &lt;li&gt;Fidelity Brokerage Account (&amp;quot;brokerage&amp;quot;) with margin enabled&lt;/li&gt;
        &lt;li&gt;Fidelity Roth IRAs&lt;/li&gt;
        &lt;li&gt;Fidelity Solo 401K&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://fundresearch.fidelity.com/mutual-funds/summary/31634R109"&gt;Fidelity Multi-Asset Index Fund (FFNOX)&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://www.ishares.com/us/products/239729/ishares-aggressive-allocation-etf"&gt;iShares Core Aggressive Allocation ETF&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;h2 id="how-money-flows" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/automatic-investing/#how-money-flows"&gt;How Money Flows&lt;/a&gt;&lt;/h2&gt;
        &lt;ul&gt;
        &lt;li&gt;Every investable dollar is in FFNOX across all non-401k tax advantaged accounts&lt;/li&gt;
        &lt;li&gt;Every investable dollar in taxable accounts is invested in AOA&lt;/li&gt;
        &lt;li&gt;Automatically contribute to my employer's 401K plan every pay period into whatever fund&lt;/li&gt;
        &lt;/ul&gt;
        &lt;!--more--&gt;
        &lt;h2 id="why" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/automatic-investing/#why"&gt;Why&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;I don't want anyone to have to think about where to pull money from at any time.
        I want me or my wife to be able to login to Fidelity and sell enough to cover cash needs with a very small number of clicks.&lt;/p&gt;
        &lt;p&gt;FFNOX and AOA are &lt;strong&gt;funds of funds&lt;/strong&gt;. FFNOX consists of low cost Fidelity funds and AOA consists of low cost Blackrock iShares ETFs.
        They both invest in approximately 60% US total stock market, 25% international developed total stock market, and 15% US total bond market.
        This fits our family's desired asset allocation.&lt;/p&gt;
        &lt;p&gt;The brokerage account has &lt;strong&gt;margin&lt;/strong&gt; enabled.
        Margin allows you to borrow up to 50% of the value of your investable assets (everything but cash and CDs) from your broker for any purpose whatsoever.
        It kicks in while you run out of cash and will automatically pay itself back when you deposit cash in the account.&lt;/p&gt;
        &lt;p&gt;We have margin turned on so that we don't have to worry about selling investments to raise cash while something awful is happening.
        We can login to Fidelity and sell some FFNOX when it's convenient rather than having to do it one some kind of schedule.&lt;/p&gt;
        &lt;p&gt;Contributing to my employer's 401K plan is an automatic tax break and also means I get my employer's matching contribution.
        I currently (for 2024) have it set up to max out my employer's match.&lt;/p&gt;
        &lt;p&gt;&lt;strong&gt;We don't use robo-investors.&lt;/strong&gt;
        FFNOX's total expenses are capped at 0.08%, or $80 per $100,000 annually.
        Wealthfront charges 0.25% (more than 3x) &lt;em&gt;on top of&lt;/em&gt; the fees for the actual ETFs (typically ~0.1%).
        &lt;em&gt;Robo-investors are never going to offset their fees&lt;/em&gt; when compared to FFNOX or similar funds (Target Retirement funds at Vanguard or Freedom Index funds at Fidelity).&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Wed, 31 Jul 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/automatic-investing/</guid></item><item><title>Automatic Finances</title><link>https://www.petekeen.net/automatic-finances/</link><description>&lt;hr /&gt;
        &lt;p&gt;My friend Amy Hoy recently tweeted about financial planning and personal finance.
        This particular tweet stuck out to me:&lt;/p&gt;
        &lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;you'd probably think given my love of money that i'd be all over financial planning, but truly i only enjoy earning it and spending it. saving it feels like… freezing cake. i guess you could do it, but why, you have cake&lt;/p&gt;&amp;mdash; Amy Hoy ✨ (@amyhoy) &lt;a href="https://twitter.com/amyhoy/status/1097662568645214208?ref_src=twsrc%5Etfw"&gt;February 19, 2019&lt;/a&gt;&lt;/blockquote&gt; 
        &lt;p&gt;In early 2018 I radically simplified my family's personal financial system and made everything as automatic as possible.
        Amy and Joel Hooks asked me to write up how it works, so this is the start of a series of short posts about how and why I set everything up.&lt;/p&gt;
        &lt;!--more--&gt;
        &lt;h2 id="a-little-bit-of-why" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/automatic-finances/#a-little-bit-of-why"&gt;A Little Bit of Why&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;I prefer to think of myself as a realist.
        Due to my &lt;a href="https://www.petekeen.net/cancer"&gt;health history&lt;/a&gt; my wife is likely going to be around longer than me. Her family has some very long lived female members as well. Her grandmother is 103 and her great aunt just passed in 2018 at the ripe old age of 95.&lt;/p&gt;
        &lt;p&gt;I want to make our finances as simple as possible so she doesn't have to worry about them when the inevitable happens.&lt;/p&gt;
        &lt;p&gt;In 2016 my wife and I welcomed our first child into the world and in late 2018 we welcomed our second.
        They are two more very important reasons why I want things to be simple.
        If something happens to both my wife and me, I want our intentions with regards to our finances as plain as possible.&lt;/p&gt;
        &lt;p&gt;This system got a trial run in late 2018.
        My wife was admitted to the hospital at 29 weeks pregnant for preeclampsia, a very dangerous condition that needs close monitoring.
        My daugther was born at 34 weeks and spent the next five weeks in NICU.&lt;/p&gt;
        &lt;p&gt;&lt;strong&gt;I didn't have to touch this system at all.&lt;/strong&gt; Not once. I logged in a handful of times to check up on it, but everything just hummed along.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Wed, 31 Jul 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/automatic-finances/</guid></item><item><title>Delightful, production-grade replication for Postgres</title><link>http://notes.eatonphil.com/2024-07-30-delightful-production-grade-replication-postgres.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/delightful-production-grade-replication-postgres"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 30 Jul 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-07-30-delightful-production-grade-replication-postgres.html</guid></item><item><title>More isn’t always better: death and over-treatment as a downside of agenticness</title><link>https://jakeseliger.com/2024/07/29/more-isnt-always-better-death-and-over-treatment-as-a-downside-of-agenticness/</link><description>i. Aren’t we clever? Bess and I felt so very smart. In April, we congratulated ourselves for navigating the healthcare system to get two doses of spot radiation that, in conjunction with clinical trial drug PDL1V, likely shrunk, and perhaps temporarily eliminated, the squamous cell carcinoma tumors in my neck that have been strangling me [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Mon, 29 Jul 2024 15:30:00 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/07/29/more-isnt-always-better-death-and-over-treatment-as-a-downside-of-agenticness/</guid></item><item><title>Ingest: Streamlining Content Preparation for LLMs</title><link>https://smcleod.net/2024/07/ingest-streamlining-content-preparation-for-llms/</link><description>A CLI tool for parsing directories into LLM-friendly markdown</description><author>smcleod.net</author><pubDate>Mon, 29 Jul 2024 04:00:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/07/ingest-streamlining-content-preparation-for-llms/</guid></item><item><title>Building a qBittorrent client with SwiftUI</title><link>https://burakku.com/blog/swiftui-qbittorrent-webui/</link><description>&lt;p&gt;&lt;img alt="Building a qBittorrent client with SwiftUI" src="thumbnail.png" /&gt;&lt;/p&gt;
&lt;p&gt;I recently got convinced to switch from rTorrent to qBittorrent for my Linux-downloading needs. Tried it out for a bit and decided that it was indeed the superior of the two, meaning I fully migrated all of my Linux ISOs to it. Unfortunately, I didn't really like any of the available clients for it, so I asked myself what any good software engineer would at that point: &lt;em&gt;how hard can it be&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;I first thought about writing one in Rust, since I've had some experience with it lately, first building &lt;a href="https://github.com/Hamuko/yurizaki" target="_blank"&gt;an automatic file sorter&lt;/a&gt; and later &lt;a href="https://github.com/Hamuko/anifunnel" target="_blank"&gt;an anime scrobbler&lt;/a&gt;, but after a cursory look at &lt;a href="https://github.com/gyscos/cursive" target="_blank"&gt;Cursive&lt;/a&gt; and &lt;a href="https://ratatui.rs/" target="_blank"&gt;Ratatui&lt;/a&gt;, I buried that idea. Cursive just seemed like a bad fit and while Ratatui seemed like it could be a good fit for building my application, the tutorials were in middle of a rewrite and I didn't want to spend too much time learning from examples that were already abandoned. Rust and TUIs are tricky enough as they are without a bunch of outdated documentation.&lt;/p&gt;
&lt;p&gt;Since I didn't want to make yet another web client for qBittorrent, and I'd preferably have something with at least decent performance, I figured I could try writing a native desktop application. And since I'm on macOS and Swift is actually a really good language, Swift it was. I've had a bit of experience in writing Mac apps in Swift before, but I figured that while I was pursuing my half-baked idea, I might as well learn something new while I was at it.&lt;/p&gt;
&lt;p&gt;Cue &lt;strong&gt;SwiftUI&lt;/strong&gt;, Apple's hot new declarative user interface framework that's actually not that new since it was released almost five years ago. Not sure if it's even that hot since I never hear anyone actually using it to ship products. Well, it's the thought that counts anyways.&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;The first thing I noticed about SwiftUI applications is that they take absolutely ages to build. People complaining about Rust having long compile times should try SwiftUI to get some perspective. Granted, most Rust programs don’t have graphical user interfaces, but it’s still quite a grating development experience. Change one line and it takes like 30–120 seconds to get a new debug build running on an M1 Max. I’ve done some stuff with Storyboard and XIB interfaces before and I do not remember any of them being nearly as bad.&lt;/p&gt;
&lt;p&gt;I first thought that the long build times were just a &lt;em&gt;me&lt;/em&gt; problem, but after bitching about it on Bluesky, I did get &lt;a href="https://bsky.app/profile/delightedcrow.dev/post/3krpn32goq22h" target="_blank"&gt;another developer chime in&lt;/a&gt; to confirm that yes, SwiftUI takes a long time to build.&lt;/p&gt;
&lt;p&gt;The other thing that I quite quickly noticed was that SwiftUI does produce quite a lot of annoying and obscure roadblocks for you to decipher. For example, if you fuck up your types and are writing views that have any girth to them at all, Swift will just shame you about your massive views without telling what type error you've produced. That's a super fun one. &lt;a href="https://danielchasehooper.com/posts/why-swift-is-slow/" target="_blank"&gt;Apparently Swift's type checker is just so slow that it decides to mercy kill the whole operation.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Publishing changes from within view updates is not allowed" src="notypecheck.png" /&gt;&lt;/p&gt;
&lt;p&gt;I've also had Xcode hard crash quite a few times while building out my user interface. I imagine most of them have been triggered by the preview window rendering things. Thankfully I've never lost any work from these crashes, only wasted a bit of time.&lt;/p&gt;
&lt;h2&gt;Redraw woes&lt;/h2&gt;
&lt;p&gt;One thing about SwiftUI's automagic logic of updating your views whenever the application state changes is that it has some very definite flaws in it. I wanted to show my data in a table view where the table rows are selectable, but because I also have OCD, I want to be able to deselect rows to get my application back to a serene unselected state. In order to fulfil my desires, I figured that I'd add a keypress handler to my table so that every time Escape is pressed, the table's selection binding is cleared and my selection would be gone. Basically, like this:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;TableView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;EnvironmentObject&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;dataModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DataModel&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;tableSelection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RowObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tableCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tableSelection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onKeyPress&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;tableSelection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;removeAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handled&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Good idea in theory. You update &lt;code&gt;tableSelection&lt;/code&gt; that is the selection binding for the &lt;code&gt;Table&lt;/code&gt; view and the table should redraw without any rows selected. Unfortunately, while it does clear the selection, the table won't show the selection disappearing until the data source for the table updates. Since I'm building a torrent client that fetches the current state every 𝓃 seconds, it might take up to 𝓃 seconds for the deselection to be visible to the user after they press it. Not great.&lt;/p&gt;
&lt;p&gt;So, what if I just forced the data source to update? Would that work? Yeah, &lt;em&gt;kinda&lt;/em&gt;.&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;TableView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;EnvironmentObject&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;dataModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DataModel&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;tableSelection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RowObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tableCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tableSelection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onKeyPress&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;tableSelection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;removeAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;dataModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objectWillChange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handled&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By sending the &lt;code&gt;objectWillChange&lt;/code&gt; event for my table's data source, I am able to force the &lt;code&gt;Table&lt;/code&gt; to redraw, which will make the deselection action feel and look responsive. Worked absolutely perfectly in my testing. So where's the &lt;em&gt;kinda&lt;/em&gt; part?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Publishing changes from within view updates is not allowed" src="objectWillChange.png" /&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately this (seemingly?) functional code is actually undefined behaviour and Xcode will give you a big warning saying it's verboten. So what can I do instead?&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;TableView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;EnvironmentObject&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;dataModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DataModel&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;tableSelection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RowObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tableCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tableSelection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onKeyPress&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;tableSelection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;removeAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;dataModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objectWillChange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handled&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Turns out that the solution is to wrap the code in &lt;code&gt;DispatchQueue.main.async&lt;/code&gt; and you're golden. It works perfectly fine and gives zero errors. Not sure if this is actually much better than the previous solution, since we're just kicking off the code to be executed asynchronously in the main thread instead of doing it here and now. Shouldn't even be any difference in the thread since &lt;code&gt;onKeyPress()&lt;/code&gt; handler should already be on the main thread, as calling &lt;code&gt;DispatchQueue.main.sync&lt;/code&gt; instead will horribly crash your application. Would definitely be better if I could somehow either force the &lt;code&gt;Table&lt;/code&gt; itself to update itself or have it update automatically whenever its selection changes, but at least there's &lt;em&gt;some&lt;/em&gt; way to do it.&lt;/p&gt;
&lt;h2&gt;Is this supposed to work like this?&lt;/h2&gt;
&lt;p&gt;One thing that I wasn't really fond of in the default qBittorrent user interface is that you can't easily remove a torrent and delete its data at the same time, since you always have to separately click a checkbox to delete files. Hence, my client would have two separate actions: remove and remove + delete. And those actions should be possible with keyboard alone.&lt;/p&gt;
&lt;p&gt;However, since it's quite risky to delete things without any confirmation, I decided to add a confirmation prompt before anything happens. Finder also does a very similar thing when you delete files on a network share, so it should feel pretty natural on macOS.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
    &lt;img alt="Delete prompt in Finder" class="unscaled no-border" src="finderdelete.png" /&gt;
&lt;/div&gt;&lt;p&gt;Thankfully this is fairly easy in SwiftUI. Just added a &lt;code&gt;confirmationDialog&lt;/code&gt; to my view with a boolean binding that controls if the confirmation dialog is presented or not, with some customisations to boot.&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confirmationDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;&amp;quot;Remove torrent and delete data?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;showDeleteConfirmation&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Remove and delete data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destructive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Delete logic here.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Cancel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Cancel logic here.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dialogIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;trash.circle.fill&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dialogSeverity&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The end result is very similar to what you get from Finder, except that my button has too much text for the buttons to fit on one row so they're stacked. Had I opted for a shorter "Delete" instead, I'd get them on one row.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
    &lt;img alt="Delete prompt" class="unscaled" src="delete.png" /&gt;
&lt;/div&gt;&lt;p&gt;Unfortunately what was less simple was the keyboard shortcuts. You get Escape as the cancel button for free by just having your cancel button defined with a "cancel" role, but the main action button isn't free. From what I've gathered is that you're supposed to use &lt;code&gt;Button.keyboardShortcut(.defaultAction)&lt;/code&gt; to define that a button is the main button and the default key binding for the given scenario should be used. For a regular confirmation dialog, that button is naturally Enter.&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confirmationDialog&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This button is highlighted in blue and can be activated with Enter.&lt;/span&gt;
    &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Delete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="n"&gt;keyboardShortcut&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;defaultAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// This button can be activated with Escape.&lt;/span&gt;
    &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Cancel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But if you want to create a destructive confirmation dialog like what you find in Finder, there seemingly does not exist a keybinding that would allow you to press that button – even though Finder lets you do it with &lt;strong&gt;⌘D&lt;/strong&gt;. At least I couldn't find one, and even if I did, it'd be so obscure that no Mac user would actually stumble upon it.&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confirmationDialog&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This button is highlighted in red and can be activated with ???.&lt;/span&gt;
    &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Delete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destructive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="n"&gt;keyboardShortcut&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;defaultAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// This button can be activated with Escape.&lt;/span&gt;
    &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Cancel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I imagine that this is not intended, and that there should exist a &lt;code&gt;.defaultAction&lt;/code&gt; shortcut that you can use with destructive confirmation dialogs. But since iOS is very light on keyboard shortcuts (I think you can use them if you have an iPad with a keyboard case?), I imagine that these sorts of things are just not a priority to Apple, as macOS is not where the money is. So after I'd determined that I probably wasn't just being dumb, I opted to just do the obvious thing instead:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confirmationDialog&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This button is highlighted in red and can be activated with Cmd+D.&lt;/span&gt;
    &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Delete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destructive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="n"&gt;keyboardShortcut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KeyEquivalent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;D&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;modifiers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// This button can be activated with Escape.&lt;/span&gt;
    &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Cancel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hope that Apple never changes how the one in Finder works.&lt;/p&gt;
&lt;h2&gt;It it any good? What's it for?&lt;/h2&gt;
&lt;p&gt;After I'd spent a week or two working on this project, I opened up an old Storyboard-based project of mine since I had to make some updates to it. More specifically, I had to add a new text field to a settings section, which was to be located underneath an existing text field. And while I'd always considered the Xcode Interface Builder to be fairly easy and fun, aligning things and adding constraints manually did feel extremely silly after having worked in SwiftUI. Why am I doing all of this silly manual work, dragging and dropping fields and setting alignment constraints, when I could just do this?&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Old and busted&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Old and busted&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;New hotness&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;New hotness&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div style="text-align: center;"&gt;
    &lt;img alt="SwiftUI form" class="unscaled no-border" src="form.png" /&gt;
&lt;/div&gt;&lt;p&gt;Fast, simple, concise, automatic alignment. What's not to love?&lt;/p&gt;
&lt;p&gt;On the other hand, whenever I did something that SwiftUI wasn't really designed to do, like have multiple lists in a &lt;code&gt;NavigationSplitView&lt;/code&gt; sidebar, then it's a massive pain. Just getting this janky piece of shit solution working took a long while:&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
    &lt;video loop="loop"&gt;
        &lt;source src="sidebar.mp4" type="video/mp4" /&gt;
    &lt;/video&gt;
&lt;/div&gt;&lt;p&gt;It's kinda hard to see why SwiftUI exists in the first place. I don't actually have any data but I'm pretty sure native desktop and mobile applications are on a steep decline, with companies electing to ship Electron or React Native apps instead of writing native applications for macOS, Windows, Linux, iOS and Android separately. Writing a truly native application is only for the biggest of companies, who can afford to write a handful of separate applications, or small boutique shops, who are targetting a niche. Perhaps Apple engineers are just building it for themselves after getting tired of having to deal with multiple different UI systems at the same time, since everything they make needs to ship on iOS, iPadOS and macOS. They even ship the Calculator app on the iPadOS nowadays.&lt;/p&gt;
&lt;p&gt;Glad that it exists though. For all the frustrations I had during my time working with SwiftUI, I did actually find it pretty nice and fun. My gaming PC went unused for days during my busiest days building my app. I definitely want to try doing more native Apple development in the future. I want to try and port my client to iPadOS at some point in the future too, since it shouldn't be a massive undertaking with SwiftUI. Not that I'd be able to distribute it unless there's some drastic changes to iPadOS distribution coming, since I don't pay for Apple's developer program. No signing or notarisation for my apps.&lt;/p&gt;
&lt;p&gt;Although the next time I try to build anything that requires HTTP, I'm going to start immediately with &lt;a href="https://github.com/Alamofire/Alamofire" target="_blank"&gt;Alamofire&lt;/a&gt; instead of trying to make do with URLSession. Anything more than GET and you'll find yourself building another Alamofire.&lt;/p&gt;
&lt;h2&gt;The app&lt;/h2&gt;
&lt;p&gt;All in all, I did manage to actually make something: a very bare-bones qBittorrent client called &lt;a href="https://github.com/Hamuko/Dreadnought" target="_blank"&gt;&lt;b&gt;Dreadnought&lt;/b&gt;&lt;/a&gt;. I even managed to make a shitty application icon for it! Progress has been extremely slow lately though, since I hit a point where I managed to make an application that does like 80% of the things I need in the qBittorrent Web UI, so everything else is more work for increasingly smaller gains.&lt;/p&gt;
&lt;p&gt;I do use it daily though, so it definitely serves someone's purpose, but it's hard to recommend to anyone that is not me, myself nor I. Someone might even call this a feature, since building apps with no general use means getting no support requests. I also wouldn't be surprised if the state updates are expensive as hell and if it was leaking memory, since I have done basically no performance testing. Well if nothing else, at least it ships without a copy of Chromium bundled inside it.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 27 Jul 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/swiftui-qbittorrent-webui/</guid></item><item><title>The blog posts I haven't written (yet)</title><link>https://austinhenley.com/blog/notyetwritten.html</link><description>&lt;a href="https://austinhenley.com/blog/notyetwritten.html"&gt;https://austinhenley.com/blog/notyetwritten.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 21 Jul 2024 21:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/notyetwritten.html</guid></item><item><title>LLM Parameter Playground</title><link>https://smcleod.net/2024/07/llm-parameter-playground/</link><description>A web app to explore the effects of different inference parameters on LLMs.</description><author>smcleod.net</author><pubDate>Sat, 20 Jul 2024 04:00:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/07/llm-parameter-playground/</guid></item><item><title>Code, Chaos, and Copilots (AI/LLM Talk July 2024)</title><link>https://smcleod.net/2024/07/code-chaos-and-copilots-ai/llm-talk-july-2024/</link><description>Integrating AI into Daily Development</description><author>smcleod.net</author><pubDate>Thu, 18 Jul 2024 04:00:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/07/code-chaos-and-copilots-ai/llm-talk-july-2024/</guid></item><item><title>Links: No one wants to talk about death, deep takes on the human condition, and more!</title><link>https://jakeseliger.com/2024/07/16/links-no-one-wants-to-talk-about-death-deep-takes-on-the-human-condition-and-more/</link><description>* &amp;#8220;The Silence Doctors Are Keeping About Millennials’ Death.&amp;#8221; Germane to me for obvious reasons; our culture denies both death and grief and seems not able to incorporate either, particularly when death is premature. My own end feels so close, particularly because my days, such as they are, are filled with pain and exhaustion. * &amp;#8220;How [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Wed, 17 Jul 2024 06:24:37 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/07/16/links-no-one-wants-to-talk-about-death-deep-takes-on-the-human-condition-and-more/</guid></item><item><title>Understanding AI/LLM Quantisation Through Interactive Visualisations</title><link>https://smcleod.net/2024/07/understanding-ai/llm-quantisation-through-interactive-visualisations/</link><description>AI/LLM Quantisation Visualised</description><author>smcleod.net</author><pubDate>Wed, 17 Jul 2024 04:00:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/07/understanding-ai/llm-quantisation-through-interactive-visualisations/</guid></item><item><title>My Fave Podcasts 2024 - The Big Reset</title><link>https://www.swyx.io/fave-podcasts-2024</link><description>4.5 years after my initial podcast inventory, with COVID and a career change under my belt, with my OPML file growing from 566 rss feeds in 2022 to 771 in 2024 (with quite a few of my fave NPR podcasts dying), and my backlog mounting to 43GB (according to this script, my outstanding episodes: 11915 (played: 22451)), I am now needing to declare podcast bankruptcy.</description><author>swyx's site RSS Feed</author><pubDate>Mon, 15 Jul 2024 23:16:06 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/fave-podcasts-2024</guid></item><item><title>Story Points are Pointless, Measure Queues</title><link>https://www.brightball.com/articles/story-points-are-pointless-measure-queues</link><description>Their creator has disavowed them. People cannot agree on what a story point even represents. The measure is different for every team that uses it. They sow confusion, create conflict, unreliable timelines, are easily gamed, demotivate and degrade the performance of your team.For everyone involved, this is a waste of time. Let's deep dive into why Story Points are so broken and how to avoid dealing with them ever again.</description><author>Brightball Articles</author><pubDate>Mon, 15 Jul 2024 13:05:55 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/story-points-are-pointless-measure-queues</guid></item><item><title>Can ChatGPT do data science?</title><link>https://austinhenley.com/blog/chatgptdatascience.html</link><description>&lt;a href="https://austinhenley.com/blog/chatgptdatascience.html"&gt;https://austinhenley.com/blog/chatgptdatascience.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Mon, 15 Jul 2024 00:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/chatgptdatascience.html</guid></item><item><title>Links: Slowness as a problem, kinds of conversations, curing cancer, and more!</title><link>https://jakeseliger.com/2024/07/14/links-slowness-as-a-problem-kinds-of-conversations-curing-cancer-and-more/</link><description>* &amp;#8220;Pentagon ‘alarmingly slow’ at fielding new weapons.&amp;#8221; China seems not to suffer this problem. Perhaps, given what&amp;#8217;s happening with Taiwan, we should pay more attention. In other &amp;#8220;China moves fast&amp;#8221; news: &amp;#8220;China State Shipbuilding Corporation is the world&amp;#8217;s largest shipbuilder. It builds vessels for the People&amp;#8217;s Liberation Army Navy and increasingly sophisticated dual-use commercial [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Sun, 14 Jul 2024 22:11:05 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/07/14/links-slowness-as-a-problem-kinds-of-conversations-curing-cancer-and-more/</guid></item><item><title>The two-crisis update</title><link>https://jakeseliger.com/2024/07/10/the-two-crisis-update/</link><description>Five days ago I wrote about two simultaneous crises: one from swallowing and breathing poorly, and the second from PDL1V no longer holding back the tumors. Despite those beautiful scans in May showing extensive tumor-size reduction, the most recent scans show &amp;#8220;Continued neoplastic disease progression throughout the neck as described. Of note tumor results in [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Thu, 11 Jul 2024 05:40:56 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/07/10/the-two-crisis-update/</guid></item><item><title>Rejoining academia</title><link>https://austinhenley.com/blog/rejoiningacademia.html</link><description>&lt;a href="https://austinhenley.com/blog/rejoiningacademia.html"&gt;https://austinhenley.com/blog/rejoiningacademia.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Wed, 10 Jul 2024 23:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/rejoiningacademia.html</guid></item><item><title>The CP/M emulator is good enough, I think.</title><link>https://blog.steve.fi/the_cp_m_emulator_is_good_enough__i_think_.html</link><description>&lt;p&gt;&lt;a href="https://blog.steve.fi/the_cp_m_emulator_is_working_well" rel="nofollow"&gt;My previous post&lt;/a&gt; mentioned that I'd added some custom syscalls to &lt;a href="https://github.com/skx/cpmulator" rel="nofollow"&gt;my CP/M emulator&lt;/a&gt; and that lead to some more updates, embedding a bunch of binaries within the emulator so that the settings can be tweaked at run-time, for example running:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;!DEBUG 1
!CTRLC 1
!CCP ccpz
!CONSOLE adm-3a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Those embedded binaries show up on &lt;code&gt;A:&lt;/code&gt; even if they're not in the &lt;code&gt;pwd&lt;/code&gt; when you launch the emulator.&lt;/p&gt;

&lt;p&gt;Other than the custom syscalls I've updated the &lt;em&gt;real&lt;/em&gt; BDOS/BIOS syscalls a bit more, so that now I can run things like the Small C compiler, BBC BASIC, and more.  (BBCBasic.com used to launch just fine, but it turned out that the SAVE/LOAD functions didn't work.  Ooops!)&lt;/p&gt;

&lt;p&gt;I think I've now reached a point where all the binaries I care about run, and barring issues I will slow down/stop development. I can run Turbo Pascal, WordStar, various BASIC interpreters, and I have a significantly improved understanding of how CP/M works - a key milestone in that understanding was getting &lt;code&gt;SUBMIT.COM&lt;/code&gt; to execute, and understanding the split between the BDOS and the BIOS.&lt;/p&gt;

&lt;p&gt;I'd kinda like to port CP/M to a new (Z80-based) system - but I don't have such a thing to hand, and I guess there's no real need for it.  Perhaps I can say I'm "done" with retro stuff, and go back to playing Super Mario Bros (1985) with my boy!&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Tue, 09 Jul 2024 23:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/the_cp_m_emulator_is_good_enough__i_think_.html</guid></item><item><title>Optimizing a bignum library for fun</title><link>https://austinhenley.com/blog/bignum2.html</link><description>&lt;a href="https://austinhenley.com/blog/bignum2.html"&gt;https://austinhenley.com/blog/bignum2.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Tue, 09 Jul 2024 22:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/bignum2.html</guid></item><item><title>A reawakening of systems programming meetups</title><link>http://notes.eatonphil.com/2024-07-07-systems-meetups.html</link><description>&lt;p&gt;This year has seen a resurgence in really high quality systems
programming meetups. &lt;a href="https://www.meetup.com/munich-database-meetup/"&gt;Munich Database
Meetup&lt;/a&gt;, &lt;a href="https://lu.ma/8ujc7st3?tk=DAAbmn"&gt;Berlin
Systems Group&lt;/a&gt;, &lt;a href="https://lu.ma/t6r4mi4v"&gt;SF Distributed
Systems Meetup&lt;/a&gt;, &lt;a href="https://nycsystems.xyz/"&gt;NYC
Systems&lt;/a&gt;, &lt;a href="https://twitter.com/BengaluruSys"&gt;Bengaluru
Systems&lt;/a&gt;, to name a few.&lt;/p&gt;
&lt;p&gt;This post summarizes a bit of disappointing recent tech meetup
history, the new trend of excellent systems programming meetups, and
ends with some encouragement and guidance for running your own systems
programming events.&lt;/p&gt;
&lt;p&gt;I will be a little critical in this post but I want to preface by
saying: organizing meetups is really tough! It takes a lot of work and
I have a huge amount of respect for meetup organizers even when their
meetup style did not resonate with me.&lt;/p&gt;
&lt;p&gt;Although much of this post talks about NYC Systems, the reason I think
this post is worth writing is because so many other meetups in a
similar vein popped up. I hope to encourage these other meetups and to
encourage folks in other major metros (London, for example) to start
similar meetups.&lt;/p&gt;
&lt;h3 id="meetups"&gt;Meetups&lt;/h3&gt;&lt;p&gt;I used to attend a bunch of meetups before the pandemic. But I quickly
got disillusioned. Almost every meetup was varying degrees of startups
pitching their product. The last straw for me was sitting through a talk
at a JavaScript meetup that was by a devrel employee of a startup who
literally gave a tutorial for their product.&lt;/p&gt;
&lt;p&gt;There were also some pretty intelligent meetups like the New York
Haskell Users Group and the New York Emacs Meetup. But not being an
expert in either domain, and the attendees almost solely appearing to
be experts, I didn't particularly enjoy going.&lt;/p&gt;
&lt;p&gt;There were a couple of meetups that felt inclusive for various
skill-levels of attendees yet still went into interesting
depth. Specifically, &lt;a href="http://www.nylug.org/"&gt;New York Linux User
Group&lt;/a&gt; and &lt;a href="https://paperswelove.org/chapter/newyork/"&gt;Papers We Love
NYC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These meetups were exceptional because they were language- and
framework-agnostic, they would start broad to give you background, but
then go deep into a topic. Maybe you only understood 50% of what was
covered. But you get exposed to something new from an expert in that
domain.&lt;/p&gt;
&lt;p&gt;Unfortunately, the pandemic happened and these two excellent meetups
basically have not come back.&lt;/p&gt;
&lt;h3 id="a-couple-of-students-in-munich"&gt;A couple of students in Munich&lt;/h3&gt;&lt;p&gt;The pandemic ended and I tried a couple of meetups I thought might be
better quality. Rust and Go. But they weren't much better than I
remembered. People would give a high level talk and brush over all the
interesting concepts.&lt;/p&gt;
&lt;p&gt;I had been thinking of doing an in-person talk series since 2022.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;If I put together a systems/databases/distributed systems meetup in NYC (a physical meetup, not Zoom), who'd be interested (in attending, or presenting, or helping me organize, or donating space)?&lt;br /&gt;&lt;br /&gt;No promises!&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1574875016067710976?ref_src=twsrc%5Etfw"&gt;September 27, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;But I was busy with TigerBeetle until December of 2023 when I was
messaged on LinkedIn by &lt;a href="https://x.com/georg_kreuzmayr?lang=en"&gt;Georg
Kreuzmayr&lt;/a&gt;, a graduate student
at Technical University of Munich (TUM).&lt;/p&gt;
&lt;p&gt;Georg and his friends, fellow graduate students at TUM, started a
database club: &lt;a href="https://www.tumuchdata.club/"&gt;TUMuchData&lt;/a&gt;. We got to
talking about opportunities for collaboration and I started feeling a
bit embarrassed that a graduate student had more guts than I had to
get
&lt;a href="https://notes.eatonphil.com/eight-years-of-tech-meetups.html"&gt;back&lt;/a&gt;
onto the meetup organizer wagon.&lt;/p&gt;
&lt;p&gt;A week later, with assurance from &lt;a href="https://twitter.com/justinjaffray"&gt;Justin
Jaffray&lt;/a&gt; that at least he would
show up with me if no one else did, I started the &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;NYC Systems Coffee
Club&lt;/a&gt; to bring
together folks in NYC interested in any topic of systems programming
(e.g. compilers, databases, web browser internals, distributed
systems, formal methods, etc.). To bring them together in a completely
informal setting for coffee at 9am in the morning in a public space in
midtown Manhattan.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Trying something new! If you're a dev in NYC working &lt;br /&gt;on (or interested in) systems programming, grab a coffee and come hang out at 1 Bryant Park (indoor space) this Thursday 9AM - 9:30AM.&lt;br /&gt;&lt;br /&gt;See post for details and fill out the Google Form or DM me!&lt;a href="https://t.co/A4bzcPGy6x"&gt;https://t.co/A4bzcPGy6x&lt;/a&gt; &lt;a href="https://t.co/n1ECMd59ev"&gt;pic.twitter.com/n1ECMd59ev&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1734216183459512486?ref_src=twsrc%5Etfw"&gt;December 11, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;I set up that linked web page and started collecting subscribers to
the club via Google Form. Once a month I'd send an email out to the
list asking for RSVPs to this month's coffee club. The first 20 to
respond would get a calendar invite.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/coffee-club-invite.png" src="/assets/coffee-club-invite.png" /&gt;&lt;/p&gt;
&lt;p&gt;And about the same time I started asking around on Twitter/LinkedIn if
someone would be interested in co-organizing a new systems programming
meetup in NYC. &lt;a href="https://twitter.com/ngeloxyz"&gt;Angelo Saraceno&lt;/a&gt;
immediately took me up on the idea and we met up.&lt;/p&gt;
&lt;h3 id="nyc-systems"&gt;NYC Systems&lt;/h3&gt;&lt;p&gt;We agreed on the premise: this would be a language- and
framework-agnostic meetup that was focused on engineering challenges,
not product pitches. It would be 100% for the sake of corporate
marketing, but corporate marketing of the &lt;em&gt;engineering team&lt;/em&gt;, not the
product.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://nycsystems.xyz/"&gt;NYC Systems&lt;/a&gt; was born!&lt;/p&gt;
&lt;p&gt;We'd find speakers who could start broad and dive deep into some
interesting aspect of databases, programming languages, distributed
systems, and so on. Product pitches were necessary to establish a
context, but the focus of the talk would be about some interesting
recent technical challenge and how they dealt with it.&lt;/p&gt;
&lt;p&gt;We'd schedule talks only every other month to ease our own burden in
organizing and finding great speakers.&lt;/p&gt;
&lt;p&gt;Once Angelo and I had decided to go forward, the next two challenges
were finding speakers and finding a venue. Thanks to Twitter and
LinkedIn, finding speakers turned out to be the easy part.&lt;/p&gt;
&lt;p&gt;It was harder to find a venue. It was surprisingly challenging to find
a company in NYC with a shared vision that the important thing about
being associated with a meetup like this is to be associated with the
quality of speakers and audience we can bring in by not allowing
transparent product pitches.&lt;/p&gt;
&lt;p&gt;Almost every company in Manhattan with space we spoke with had a
requirement that they have their own speaker each night. That seemed
like a bad idea.&lt;/p&gt;
&lt;p&gt;I think it was especially challenging to find a company willing to
relax about branding requirements like this because we were a new
meetup.&lt;/p&gt;
&lt;p&gt;It was pretty frustrating not to find a sympathetic company with space
in Manhattan. And the only reason we didn't give up was because Angelo
was so adament that this kind of meetup actually happen. It's always
best to start something new with someone else for this exact
reason. You can keep each other going.&lt;/p&gt;
&lt;p&gt;In the end we went with the company that did not insist on their
own speaker or their own branding. A Brooklyn-based company whose CEO
immediately got in touch with me that they wanted to host us, &lt;a href="https://trailofbits.com/"&gt;Trail
of Bits&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="how-it-works"&gt;How it works&lt;/h3&gt;&lt;p&gt;To keep things easy, I set up a web page on my personal site with
information about the meetup. (Eventually we moved this to
&lt;a href="https://nycsystems.xyz/"&gt;nycsystems.xyz&lt;/a&gt;.) I set up a Google Form to
collect emails for a mailing list. And we started posting about the
group on Twitter and LinkedIn.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Very pleased to share the first NYC Systems Talks are taking place next Thursday Feb 22nd 6PM. Hosted by &lt;a href="https://twitter.com/trailofbits?ref_src=twsrc%5Etfw"&gt;@trailofbits&lt;/a&gt;, with &lt;a href="https://twitter.com/paulgb?ref_src=twsrc%5Etfw"&gt;@paulgb&lt;/a&gt; and &lt;a href="https://twitter.com/StefanKarpinski?ref_src=twsrc%5Etfw"&gt;@StefanKarpinski&lt;/a&gt; speaking.&lt;br /&gt;&lt;br /&gt;Space is not infinite, fill out the Google Form if you can attend and would like an invite!&lt;a href="https://t.co/jNssr5v1kJ"&gt;https://t.co/jNssr5v1kJ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1758249063550447768?ref_src=twsrc%5Etfw"&gt;February 15, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;We published the event calendar in advance (an HTML table on the
website) and announced each event's speakers a week in advance of the
event. I'd send another Google Form to the mailing list taking RSVPs
for the night. The first 60 people to respond got a Google Calendar
invite.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/nyc-systems.png" src="/assets/nyc-systems.png" /&gt;&lt;/p&gt;
&lt;p&gt;It's a bit of work, sure, but I'd do anything to avoid Meetup.com.&lt;/p&gt;
&lt;p class="note"&gt;
  It is interesting to see every new systems programming meetup also
  not pick Meetup.com. The only one that went with it, Munich Database
  Meetup, is a revival of an existing group, the Munich NoSQL Meetup
  and presumably they didn't want to give up their subscribers. Though
  most others use lu.ma.
&lt;/p&gt;&lt;p&gt;The mailing list is now about 400+ people. And in each event RSVP we
have a wait list of 20-30 people. Of course although 60 people say Yes
initially, by the time of the event we have typically gotten about 50
people in attendance.&lt;/p&gt;
&lt;p&gt;At each event, Trail of Bits provided screens, chairs, food, and
drink. Angelo had recording equipment so he took over audio/video
capturing (and later editing and publishing).&lt;/p&gt;
&lt;p&gt;After each event we'd publish talk videos to our
&lt;a href="https://www.youtube.com/@NYCSystems"&gt;@NYCSystems&lt;/a&gt; Youtube.&lt;/p&gt;
&lt;h3 id="network-effects"&gt;Network effects&lt;/h3&gt;&lt;p&gt;In March 2024, the TUMuchData folks joined &lt;a href="https://x.com/ifesdjeen"&gt;Alex
Petrov&lt;/a&gt;'s Munich NoSQL Meetup to form the
Munich Database Meetup. In May, &lt;a href="https://twitter.com/thegeeknarrator"&gt;Kaivalya
Apte&lt;/a&gt; and &lt;a href="https://twitter.com/mgill25"&gt;Manish
Gill&lt;/a&gt; started the Berlin Systems Group,
inspired by Alex and the Munich Database Meetup.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I want to start a Berlin Database/Storage systems group, where we have regular meetups, discussions and talks. &lt;br /&gt;&lt;br /&gt;WDYT? &lt;a href="https://twitter.com/mgill25?ref_src=twsrc%5Etfw"&gt;@mgill25&lt;/a&gt; &lt;a href="https://twitter.com/mehd_io?ref_src=twsrc%5Etfw"&gt;@mehd_io&lt;/a&gt; &lt;a href="https://twitter.com/ClickHouseDB?ref_src=twsrc%5Etfw"&gt;@ClickHouseDB&lt;/a&gt; &lt;a href="https://twitter.com/SnowflakeDB?ref_src=twsrc%5Etfw"&gt;@SnowflakeDB&lt;/a&gt; &lt;a href="https://twitter.com/awscloud?ref_src=twsrc%5Etfw"&gt;@awscloud&lt;/a&gt; &lt;a href="https://twitter.com/GoogleDE?ref_src=twsrc%5Etfw"&gt;@GoogleDE&lt;/a&gt; &lt;a href="https://twitter.com/TUBerlin?ref_src=twsrc%5Etfw"&gt;@TUBerlin&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;Can I get some support? Who else would be interested? &lt;a href="https://twitter.com/hashtag/Databases?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#Databases&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;Thanks…&lt;/p&gt;&amp;mdash; Kaivalya Apte - The Geek Narrator (@thegeeknarrator) &lt;a href="https://twitter.com/thegeeknarrator/status/1790782561515372676?ref_src=twsrc%5Etfw"&gt;May 15, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;In May 2024, two PhD students in the San Francisco Bay Area, &lt;a href="https://x.com/ShadajL"&gt;Shadaj
Laddad&lt;/a&gt; and &lt;a href="https://x.com/conor_power23"&gt;Conor
Power&lt;/a&gt;, started the SF Distributed
Systems meetup.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;We’re super excited to be organizing a new SF Distributed Systems meetup NEXT WEEK! Our first meetup features &lt;a href="https://twitter.com/julianhyde?ref_src=twsrc%5Etfw"&gt;@julianhyde&lt;/a&gt; and &lt;a href="https://twitter.com/conor_power23?ref_src=twsrc%5Etfw"&gt;@conor_power23&lt;/a&gt; presenting work on extending SQL and applying algebraic properties, sign up at &lt;a href="https://t.co/d2lLDaQ5iJ"&gt;https://t.co/d2lLDaQ5iJ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Shadaj Laddad (@ShadajL) &lt;a href="https://twitter.com/ShadajL/status/1790767187327889456?ref_src=twsrc%5Etfw"&gt;May 15, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;And in July 2024, &lt;a href="https://twitter.com/shraddhaag"&gt;Shraddha Agrawal&lt;/a&gt;,
&lt;a href="https://twitter.com/anirudhRowjee"&gt;Anirudh Rowjee&lt;/a&gt; and friends kicked
off the first Bengaluru Systems Meetup.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Are you ready, Systems Enthusiasts of Bengaluru?&lt;br /&gt;&lt;br /&gt;Speaking at our first-ever meetup on 6th July, we have:&lt;a href="https://twitter.com/simsimsandy?ref_src=twsrc%5Etfw"&gt;@simsimsandy&lt;/a&gt; with &amp;quot;Learn about the systems that power GenAI applications&amp;quot; and &lt;a href="https://twitter.com/vivekgalatage?ref_src=twsrc%5Etfw"&gt;@vivekgalatage&lt;/a&gt; with &amp;quot;The Browser Backstage: Performance vs Security&amp;quot; &lt;br /&gt;(talks linked below!)&lt;/p&gt;&amp;mdash; Bengaluru Systems Meetup (@BengaluruSys) &lt;a href="https://twitter.com/BengaluruSys/status/1808949578307183060?ref_src=twsrc%5Etfw"&gt;July 4, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;h3 id="suggestions"&gt;Suggestions&lt;/h3&gt;&lt;p&gt;First off, don't pay for anything yourself. Find a company who will
host. At the same time, don't feel the need to give in too much to the
demands of the company. I'd be happy to help you think through how to
talk about the event with companies. It is mutually beneficial for
them to get to give a 5-minute hiring/product pitch and not need to do
extensive branding nor to give a 30-minute product tutorial.&lt;/p&gt;
&lt;p&gt;Second, keep a bit of pressure on speakers to not do an overview talk
and not to do a product pitch. Suggest that they tell the story of
some interesting recent bug or interesting recent feature. What
happened? Why was it hard? What did you learn?&lt;/p&gt;
&lt;p&gt;Focusing on these types of talks will help you get a really
interesting audience.&lt;/p&gt;
&lt;p&gt;I have been continuously surprised and impressed at the folks who show
up for NYC Systems. It's a mix of technical founders in the systems
space, pretty experienced developers in the systems space, graduate
students, and developers of all sorts.&lt;/p&gt;
&lt;p&gt;I am certain we can only get these kinds of folks to show up because
we avoid product pitch-type talks.&lt;/p&gt;
&lt;p&gt;Third, finding speakers is still hard! The best approach so far has
been to individually message folks in industry and academia who hang
out on Twitter. Sending out a public call is easy but doesn't often
pan out. So keep an eye on interesting companies in the area.&lt;/p&gt;
&lt;p&gt;Another avenue I've been thinking about is messaging VC connections to
ask them if they know any engineers/technical founders/CTOs in the
area who could give an interesting technical talk.&lt;/p&gt;
&lt;p&gt;Fourth, speak with other organizers! I finally met Alex Petrov in
person last month and we had a &lt;a href="https://twitter.com/ifesdjeen/status/1806677549038063901"&gt;great
time&lt;/a&gt;
talking about the challenges and joys of organizing really high
quality meetups.&lt;/p&gt;
&lt;p&gt;I'm always happy to chat, DMs are open.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;New post telling a bit of the history behind &lt;a href="https://t.co/NEh1tm8v3Q"&gt;https://t.co/NEh1tm8v3Q&lt;/a&gt;; why it only exists due to folks like &lt;a href="https://twitter.com/georg_kreuzmayr?ref_src=twsrc%5Etfw"&gt;@georg_kreuzmayr&lt;/a&gt; and &lt;a href="https://twitter.com/ngeloxyz?ref_src=twsrc%5Etfw"&gt;@ngeloxyz&lt;/a&gt;; the explosion of systems meetups around the world; and encouragement and suggestions for future organizers!&lt;a href="https://t.co/dwe4TtmXKK"&gt;https://t.co/dwe4TtmXKK&lt;/a&gt; &lt;a href="https://t.co/ZMLkVYdZDJ"&gt;pic.twitter.com/ZMLkVYdZDJ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1809934997442498812?ref_src=twsrc%5Etfw"&gt;July 7, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 07 Jul 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-07-07-systems-meetups.html</guid></item><item><title>Links: Good-tasting berries, other biology news, being the future you want to lead, and more!</title><link>https://jakeseliger.com/2024/07/06/links-good-tasting-berries-other-biology-news-being-the-future-you-want-to-lead-and-more/</link><description>* &amp;#8220;Why America’s Berries Have Never Tasted So Good: Driscoll’s had to figure out how to breed, produce and sell its most flavorful strawberries and raspberries. Now the strategy is starting to bear fruit&amp;#8221; (wsj, $). Good news is underrated and seemingly hardly ever goes viral. I think that even though I&amp;#8217;m very sick and [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Sat, 06 Jul 2024 17:03:53 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/07/06/links-good-tasting-berries-other-biology-news-being-the-future-you-want-to-lead-and-more/</guid></item><item><title>Very sick, and two crises hit at once</title><link>https://jakeseliger.com/2024/07/05/very-sick-and-two-crises-hit-at-once/</link><description>I&amp;#8217;ve been silent because I&amp;#8217;ve been so very sick: swallowing has gotten much harder, and aspiration of food or liquid much more common, to the point that on July 1 I got a new PEG tube put in. Given how much removing the previous tube felt like a triumph, the new installation, although it&amp;#8217;s keeping [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Sat, 06 Jul 2024 02:39:42 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/07/05/very-sick-and-two-crises-hit-at-once/</guid></item><item><title>Rating AI Tools</title><link>https://smcleod.net/2024/07/rating-ai-tools/</link><description>Subjective ratings of the notable AI tools I've tried.</description><author>smcleod.net</author><pubDate>Wed, 03 Jul 2024 04:00:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/07/rating-ai-tools/</guid></item><item><title>Benchmarking rust compilation speedups and slowdowns from sccache and -Zthreads</title><link>https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/</link><description>&lt;p&gt;Just a PSA from one rust developer to another: if you use sccache, take a moment to benchmark a clean build1 of your favorite or current project and verify whether or not having RUSTC_WRAPPER=sccache is doing you any favors. I&amp;#8217;ve &amp;#8230; &lt;a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/"&gt;Benchmarking rust compilation speedups and slowdowns from &lt;code&gt;sccache&lt;/code&gt; and &lt;code&gt;-Zthreads&lt;/code&gt;&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Mon, 01 Jul 2024 21:50:08 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/</guid></item><item><title>A write-ahead log is not a universal part of durability</title><link>http://notes.eatonphil.com/2024-07-01-a-write-ahead-log-is-not-a-universal-part-of-durability.html</link><description>&lt;p&gt;A database does not need a write-ahead log (WAL) to achieve
durability. A database can write its long-term data structure durably
to disk before returning to a client. Granted, this is a bad idea! And
granted, a WAL &lt;b&gt;is&lt;/b&gt; critical for durability &lt;b&gt;by design&lt;/b&gt; in most
databases. But I think it's helpful to understand WALs by
understanding what you &lt;b&gt;could&lt;/b&gt; do without them.&lt;/p&gt;
&lt;p&gt;So let's look at what terrible design we can make for a durable
database that has no write-ahead log. To motivate the idea of, and
build an intuition for, a write-ahead log.&lt;/p&gt;
&lt;p&gt;Thank you to Alex Miller for reviewing a version of this post.&lt;/p&gt;
&lt;p&gt;But first, what is durability?&lt;/p&gt;
&lt;h3 id="durability"&gt;Durability&lt;/h3&gt;&lt;p&gt;Durability happens in the context of a request a client makes to a
data system (either an embedded system like SQLite or RocksDB or a
standalone system like Postgres). Durability is a spectrum of
guarantees the server provides when a client requests to write some
data: that either the request succeeds and the data is safely written
to disk, or the request fails and the client must retry or decide to
do something else.&lt;/p&gt;
&lt;p&gt;It can be difficult to set an absolute definition for durability since
different databases have different concepts of what can go wrong with
disks (also called a "storage fault model"), or they have no concept
at all.&lt;/p&gt;
&lt;p&gt;Let's start from the beginning.&lt;/p&gt;
&lt;h4 id="an-in-memory-database"&gt;An in-memory database&lt;/h4&gt;&lt;p&gt;An in-memory database has no durability at all. Here is pseudo-code
for an in-memory database service.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Throughout this post, for the sake of code brevity, imagine that the
environment is concurrent and that data races around shared mutable
values like &lt;code&gt;db&lt;/code&gt; are protected somehow.&lt;/p&gt;
&lt;h4 id="writing-to-disk"&gt;Writing to disk&lt;/h4&gt;&lt;p&gt;If we want to achieve the most basic level of durability, we can write
this database to a file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kv.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init_from_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;btree.write_to_disk&lt;/code&gt; will call
&lt;a href="https://linux.die.net/man/2/pwrite"&gt;pwrite(2)&lt;/a&gt; under the hood. And
we'll assume it does copy-on-write for only changed pages. So imagine
we have a large database represented by a btree that takes up 10GiB on
disk. With the btree algorithm, if we write a single entry to the
btree, often only a single (often 4Kib) page will get written rather
than all pages (holding all values) in the tree. At the same time, in
the worst case, the entire tree (all 10GiB of data) may need to get
rewritten.&lt;/p&gt;
&lt;p&gt;But this code isn't crash-safe. If the virtual or physical machine
this code is running on reboots, the data we wrote to the file may not
actually be on disk.&lt;/p&gt;
&lt;h4 id="fsync"&gt;fsync&lt;/h4&gt;&lt;p&gt;File data is buffered by the operating system by default. By general
consensus, writing data without flushing the operating system buffer
is not considered durable. Every so often a new database will show up
on Hacker News claiming to beat all other databases on insert speed
until a commenter points out the new database doesn't actually flush
data to disk.&lt;/p&gt;
&lt;p&gt;In other words, the commonly accepted requirement for durability is
that not only do you write data to a file on disk but you
&lt;a href="https://man7.org/linux/man-pages/man2/fsync.2.html"&gt;fsync(2)&lt;/a&gt; the
file you wrote. This forces the operating system to flush to disk any
data it has buffered.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kv.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init_from_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Force a flush&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Furthermore you must not ignore fsync failure. How you deal with fsync
failure is up to you, but exiting immediately with a message that the
user should restore from a backup is sometimes considered acceptable.&lt;/p&gt;
&lt;p&gt;Databases don't like to fsync because it's slow. Many major databases
offer modes where they do not fsync data files before returning a
success to a client. Postgres
&lt;a href="https://www.postgresql.org/docs/current/runtime-config-wal.html#GUC-FSYNC"&gt;offers&lt;/a&gt;
this unsafe mode, though does not default to it and warns against
it. MongoDB offers this unsafe mode but &lt;a href="https://www.mongodb.com/docs/manual/core/journaling/#journaling-process"&gt;does not
default&lt;/a&gt;
to it.&lt;/p&gt;
&lt;p class="note"&gt;
  An earlier version of this post said that MongoDB would unsafely
  flush on an interval. Daniel Gomez Ferro from MongoDB messaged me
  that while the docs are confusing, the default write concern
  "majority" does actually imply "j: true" which means data is
  synchronized (i.e. fsync-ed) before returning a success to a client.
&lt;/p&gt;&lt;p&gt;Almost every database trades safety for performance in some
regard. For example, few databases but SQLite and Cockroach default to
Serializable Isolation. While it is commonly agreed that basically no
level below Serializable Isolation (that all other databases default
to) can be reasoned about. Other databases offer Serializable
Isolation, they just don't default to it. Because it can be slow.&lt;/p&gt;
&lt;h4 id="group-commit"&gt;Group commit&lt;/h4&gt;&lt;p&gt;But let's get back to fsync. One way to amortize the cost of fsync is
to delay requests so that you write data from each of them and then
fsync the data from all requests. This is sometimes called group
commit.&lt;/p&gt;
&lt;p&gt;For example, we could update the database in-memory but have a
background thread serialize to disk and call fsync only every 5ms.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kv.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init_from_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;group_commit_sems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nd"&gt;@background_worker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;group_commit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Durably flush for the group&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group_commit_sems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;group_commit_sems&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It is critical that &lt;code&gt;handle_write&lt;/code&gt; waits to return a success until the
write is durable via fsync.&lt;/p&gt;
&lt;p&gt;So to reiterate, the key idea for durability of a client request is
that you have some version of the client message stored on disk
durably with fsync before returning a success to a client.&lt;/p&gt;
&lt;p&gt;From now on in this post, when you see "durable" or "durability", it
means that the data has been written and fsync-ed to disk.&lt;/p&gt;
&lt;h3 id="optimizing-durable-writes"&gt;Optimizing durable writes&lt;/h3&gt;&lt;p&gt;A key insight is that it's silly to serialize the entire permanent
structure of the database to disk every time a user writes.&lt;/p&gt;
&lt;p&gt;We could just write the user's message itself to an append-only
log. And then only periodically write the entire btree to disk. So
long as we have fsync-ed the append-only log file, we can safely
return to the user even if the btree itself has not yet been written
to disk.&lt;/p&gt;
&lt;p&gt;The additional logic this requires is that on startup we must read the
btree from disk and then replay the log on top of the btree.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kv.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init_from_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;log_f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kv.log&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init_from_disk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_logs_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_log_index&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;group_commit_sems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nd"&gt;@background_worker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;group_commit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;log_accumulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group_commit_sems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log_accumulator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;log_f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_accumulator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;# Write out all log entries at once&lt;/span&gt;
      &lt;span class="n"&gt;log_f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Durably flush wal data&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group_commit_sems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Durably flush db data&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;
  &lt;span class="n"&gt;group_commit_sems&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# This time waiting for only the log to be written and flushed, not the btree.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a write-ahead log!&lt;/p&gt;
&lt;p&gt;Consider a few scenarios. One request writes the smallest key ever
seen. And one request within the same millisecond writes the largest
key ever seen. Writing these to disk on the btree means modifying at
least two pages spread out in space on disk.&lt;/p&gt;
&lt;p&gt;But if we only have to durably write these two messages to a log, they
can likely both be included in the same log page. ("Likely" so long as
key and values are small enough that multiple can fit into the same
page.)&lt;/p&gt;
&lt;p&gt;That is, it's cheaper to write only these small messages representing
the client request to disk. And we save the structured btree
persistence for a less frequent durable write.&lt;/p&gt;
&lt;h3 id="filesystem-and-disk-bugs"&gt;Filesystem and disk bugs&lt;/h3&gt;&lt;p&gt;Sometimes filesystems will write data to the wrong place. Sometimes
disks corrupt data. A solution to both of these is to checksum the
data on write, store the checksum on disk, and confirm the checksum on
read. This combined with a background process called scrubbing to
validate unread data can help you learn quickly when your data has
been corrupted and you must recover from backup.&lt;/p&gt;
&lt;p&gt;MongoDB's default storage engine WiredTiger &lt;b&gt;does&lt;/b&gt; checksum data &lt;a href="https://github.com/wiredtiger/wiredtiger/blob/develop/src/docs/tune-checksum.dox#L3"&gt;by
default&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But some databases famous for integrity do not. Postgres does &lt;a href="https://www.postgresql.org/docs/current/checksums.html"&gt;no data
checksumming&lt;/a&gt;
by default:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;By default, data pages are not protected by checksums, but this can
optionally be enabled for a cluster. When enabled, each data page
includes a checksum that is updated when the page is written and
verified each time the page is read. Only data pages are protected by
checksums; internal data structures and temporary files are not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;SQLite likewise does no checksumming by default. Checksumming is an
&lt;a href="https://www.sqlite.org/cksumvfs.html"&gt;optional extension&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The checksum VFS extension is a VFS shim that adds an 8-byte
checksum to the end of every page in an SQLite database. The checksum
is added as each page is written and verified as each page is
read. The checksum is intended to help detect database corruption
caused by random bit-flips in the mass storage device.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But even this isn't perfect. Disks and nodes can fail completely. At
that point you can only improve durability by introducing redundancy
across disks (and/or nodes), for example, via distributed consensus.&lt;/p&gt;
&lt;h3 id="other-reasons-you-&amp;lt;em&amp;gt;need&amp;lt;/em&amp;gt;-a-wal?"&gt;Other reasons you &lt;em&gt;need&lt;/em&gt; a WAL?&lt;/h3&gt;&lt;p&gt;Some databases (like SQLite) require a write-ahead log to implement
aspects of ACID transactions. But this need not be a requirement for
ACID transactions if you do MVCC (SQLite does not). See my previous
post on &lt;a href="https://notes.eatonphil.com/2024-05-16-mvcc.html"&gt;implementing
MVCC&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;Logical replication (also called change data capture (CDC)) is another
interesting feature that requires a write-ahead log. The idea is that
the log already preserves the exact order and changes that affect the
database's "state machine". So we could copy these changes out of the
system by tracking the write-ahead log, preserving change order, and
apply these changes to a foreign system.&lt;/p&gt;
&lt;p&gt;But again, just CDC is not about durability. It's an ancillary feature
that write-ahead logs make simple.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;A few key points. One, durability primarily matters if it is
established before returning a success to the client. Second, a
write-ahead log is a cheap way to get durability.&lt;/p&gt;
&lt;p&gt;And finally, durability is a spectrum. You need to read the docs for
your database to understand what it does and does not.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Here's a new post about durability and write-ahead logs. Write-ahead logs are used almost everywhere. But to build an intuition for why, it is helpful to imagine what you would do without a WAL. And to explore the meaning of durability.&lt;a href="https://t.co/nzS2pMz22z"&gt;https://t.co/nzS2pMz22z&lt;/a&gt; &lt;a href="https://t.co/m1n9x8CNcp"&gt;pic.twitter.com/m1n9x8CNcp&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1807741130093556098?ref_src=twsrc%5Etfw"&gt;July 1, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 01 Jul 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-07-01-a-write-ahead-log-is-not-a-universal-part-of-durability.html</guid></item><item><title>How I screen CVs</title><link>https://bytepawn.com/how-i-screen-cvs.html</link><description>&lt;p&gt;Screening CVs is a critical step in the hiring process, used to determine which candidates progress further in the hiring funnel. While the approach to CV screening can vary by company and industry, the primary goal remains the same: to identify candidates with the potential to succeed in subsequent interview stages.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="https://www.rchilli.com/hubfs/05How_employer_screen_CV1-1525861528452.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 01 Jul 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/how-i-screen-cvs.html</guid></item><item><title>Organizing AI Engineer World's Fair 2024</title><link>https://www.swyx.io/aiewf-2024</link><description>&gt; note... this only represents my half of running AIE - the intention was to have Ben also write his side on the logistics and sponsorships but we never got round to it and its been a year and a half so... you get what you get, this brain dump of stuff. sorry. 2025's version is https://www.latent.space/p/aiewf-2025-keynotes</description><author>swyx's site RSS Feed</author><pubDate>Sat, 29 Jun 2024 04:52:27 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/aiewf-2024</guid></item><item><title>Links: The longevity of electrics, improving health tech, building the future, and more!</title><link>https://jakeseliger.com/2024/06/26/links-the-longevity-of-electrics-improving-health-tech-building-the-future-and-more/</link><description>* &amp;#8220;Electric Vehicles (EVs) Could Last Nearly Forever.&amp;#8221; Buying or leasing a legacy internal combustion engine (ICE) car now or going forward is nuts because its value is going to drop to near-zero within a few years. Granted there are a lot of obvious things that people don&amp;#8217;t see coming. * Making sense of honor culture. [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Wed, 26 Jun 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/06/26/links-the-longevity-of-electrics-improving-health-tech-building-the-future-and-more/</guid></item><item><title>Time Programming for Lawyers and Jurors</title><link>https://specbranch.com/posts/time-for-jurors/</link><description>&lt;p&gt;I often like to have streams of trials playing in the background when I am working, and
the recent trial of interest was the trial of Karen Read, who was accused of murder.
One issue in this case was a conflict between two timestamps logged by different apps
on a potential suspect's phone.  Apple health had logged the person climbing flights of
stairs at a given time, and at the same timestamp, a log from the app Waze indicated
that the person was driving.  This seems impossible, but these apps used different
time sources on the phone.&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Wed, 26 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/time-for-jurors/</guid></item><item><title>Debugging Slow Ubuntu Boot</title><link>https://jodavaho.io/lists/list-cli-ubuntu-boot.html</link><description>&lt;p&gt;Created with chatgpt + my own notes.&lt;/p&gt;
&lt;h3 id="system-analysis"&gt;System Analysis&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check Boot Time&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;systemd-analyze&lt;/code&gt; to see the total boot time.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;systemd-analyze blame&lt;/code&gt; to see which services take the longest to start.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;systemd-analyze critical-chain&lt;/code&gt; to understand the critical path.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check Boot Logs&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Review the logs with &lt;code&gt;journalctl -b&lt;/code&gt; to identify errors and warnings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="hardware-and-biosuefi"&gt;Hardware and BIOS/UEFI&lt;/h3&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Update BIOS/UEFI&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensure your BIOS/UEFI is up to date.&lt;/li&gt;
&lt;li&gt;Check BIOS/UEFI settings for any boot-related options (e.g., Fast Boot).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check Hardware&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensure all hardware components (RAM, SSD/HDD) are functioning correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="services-and-applications"&gt;Services and Applications&lt;/h3&gt;
&lt;ol start="5"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable Unnecessary Services&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;systemctl disable &amp;lt;service&amp;gt;&lt;/code&gt; to disable unneeded services.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;systemctl mask &amp;lt;service&amp;gt;&lt;/code&gt; to prevent services from starting.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Remove Unnecessary Applications&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;sudo apt-get remove &amp;lt;application&amp;gt;&lt;/code&gt; to uninstall unneeded applications.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;sudo apt-get purge &amp;lt;application&amp;gt;&lt;/code&gt; for a thorough cleanup.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="file-system-and-storage"&gt;File System and Storage&lt;/h3&gt;
&lt;ol start="7"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check Disk Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;df -h&lt;/code&gt; to check for any partitions that are almost full.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;du -sh /path/to/directory&lt;/code&gt; to find large files and directories.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimize File System&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;sudo e4defrag /&lt;/code&gt; for ext4 file system defragmentation.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;sudo tune2fs -O fast_commit /dev/sdX&lt;/code&gt; for ext4 optimizations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check for Disk Errors&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;sudo fsck /dev/sdX&lt;/code&gt; to check and repair filesystem errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="kernel-and-drivers"&gt;Kernel and Drivers&lt;/h3&gt;
&lt;ol start="10"&gt;
&lt;li&gt;&lt;strong&gt;Update Kernel and Drivers&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;sudo apt-get update&lt;/code&gt; and &lt;code&gt;sudo apt-get upgrade&lt;/code&gt; to ensure you have the latest updates.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ubuntu-drivers autoinstall&lt;/code&gt; to install the recommended drivers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="startup-and-grub"&gt;Startup and Grub&lt;/h3&gt;
&lt;ol start="11"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimize GRUB&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Edit &lt;code&gt;/etc/default/grub&lt;/code&gt; and set &lt;code&gt;GRUB_TIMEOUT&lt;/code&gt; to a lower value (e.g., 2 seconds).&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;sudo update-grub&lt;/code&gt; after making changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reduce Kernel Load&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;/etc/default/grub&lt;/code&gt;, add &lt;code&gt;quiet splash&lt;/code&gt; to &lt;code&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/code&gt; for a quieter boot.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="swap-and-memory"&gt;Swap and Memory&lt;/h3&gt;
&lt;ol start="13"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimize Swap Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adjust &lt;code&gt;vm.swappiness&lt;/code&gt; in &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; to a lower value (e.g., 10).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check Memory Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;free -h&lt;/code&gt; to check RAM usage.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;top&lt;/code&gt; or &lt;code&gt;htop&lt;/code&gt; to monitor active processes and their memory consumption.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="network-and-connectivity"&gt;Network and Connectivity&lt;/h3&gt;
&lt;ol start="15"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable Unused Network Services&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disable services like &lt;code&gt;NetworkManager-wait-online.service&lt;/code&gt; if not needed.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;systemctl disable NetworkManager-wait-online.service&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimize Network Settings&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Edit &lt;code&gt;/etc/network/interfaces&lt;/code&gt; to configure network interfaces for faster boot.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="miscellaneous"&gt;Miscellaneous&lt;/h3&gt;
&lt;ol start="17"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable Unused TTYs&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Edit &lt;code&gt;/etc/systemd/logind.conf&lt;/code&gt; and set &lt;code&gt;NAutoVTs=1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use a Lightweight Desktop Environment&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consider switching to a lighter desktop environment like Xfce or LXDE.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Profile Your Boot&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;systemd-analyze plot &amp;gt; bootchart.svg&lt;/code&gt; to create a visual representation of the boot process.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="regular-maintenance"&gt;Regular Maintenance&lt;/h3&gt;
&lt;ol start="20"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clean Package Cache&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;sudo apt-get clean&lt;/code&gt; to clear the package cache.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Remove Old Kernels&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;sudo apt-get autoremove&lt;/code&gt; to remove old kernel versions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>jodavaho.io</author><pubDate>Sun, 23 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/lists/list-cli-ubuntu-boot.html</guid></item><item><title>2024 Midyear Project Update</title><link>https://benovermyer.com/blog/2024/06/2024-midyear-project-update/</link><description>&lt;p&gt;2024 has been a hectic year so far. Most recently, we very nearly moved to
western New York, but a series of unfortunate events and observations made
us reconsider at the last second and decide to continue renting in Wilmington.&lt;/p&gt;
&lt;p&gt;I haven't made any progress on reigniting my morning jogging habit. However,
once my leg heals (dermatology thing, don't ask), I intend to start it up again.
I completely halted my weight training after my doctor told me "it counts as
exercise, I guess." That just took all the wind out of my sails.&lt;/p&gt;
&lt;p&gt;My alcohol-free phases are on track. January and March were alcohol-free, and
September will be the third for this year.&lt;/p&gt;
&lt;p&gt;Silver Gryphon Games has stagnated. I finished the Aether second edition
manuscript and got cover art for both it and Ingenium's second edition. However,
Aether and Aethermancy are both stalled, waiting on Kevin. Considering how
crazy his life is right now - as I'm writing this his hometown is flooded so
bad he can't go home - I don't blame him one bit. I'll have to focus on getting
Ingenium out the door.&lt;/p&gt;
&lt;p&gt;Iron Arachne has had some minor tweaks to the star system and planet generators,
and one new generator in the form of a Velgarth gift generator. Other than that,
it hasn't had any new sci-fi generator updates. If I want to complete this goal,
I'll need to work on something in the latter half of this year.&lt;/p&gt;
&lt;p&gt;Two of my three planned trips are behind me. The resort vacation in Cancun was
kind of like a light version of "The Hangover," so we'll just leave it at that.
The eclipse-viewing party was awesome; I will probably never get to see an
eclipse like that again in my lifetime.&lt;/p&gt;
&lt;p&gt;The final planned trip for this year is in August. I also drove to New York to
look at property and explore in the first half of June, and I will be flying up
to Minnesota in late August for 2D Con. That's plenty of trips, I think! I
didn't journal about any of them so far.&lt;/p&gt;
&lt;p&gt;However, I have been consistent in journaling about my day job every Friday. I
intend to turn this into a big year-end review, but that won't be public. At
least, not in its entirety, anyway.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 23 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2024/06/2024-midyear-project-update/</guid></item><item><title>2024 Jun Summary</title><link>https://jodavaho.io/now/2024-jun.html</link><description>&lt;p&gt;Wife and I are insanely busy. We both work 50+ hours a week. The kids don&amp;rsquo;t seem to mind - still hilarious, happy, and energetic. Our nanny is the GOAT.&lt;/p&gt;
&lt;p&gt;I have not been playing many games lately. I&amp;rsquo;ve been doing a lot of yard work,
and we&amp;rsquo;ve been spending a lot of time at the cabin. I&amp;rsquo;ve been working a lot,
too. When I do play, I prefer &lt;a href="https://store.steampowered.com/app/553850/HELLDIVERS_2/"&gt;Helldivers
2&lt;/a&gt; by &lt;a href="https://arrowheadgamestudios.com"&gt;arrow head games&lt;/a&gt;.
It&amp;rsquo;s insane. Check out some videos on
youtube.&lt;/p&gt;
&lt;p&gt;The back yard is coming together! We built the deck, landscaped the top of the
terrace, and put in the playground. Next: build another terrace, plant 5 more
trees, and put in a fire pit.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://shield.ai"&gt;Work at Shield&lt;/a&gt; is pretty nuts. Busy, for sure. But mostly
for &lt;em&gt;other&lt;/em&gt; folks. I am still getting up to speed. &lt;em&gt;Eventually&lt;/em&gt; I&amp;rsquo;ll manage the
mission planning stack for real, but for now it&amp;rsquo;s that awkward phase where I am
not the best person for any task, so until deadlines slack a little, I&amp;rsquo;ll let
the cooks cook and pick up side tasks.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been writing a ton of Rust for side projects (attention HR: This does not
involve Shield stuff). This marks 4 years of Rust for me.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Tue, 18 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/now/2024-jun.html</guid></item><item><title>One year of solo dev, wrapping up the grant-funded work</title><link>https://www.marginalia.nu/log/a_107_nlnext/</link><description>&lt;p&gt;&lt;a href="https://www.marginalia.nu/log/83_full_time/"&gt;A year ago&lt;/a&gt; I walked out of the office for the last time. I handed in my corpo laptop, said some good-byes, and since then I have been my own boss.&lt;/p&gt;
&lt;p&gt;This first year has been funded by an &lt;a href="https://nlnet.nl/" rel="external noopener"&gt;NLnet&lt;/a&gt; grant, which I&amp;rsquo;m in the midst of wrapping up. As of now, the work is all done, the final request for payment has been sent.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a similar last-day-of-school levity to both these events.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 18 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_107_nlnext/</guid></item><item><title>The limitations of LLMs, or why are we doing RAG?</title><link>http://notes.eatonphil.com/2024-06-17-limitations-llm-or-why-are-we-doing-rag.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/limitations-llm-or-why-are-we-doing-rag"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 17 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-06-17-limitations-llm-or-why-are-we-doing-rag.html</guid></item><item><title>Writing a simple Javascript async message queue server - Part III</title><link>https://bytepawn.com/writing-a-simple-javascript-async-message-queue-server-part-iii.html</link><description>&lt;p&gt;In this final post on the toy Javascript async message queue server implementation, I make it compatible with the Python version. I use the library of unit tests developed previously to ensure identical behaviour between the two codebases.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/js-mq-advanced.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 17 Jun 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-javascript-async-message-queue-server-part-iii.html</guid></item><item><title>ESPHome Server in Python</title><link>https://www.petekeen.net/esphome-server-in-python/</link><description>&lt;p&gt;Last year I installed holiday lights on my house (&lt;a href="https://www.petekeen.net/wled-eufy-permanent-outdoor-lights"&gt;previously&lt;/a&gt;).
        At the time I chose a controller that had the following key qualities: a) cheap, b) worked with WLED, c) cheap.&lt;/p&gt;
        &lt;p&gt;After using it for a season I discovered that the reason why this controller is so cheap is because it uses an ESP8266, which is &lt;em&gt;fine&lt;/em&gt;,
        but it doesn't play well with my setup.
        For example, if I enable the &lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt; integration the controller falls over after a few hours.
        It also reboots for unknowable reasons sometimes and I would come home to find the lights in their default orange color.&lt;/p&gt;
        &lt;p&gt;I probably could have fixed this with a more powerful controller.
        I even bought a neat ESP32 controller with a built-in wide range voltage regulator but never got around to setting it up.&lt;/p&gt;
        &lt;p&gt;Loosely, what I want is:&lt;/p&gt;
        &lt;ol&gt;
        &lt;li&gt;Control the lights without random reboots&lt;/li&gt;
        &lt;li&gt;Easy Home Assistant integration&lt;/li&gt;
        &lt;li&gt;Easy customization&lt;/li&gt;
        &lt;li&gt;No Wi-Fi&lt;/li&gt;
        &lt;li&gt;Use hardware that I already have&lt;/li&gt;
        &lt;li&gt;Tailscale, ideally&lt;/li&gt;
        &lt;li&gt;Learn some stuff&lt;/li&gt;
        &lt;/ol&gt;
        &lt;p&gt;I could have gotten the first two using a more powerful ESP32 module.
        Third could be done with &lt;a href="https://esphome.io/"&gt;ESPHome&lt;/a&gt;.
        Four and five are contradictory while staying within the constraint of an ESP32-based system.&lt;/p&gt;
        &lt;p&gt;Also last year, I built a little box that controls the power for my 3D printer with a Raspberry Pi Pico and a second Klipper instance (&lt;a href="https://www.petekeen.net/secondary-klipper-for-power-control"&gt;previously&lt;/a&gt;) so naturally I tried to get Klipper to fit in this non-3d-printer shaped hole.
        I tried so hard.&lt;/p&gt;
        &lt;p&gt;On the surface, Klipper appears to do everything that I want (control addressable LEDs, kind of customizable) but it makes no compromises in wanting to be a 3D printer controller.
        Most of the firmware is dedicated to running a motion controller, there's a lot of emphasis on scheduling things to happen in the near future, and there's a global &lt;code&gt;printer&lt;/code&gt; object.
        Importantly for my purposes, there's no built-in way to set up a digital input without declaring it a &lt;code&gt;button&lt;/code&gt;.&lt;/p&gt;
        &lt;p&gt;It's fine. Klipper is fine. It's just not built to be a generic IO platform.&lt;/p&gt;
        &lt;p&gt;So, what's a reasonable rational person to do?&lt;/p&gt;
        &lt;h2 id="write-an-esphome-protocol-server" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-server-in-python/#write-an-esphome-protocol-server"&gt;Write an ESPHome Protocol Server&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;Of course.&lt;/p&gt;
        &lt;p&gt;There are essentially three ways to get arbitrary devices and entities to show up automatically in Home Assistant.&lt;/p&gt;
        &lt;p&gt;First, one can &lt;a href="https://developers.home-assistant.io/docs/creating_component_index/"&gt;write a Home Assistant integration&lt;/a&gt;.
        This is fine and good but it doesn't work for me because my devices are far away from the VM that Home Assistant runs in.&lt;/p&gt;
        &lt;p&gt;Second, there's &lt;a href="https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery"&gt;MQTT autodiscovery&lt;/a&gt;.
        I know this works because it's how my Zigbee devices integrate with HA, but I just could not make any of the &lt;a href="https://github.com/unixorn/ha-mqtt-discoverable"&gt;existing&lt;/a&gt; &lt;a href="https://github.com/alexbarcelo/hass-mqtt-things"&gt;generic&lt;/a&gt; autodiscovery libraries work consistently.
        Usually I would end up with a bunch of duplicate MQTT devices and then HA would get confused.&lt;/p&gt;
        &lt;p&gt;Third, there's ESPHome.
        ESPHome is a firmware for ESP modules (think: small devices with wifi like plugs, air quality monitors, etc).
        ESPHome belongs to the Open Home Foundation, same as Home Assistant, so it has commercial support and a first class HA integration.
        I already have a bunch of ESPHome devices running in my house, so it seems like a pretty natural fit.&lt;/p&gt;
        &lt;p&gt;The normal and ordinary way of using ESPHome is to write some YAML config that ESPHome compiles into a firmware for your device, then you flash the device and HA sets itself up to interact with the entities you described in YAML. What I want to do is just that last bit, the part where I can tell HA what entities I have and it sets up UI for me.&lt;/p&gt;
        &lt;p&gt;HA talks to ESPHome over what they call their &amp;quot;native API&amp;quot;.
        The native API is a TCP-based streaming protocol where the ESPHome device is the server and Home Assistant is the client.
        They exchange protocol buffer encoded messages over either plain TCP or with a Noise-based encryption scheme.&lt;/p&gt;
        &lt;p&gt;Over the last week or so I built a Python implementation of that protocol named &lt;a href="https://github.com/peterkeen/aioesphomeserver"&gt;aioesphomeserver&lt;/a&gt;, bootstrapping off of the official &lt;a href="https://github.com/esphome/aioesphomeapi"&gt;aioesphomeapi&lt;/a&gt; client library that HA uses.&lt;/p&gt;
        &lt;h2 id="a-minimal-example" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-server-in-python/#a-minimal-example"&gt;A Minimal Example&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;Here's a very simple example of what &lt;code&gt;aioesphomeserver&lt;/code&gt; looks like:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-python"&gt;import asyncio
        
        from aioesphomeserver import (
        Device,
        SwitchEntity,
        BinarySensorEntity,
        EntityListener,
        )
        
        class SwitchListener(EntityListener):
        async def handle(self, key, message):
        sensor = self.device.get_entity(&amp;quot;test_binary_sensor&amp;quot;)
        if sensor != None:
        await sensor.set_state(message.state)
        
        device = Device(
        name = &amp;quot;Test Device&amp;quot;,
        mac_address = &amp;quot;AC:BC:32:89:0E:C9&amp;quot;,
        )
        
        device.add_entity(
        BinarySensorEntity(
        name = &amp;quot;Test Binary Sensor&amp;quot;,
        )
        )
        
        device.add_entity(
        SwitchEntity(
        name = &amp;quot;Test Switch&amp;quot;,
        )
        )
        
        device.add_entity(
        SwitchListener(
        name=&amp;quot;_listener&amp;quot;,
        entity_id=&amp;quot;test_switch&amp;quot;
        )
        )
        
        asyncio.run(device.run())
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;From the top, we import a bunch of stuff and then create a class that listens for messages from the device (the &lt;code&gt;handle&lt;/code&gt; method).
        Then, we set up a device with a name and a fake MAC address. &lt;code&gt;Device&lt;/code&gt; can generate a random one for you but it doesn't persist, so if you want this device to stick around in HA you should declare a static MAC.&lt;/p&gt;
        &lt;p&gt;We then add some entities to it: a binary sensor, a switch, and an instance of our switch listener configured for &lt;code&gt;Test Switch&lt;/code&gt;.&lt;/p&gt;
        &lt;p&gt;Finally, we start the &lt;code&gt;asyncio&lt;/code&gt; event loop.&lt;/p&gt;
        &lt;p&gt;With just that, you get the ESPHome web UI:&lt;/p&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="ESPHome Web UI" class="thumbnail" src="https://www.petekeen.net/images/esphome-server-in-python/esphome.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;p&gt;Adding the device to Home Assistant you'll see this:&lt;/p&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="Home Assistant view" class="thumbnail" src="https://www.petekeen.net/images/esphome-server-in-python/home-assistant.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;h2 id="aio-esphome-server-architecture" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-server-in-python/#aio-esphome-server-architecture"&gt;AIO ESPHome Server Architecture&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;I tried to follow the spirit of ESPHome's architecture when writing the server.&lt;/p&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="Home Assistant device view for the test device" class="thumbnail" src="https://www.petekeen.net/images/esphome-server-in-python/architecture.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;p&gt;The &lt;code&gt;Device&lt;/code&gt; is a central registrar for &lt;code&gt;Entity&lt;/code&gt;s and serves as a message hub.
        The native API server and web server are entities that plug into the message bus, as are things like &lt;code&gt;SwitchEntity&lt;/code&gt; and &lt;code&gt;BinarySensorEntity&lt;/code&gt;.
        Everything is async using Python's &lt;code&gt;asyncio&lt;/code&gt;.&lt;/p&gt;
        &lt;p&gt;Any entity with a &lt;code&gt;run&lt;/code&gt; method will automatically be scheduled as a task at startup.&lt;/p&gt;
        &lt;h2 id="a-production-example" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-server-in-python/#a-production-example"&gt;A Production Example&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;The development case for this library has been driving the addressable LEDs on my house.
        I found a project named &lt;a href="https://github.com/execuc/u2if"&gt;u2if&lt;/a&gt; that turns a Raspberry Pi Pico into a USB peripheral that provides a bunch of fun stuff: GPIO, I2C, SPI, PWM, ADC as well as an addressable LED driver for WS2812-compatible lights.
        The fun wrinkle of the light driver is that it offloads the bitstream generation to the Pico's PIO coprocessors.&lt;/p&gt;
        &lt;p&gt;I &lt;a href="https://github.com/peterkeen/u2if"&gt;forked u2if&lt;/a&gt; and added a few things:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;RGBW support, which was already in the codebase but not available&lt;/li&gt;
        &lt;li&gt;Support for the Pico clone boards I have (SparkFun Pro Micro RP2040)&lt;/li&gt;
        &lt;li&gt;A set of effects for Neopixel along with a console-mode simulator to use while developing&lt;/li&gt;
        &lt;li&gt;A Docker image that bundles the firmware and the Python library&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;This deployment consists of:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;A Dell Wyse 3040 thin client running Alpine Linux that already handles Z-Wave for the garage&lt;/li&gt;
        &lt;li&gt;SparkFun Pro Micro RP2040 running the u2if firmware connected over USB&lt;/li&gt;
        &lt;li&gt;Two channels of RS485 transceivers so I can get the very fast, very unforgiving light control signals 40 feet from where the 3040 is mounted to the wall to where the light power injector lives.&lt;/li&gt;
        &lt;/ul&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="Project spread out on my desk, including a Dell Wyse 3040 thin client, the USB interface board in a 3d printed box, the RS485 receiver board hooked up to a spool of LEDs via a breadboard." class="thumbnail" src="https://www.petekeen.net/images/esphome-server-in-python/desk-layout.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;p&gt;Here is the full script that I'm using to drive the addressable lights on my house:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-python"&gt;import asyncio
        
        from machine import WS2812B
        from neopixel.effects import StaticEffect, BlendEffect, TwinkleEffect
        
        from aioesphomeserver import (
        Device,
        LightEntity,
        LightStateResponse,
        EntityListener,
        )
        
        from aioesphomeapi import LightColorCapability
        
        class LightStrip(EntityListener):
        def __init__(self, *args, strings=[], effects={}, **kwargs):
        super().__init__(*args, **kwargs)
        self.strings = strings
        self.num_pixels = sum([s[1] for s in strings])
        self.effects = effects
        
        self.current_effect_name = None
        self.current_effect = StaticEffect(count=self.num_pixels)
        self.white_brightness = 0.0
        self.color_brightness = 0.0
        
        async def handle(self, key, message):
        if type(message) != LightStateResponse:
        return
        
        await self.device.log(1, f&amp;quot;message.effect: '{message.effect}'&amp;quot;)
        
        if message.effect != &amp;quot;&amp;quot; and message.effect != self.current_effect_name:
        if message.effect in self.effects:
        self.current_effect_name = message.effect
        self.current_effect = self.effects[message.effect](self.num_pixels, message)
        self.current_effect.next_frame()
        
        if self.current_effect:
        self.current_effect.update(message)
        
        self.color_brightness = message.color_brightness
        self.white_brightness = message.brightness
        
        if message.state == False:
        self.color_brightness = 0.0
        self.white_brightness = 0.0
        
        def render(self):
        pixels = []
        
        for i in range(self.num_pixels):
        color = self.current_effect.pixels[i]
        
        pixel = [
        int(color[0] * 255.0 * self.color_brightness),
        int(color[1] * 255.0 * self.color_brightness),
        int(color[2] * 255.0 * self.color_brightness),
        int(color[3] * 255.0 * self.white_brightness),
        ]
        
        pixels.append(pixel)
        
        # partition strings
        # write to each string
        cur = 0
        for string, length in self.strings:
        last = cur + length - 1
        string.write(pixels[cur:last])
        cur = last + 1
        
        async def run(self):
        while True:
        self.current_effect.next_frame()
        self.render()
        await asyncio.sleep(1/24.0)
        
        
        device = Device(
        name = &amp;quot;Garage Stuff&amp;quot;,
        mac_address = &amp;quot;7E:85:BA:7E:38:07&amp;quot;,
        model = &amp;quot;Garage Stuff&amp;quot;
        )
        
        device.add_entity(LightEntity(
        name=&amp;quot;Front Lights&amp;quot;,
        color_modes=[LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS | LightColorCapability.RGB | LightColorCapability.WHITE],
        effects=[&amp;quot;Static&amp;quot;, &amp;quot;Twinkle&amp;quot;],
        ))
        
        def make_twinkle_effect(count, state):
        return BlendEffect(
        TwinkleEffect(count=count, include_white_channel=True),
        StaticEffect(count=count, color=[state.red, state.green, state.blue, state.white], include_white_channel=True),
        mode='lighten',
        include_white_channel=True,
        )
        
        device.add_entity(LightStrip(
        name = &amp;quot;_front_lights_strip&amp;quot;,
        entity_id = &amp;quot;front_lights&amp;quot;,
        strings = [(WS2812B(23, rgbw = True, color_order=&amp;quot;GRBW&amp;quot;), 20)],
        effects={
        &amp;quot;Static&amp;quot;: lambda count, state: StaticEffect(count=count, color=[state.red, state.green, state.blue, state.white], include_white_channel=True),
        &amp;quot;Twinkle&amp;quot;: make_twinkle_effect,
        },
        ))
        
        asyncio.run(device.run())
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;The structure is basically the same as the minimal example.
        We import some stuff, we set up an &lt;code&gt;EntityListener&lt;/code&gt; class, and then we set up a &lt;code&gt;Device&lt;/code&gt; with a &lt;code&gt;LightEntity&lt;/code&gt; and an instance of the listener .&lt;/p&gt;
        &lt;p&gt;In this case, the listener listens for state responses from a &lt;code&gt;Light&lt;/code&gt; entity and renders pixels according to a set of effects.
        It also has a &lt;code&gt;run&lt;/code&gt; method that renders the current effect out every 1/24th of a second.&lt;/p&gt;
        &lt;h2 id="should-you-use-this%3F" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-server-in-python/#should-you-use-this%3F"&gt;Should you use this?&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;I don't know!&lt;/p&gt;
        &lt;p&gt;If your constraints match mine, maybe it'd be helpful.
        If you want to expose a thing to Home Assistant and would rather have it show up as an ESPHome device rather than, say, writing your own HA integration or messing with MQTT or writing RESTful API handlers, this would probably be useful.&lt;/p&gt;
        &lt;p&gt;That said, I think if your use case fits within ESPHome proper you should use that.
        ESPHome has built in drivers for so many things and is going to be better supported (i.e. people are paid to work on it).&lt;/p&gt;
        &lt;p&gt;Pretty neat though, eh?&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sat, 15 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/esphome-server-in-python/</guid></item><item><title>Writing unit tests for the async message queue server</title><link>https://bytepawn.com/writing-unit-tests-for-the-async-message-queue-server.html</link><description>&lt;p&gt;I write a small library of unit tests for the message queue servers.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/unittests.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 15 Jun 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-unit-tests-for-the-async-message-queue-server.html</guid></item><item><title>Confusion is a muse</title><link>http://notes.eatonphil.com/2024-06-14-confusion-is-a-muse.html</link><description>&lt;p&gt;Some of the most interesting technical blog posts I read come from,
and a common reason for posts I write is, confusion. You're at work
and you start asking questions that are difficult to answer. You spend
a few hours or a day trying to get to the bottom of things.&lt;/p&gt;
&lt;p&gt;If you ask a question to very experienced and successful developers at
work, they have a tendency not to give context and to simplify things
down to a single answer. This may be a good way to make business
decisions. (One can't afford to waste an eternity considering
everything indefinitely.) But accepting an answer you don't understand
is actively harmful for building intuition.&lt;/p&gt;
&lt;p&gt;Certainly, sometimes not accepting an answer can be irritating. You'll
have to figure that out.&lt;/p&gt;
&lt;p&gt;But beyond "go along to get along", another reason we don't pursue
what we're confused about is because we're embarrassed that we're
confused in the first place. What's worse, the embarrassment we feel
naturally grows the more experienced we get. "I've got this job title,
I don't want to seem like I don't know what you mean."&lt;/p&gt;
&lt;p&gt;But if you fight the embarrassment and pursue your confusion
regardless, you'll likely figure something very interesting
out. Moreover, you will probably not have been the only person who was
confused. At least personally it is quite rare that I am confused
about something and no one else is.&lt;/p&gt;
&lt;p&gt;So pay attention when you get confused, and consider why it
happened. What did you expect to be the case, and how did reality
differ? Explore the angles and the options. When you finally
understand, think about what led you to that understanding.&lt;/p&gt;
&lt;p&gt;Write it down. Put it into an internal Markdown doc, an internal
Atlassian doc, an internal Google Slides page, whatever. The medium
doesn't matter.&lt;/p&gt;
&lt;p&gt;This entire process doesn't come easily. We feel embarrassed. We
aren't used to lingering on something we're confused by. We aren't
used to writing things down.&lt;/p&gt;
&lt;p&gt;But if you can make yourself pause every once in a while and think
about what you (or someone around you) got confused by, and if you can
force yourself to stop getting embarrassed by what you got confused
by, and if you can write down the background and the reasoning that
led to your ultimate understanding, you're going to have something
pretty interesting to talk about.&lt;/p&gt;
&lt;p&gt;You'll contribute to the growth and intuition of your colleagues. And
you'll never run out of things to write about.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Confusion is embarrassing. But fight that feeling, and dig into why you're confused. And write it down.&lt;br /&gt;&lt;br /&gt;You won't be the only one who was confused. And you'll tend to have something pretty interesting to talk about.&lt;a href="https://t.co/IdX1nGBheR"&gt;https://t.co/IdX1nGBheR&lt;/a&gt; &lt;a href="https://t.co/KzTjqMxw6u"&gt;pic.twitter.com/KzTjqMxw6u&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1801644601536664014?ref_src=twsrc%5Etfw"&gt;June 14, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Fri, 14 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-06-14-confusion-is-a-muse.html</guid></item><item><title>Making a bignum library for fun</title><link>https://austinhenley.com/blog/bignum1.html</link><description>&lt;a href="https://austinhenley.com/blog/bignum1.html"&gt;https://austinhenley.com/blog/bignum1.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Fri, 14 Jun 2024 01:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/bignum1.html</guid></item><item><title>Five Nine Problems</title><link>https://specbranch.com/posts/five-nines/</link><description>&lt;p&gt;A guilty pleasure of mine is the pursuit of perfection.  It is certainly a vice in most
contexts, but there are some problems whose solutions demand a measure of perfection.
These are problems that I will refer to as &amp;quot;5-9 problems&amp;quot;: problems whose solutions need
five 9's (or more) in some dimension.  Usually, those nines are correctness of some kind,
but they can also be availability or for some systems, speed.&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Thu, 13 Jun 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/five-nines/</guid></item><item><title>Testing randomness extractors</title><link>https://bytepawn.com/testing-randomness-extractors.html</link><description>&lt;p&gt;I apply NIST's suite of statistical tests to my randomness extractor implementations from the previous posts.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Coin flip" src="/images/random01.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 02 Jun 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/testing-randomness-extractors.html</guid></item><item><title>Gollama: Ollama Model Manager</title><link>https://smcleod.net/2024/06/gollama-ollama-model-manager/</link><description>Gollama is a Go-based client for Ollama for managing models.</description><author>smcleod.net</author><pubDate>Sat, 01 Jun 2024 04:00:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/06/gollama-ollama-model-manager/</guid></item><item><title>How I run a software book club</title><link>http://notes.eatonphil.com/2024-05-30-how-i-run-book-clubs.html</link><description>&lt;p&gt;I've been running software book clubs almost continuously since last
summer, about 12 months ago. We read through &lt;a href="https://eatonphil.com/2023-ddia.html"&gt;Designing Data-Intensive
Applications&lt;/a&gt;, &lt;a href="https://eatonphil.com/2023-database-internals.html"&gt;Database
Internals&lt;/a&gt;,
&lt;a href="https://eatonphil.com/2024-systems-performance.html"&gt;Systems
Performance&lt;/a&gt;, and
we just started &lt;a href="https://eatonphil.com/2024-understanding-software-dynamics.html"&gt;Understanding Software
Dynamics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The DDIA discussions were in-person in NYC with about 5-8 consistent
attendees. The rest have been over email with 300, 500, and 600
attendees.&lt;/p&gt;
&lt;p&gt;This post is for folks who are interested in running their own book
club. None of these ideas are novel. I co-opted the best parts I saw
from other people running similar things. And hopefully you'll improve
on my experience too, should you try.&lt;/p&gt;
&lt;p&gt;Despite the length of this post running a book club takes almost no
noticeable effort, other than when I need to select and confirm
discussion leaders. It is the limited-effort-required to thank that
I've kept up the book clubs so consistently.&lt;/p&gt;
&lt;h3 id="google-groups"&gt;Google Groups&lt;/h3&gt;&lt;p&gt;I run the virtual book clubs over email. I create a Google Group and
tell people to send me their email for an invite. I use a Google Form
to collect emails since I get many. If you're doing a small group
book club you can just collect member emails directly.&lt;/p&gt;
&lt;p&gt;In the Google Form I ask people to volunteer to lead discussion for a
chapter (or chapters). And I ask for a Twitter/GitHub/LinkedIn
account.&lt;/p&gt;
&lt;p&gt;When I've gotten enough responses I go through the list and check
Twitter/GitHub/LinkedIn info to find people who might have a
particularly interesting perspective to lead a discussion.&lt;/p&gt;
&lt;p&gt;"Lead a discussion" sounds formal but I mean anything but. All I am
looking for is someone to start a new Google Group thread each week
and for them to share their thoughts.&lt;/p&gt;
&lt;p&gt;For example a discussion leader might share:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What they liked about the chapter&lt;/li&gt;
&lt;li&gt;Something new they learned from the chapter&lt;/li&gt;
&lt;li&gt;A story about their work that the chapter reminded them of&lt;/li&gt;
&lt;li&gt;A little project they hacked on, inspired by reading the chapter&lt;/li&gt;
&lt;li&gt;A paper or YouTube video this chapter reminded them of&lt;/li&gt;
&lt;li&gt;Something from the chapter that was confusing&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The "discussion leader" has no responsibility for remaining in the
discussion after posting the thread. There just isn't an easy way to
say "person who kicks off discussion" than to call them a "discussion
leader".&lt;/p&gt;
&lt;p&gt;By the way, I didn't do discussion leaders for the first book club,
reading DDIA. And that book club took noticeably more effort. Because
I organized it, I was effectively the discussion leader every
time. Having discussion leaders disperses the effort of the book
club. And I think it makes the club much more interesting.&lt;/p&gt;
&lt;h4 id="sparknotes-ification"&gt;SparkNotes-ification&lt;/h4&gt;&lt;p&gt;One thing I noticed happening often was that the discussion leader
might do a large summary of the chapter. I greatly appreciate and
respect that effort, but I think this is not the ideal thing to
happen. Of course you can't control what people do and maybe they
really wanted to write a summary. But since noticing this happen I now
try to discourage the discussion leader from summarizing since 1) it
must be quite time-consuming and 2) it isn't as interesting as some of
the above bullet points.&lt;/p&gt;
&lt;h4 id="confirming-with-leaders"&gt;Confirming with leaders&lt;/h4&gt;&lt;p&gt;When I have picked out folks who seem like they'd be fun discussion
leaders, I bcc email them all asking them to confirm. At the same time
I explain what being a discussion leader means. As I just explained it
here above.&lt;/p&gt;
&lt;p&gt;Each week's discussion gets a new Google Group thread. Discussion
happens in responses to the thread.&lt;/p&gt;
&lt;p&gt;I ask the discussion leaders to create the new discussion thread
between Friday and Saturday their local time.&lt;/p&gt;
&lt;p&gt;For folks who don't confirm, I email them one last time and then if
they still haven't confirmed I find someone new.&lt;/p&gt;
&lt;p&gt;I always lead the first week's discussion so that the discussion
leaders can see what I do and so that I can establish the pattern.&lt;/p&gt;
&lt;h4 id="managing-leaders"&gt;Managing leaders&lt;/h4&gt;&lt;p&gt;It takes a while to read a book. Sometimes the leaders forget to do
their part. If it gets to be Sunday and the discussion leader for the
week hasn't started discussion, I email them to gently ask if they are
still available to kick off discussion. And if they are not, no
worries, I can step in.&lt;/p&gt;
&lt;p&gt;I have had to step in a few times to start discussion and it's no
problem.&lt;/p&gt;
&lt;h4 id="managing-non-leaders"&gt;Managing non-leaders&lt;/h4&gt;&lt;p&gt;Just as you need to clarify and set expectations for discussion
leaders, you need to clarify and set expectations for everyone else.&lt;/p&gt;
&lt;p&gt;When I invite people to the Google Group I typically also create an
Intro thread where I explain the discussion format.&lt;/p&gt;
&lt;p&gt;An annoying aspect of Google Groups is that I cannot limit who can
&lt;em&gt;create&lt;/em&gt; a thread without limiting who can &lt;em&gt;respond&lt;/em&gt; to a thread.&lt;/p&gt;
&lt;p&gt;It would simplify things for me if I could limit thread creation to
discussion leaders. But since I cannot, I try to repeatedly and
explicitly mention in the Intro thread that no one should start a new
discussion thread unless they are a discussion leader. And that new
threads will come out each weekend to discuss the previous chapter.&lt;/p&gt;
&lt;h4 id="setting-the-tone"&gt;Setting the tone&lt;/h4&gt;&lt;p&gt;One of the most important things to do in the Intro email is to set
the tone. I try to clarify this is a friendly and encouraging group
focused on learning and improving ourselves. We have experts in the
group and we have noobs in the group and they are all welcome and will
all come away with different things.&lt;/p&gt;
&lt;h3 id="why-email?"&gt;Why email?&lt;/h3&gt;&lt;p&gt;Email seems to be the most time-friendly and demographic-friendly
medium. Doing live discussion sounds stressful and difficult to
schedule, although I believe Alex Petrov &lt;a href="https://x.com/ifesdjeen/status/1795813863197409384"&gt;runs live
discussions&lt;/a&gt;. Email
forces you to slow down and think things through. And email is
built around an inbox. If you didn't get to read some discussion,
you can mark it unread. You can't do that in Discord or Slack.&lt;/p&gt;
&lt;h3 id="avoiding-long-term-commitments"&gt;Avoiding long-term commitments&lt;/h3&gt;&lt;p&gt;When I pick a book, aside from picking books I think are likely to be
exceptionally well-written, I try to avoid books that we could not
finish within 3 months. It concerns me to try to get people to commit
to something longer than that.&lt;/p&gt;
&lt;p&gt;This has led to some distortion though. Systems Performance has only
16 chapters. One chapter a week means about 3 months in total. But
each chapter is 100 pages long.&lt;/p&gt;
&lt;p&gt;I was hesitant to do a reading of Understanding Software Dynamics
because it has 28 chapters. But each chapter is only 10-15 pages
long. So when I decided to go with it, I decided we'd read 2 chapters a
week. Each discussion leader is responsible for 2 chapters at a
time. That means we can finish within 3 months. And each week we read
only 20-30 pages, which is still much more doable than 100 pages of
Systems Performance.&lt;/p&gt;
&lt;p&gt;On the other hand, we did make it through Systems Performance! Which
gives me confidence to pick other books that are physically daunting,
should they otherwise seem like a good idea.&lt;/p&gt;
&lt;h4 id="a-book-ends"&gt;A book ends&lt;/h4&gt;&lt;p&gt;Many public book clubs go through a book a month and have no
ending. That is totally fair. But what I love about the way I organize
book clubs is that each reading is unrelated to the next. It's an
entirely new signup for each book. You need only "commit" (I mean, you
can drop off whenever and definitely people do) to a 3-month reading
and then you can justly feel good about yourself and join again in the
future or not.&lt;/p&gt;
&lt;p&gt;In contrast a paper reading club has no obvious ending, unless you
pick all the papers in advance and organize them around a school year
or something. This has made running a paper reading club feel more
concerning to me. Though I greatly appreciate folks like Aleksey
Charapko and Murat Demirbas &lt;a href="https://charap.co/category/reading-group/"&gt;who
do&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="most-people-don't-actively-contribute,-but-they-still-value-it"&gt;Most people don't actively contribute, but they still value it&lt;/h3&gt;&lt;p&gt;In a group of 500 people, maybe 1-2% of those people actively
contribute to discussion. 5-10 people. But I often hear from people
who didn't participate that they still highly valued the group. And
this high percentage of non-active-participants is part of why I keep
allowing the group size to grow. There's little work I have to do and
a bunch of people benefit.&lt;/p&gt;
&lt;h3 id="partipation-dies-off"&gt;Partipation dies off&lt;/h3&gt;&lt;p&gt;People start out excited and chime in to introduce themselves but by
the last chapter you're lucky if there are any responses to the
discussion leader at all.&lt;/p&gt;
&lt;p&gt;This is just how it goes. I don't really see anything wrong about this.&lt;/p&gt;
&lt;p&gt;The only way I've tried to combat this is by reassuring everyone that
they should keep up with discussion even if it means they must skim
the chapter instead of really read it.&lt;/p&gt;
&lt;p&gt;I also commonly hear from people that they stop reading because they
got behind. So to combat that I also stress in the beginning that
technical books are often forgiving and that you should just pick up
with whatever chapter we're currently on even if it means skipping
some chapters. You can always go back.&lt;/p&gt;
&lt;h3 id="doing-it-at-your-company-likely-won't-go-well"&gt;Doing it at your company likely won't go well&lt;/h3&gt;&lt;p&gt;I wrote about this
&lt;a href="https://notes.eatonphil.com/eight-years-of-tech-meetups.html"&gt;before&lt;/a&gt;. For
some reason it's hard to get people who would otherwise join an
external reading club to join a company-internal reading club.&lt;/p&gt;
&lt;p&gt;Though perhaps I'm just doing it wrong because I hear of others like
&lt;a href="https://twitter.com/sqlliz/status/1745463496161325087"&gt;Elizabeth Garrett
Christensen&lt;/a&gt;
who run an internal software book club successfully.&lt;/p&gt;
&lt;h3 id="good-luck,-have-fun!"&gt;Good luck, have fun!&lt;/h3&gt;&lt;p&gt;That's all I've got. Send me questions if you've got any. But mostly,
just give it a shot if you want to and you'll learn!&lt;/p&gt;
&lt;p&gt;And if you still don't get it, you can of course just join one of my
book clubs. :)&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Since folks have asked, here's how I run a software book club.&lt;br /&gt;&lt;br /&gt;But also, you could just join and see. :)&lt;a href="https://t.co/tXBrLFYbvC"&gt;https://t.co/tXBrLFYbvC&lt;/a&gt; &lt;a href="https://t.co/4iW8EfZCeY"&gt;pic.twitter.com/4iW8EfZCeY&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1796159854496600164?ref_src=twsrc%5Etfw"&gt;May 30, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 30 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-05-30-how-i-run-book-clubs.html</guid></item><item><title>Linux Command Line Computing book announcement</title><link>https://learnbyexample.github.io/linux-command-line-computing-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;a href="https://github.com/learnbyexample/cli-computing"&gt;Linux Command Line Computing&lt;/a&gt; ebook. This is the longest book I've published so far (204 pages) — it took me more than 7 months to complete the first version and another month for a minor revision.&lt;/p&gt;
&lt;p&gt;This ebook aims to teach &lt;strong&gt;Linux command line tools and Shell Scripting&lt;/strong&gt; for &lt;strong&gt;beginner to intermediate&lt;/strong&gt; level users. The main focus is towards &lt;strong&gt;managing your files&lt;/strong&gt; and performing &lt;strong&gt;text processing tasks&lt;/strong&gt;. Plenty of &lt;strong&gt;examples&lt;/strong&gt; are provided to make it easier to understand a particular tool and its various features. &lt;strong&gt;Exercises&lt;/strong&gt; at the end of chapters will help you practice what you've learned and &lt;strong&gt;solutions&lt;/strong&gt; are provided for reference. I hope this ebook would make it much easier for you to discover CLI tools, features and learning resources than my own blundering experience.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 9-June-2024. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/cli_computing"&gt;https://learnbyexample.gumroad.com/l/cli_computing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/cli_computing/c/new_cli_computing_release"&gt;https://leanpub.com/cli_computing/c/new_cli_computing_release&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of my bundles are on sale as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_cli_computing_release"&gt;All books bundle&lt;/a&gt; is $12 (normal price $32) — all my 13 programming ebooks&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/new_cli_computing_release"&gt;Linux CLI Text Processing&lt;/a&gt; bundle is $7 (normal price $20) — Linux CLI tools, shell scripting, grep, sed, awk, perl and ruby one-liners&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Some of the examples, exercises, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;Linux Command Line Computing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here's a short video about the &lt;strong&gt;Linux Command Line Computing&lt;/strong&gt; ebook:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Ive only gotten through first pages but appears a good Unix/bash primer. I’ll probably recommend for new hires out of bootcamp because they’re usually weak here&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/Lizziness/status/1589866691974291456"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Nice book! I just started trying to get into linux today and you have some tips I haven’t found elsewhere and the text is an enjoyable read so far.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://old.reddit.com/r/linux4noobs/comments/1adrx6c/linux_guide_for_beginners/kk3dypr/"&gt;feedback on reddit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Introduction and Setup&lt;/li&gt;
&lt;li&gt;Command Line Overview&lt;/li&gt;
&lt;li&gt;Managing Files and Directories&lt;/li&gt;
&lt;li&gt;Shell Features&lt;/li&gt;
&lt;li&gt;Viewing Part or Whole File Contents&lt;/li&gt;
&lt;li&gt;Searching Files and Filenames&lt;/li&gt;
&lt;li&gt;File Properties&lt;/li&gt;
&lt;li&gt;Managing Processes&lt;/li&gt;
&lt;li&gt;Multipurpose Text Processing Tools&lt;/li&gt;
&lt;li&gt;Sorting Stuff&lt;/li&gt;
&lt;li&gt;Comparing Files&lt;/li&gt;
&lt;li&gt;Assorted Text Processing Tools&lt;/li&gt;
&lt;li&gt;Shell Scripting&lt;/li&gt;
&lt;li&gt;Shell Customization&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/cli-computing/"&gt;https://learnbyexample.github.io/cli-computing/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/cli-computing"&gt;https://github.com/learnbyexample/cli-computing&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/cli-computing/issues"&gt;https://github.com/learnbyexample/cli-computing/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 29 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/linux-command-line-computing-announcement/</guid></item><item><title>Feynman's Garden</title><link>https://www.marginalia.nu/log/a_108_feynman_revisited/</link><description>&lt;p&gt;The best description of my problem solving process is the Feynman algorithm,
which is sometimes presented as a joke where the hidden subtext is &amp;ldquo;be smart&amp;rdquo;, but
I disagree. The &amp;ldquo;algorithm&amp;rdquo; is a surprisingly lucid description of how thinking works in the
context of hard problems where the answer can&amp;rsquo;t simply be looked up or trivially
broken down, iterated upon in a bottom-up fashion, or approached with similar methods.&lt;/p&gt;
&lt;p&gt;Feynman&amp;rsquo;s thinking algorithm is described like this:&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sun, 26 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_108_feynman_revisited/</guid></item><item><title>Randomness extractors: making fair coins out of biased coins</title><link>https://bytepawn.com/randomness-extractors-making-fair-coins-out-of-biased-coins.html</link><description>&lt;p&gt;I discuss various models of biased bit sequences, and how to extract uniform random (or close to it) output bit sequences from them, illustrated with Python code.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Coin flip" src="/images/coin_toss.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 26 May 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/randomness-extractors-making-fair-coins-out-of-biased-coins.html</guid></item><item><title>The CP/M emulator is working well</title><link>https://blog.steve.fi/the_cp_m_emulator_is_working_well.html</link><description>&lt;p&gt;In my recent posts I've talked about implementing BDOS and BIOS syscalls for my &lt;a href="https://github.com/skx/cpmulator" rel="nofollow"&gt;cp/m emulator&lt;/a&gt;. I've now implemented enough of the calls that I can run many of the standard binaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Aztech C Compiler&lt;/li&gt;
&lt;li&gt;Microsoft BASIC&lt;/li&gt;
&lt;li&gt;Turbo Pascal&lt;/li&gt;
&lt;li&gt;Wordstar&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course I've not implemented all the syscalls, so the emulation isn't 100% perfect and many binaries won't run.  But I sent myself on a detour by implementing &lt;em&gt;extra&lt;/em&gt; syscalls, &lt;em&gt;custom&lt;/em&gt; syscalls.&lt;/p&gt;

&lt;p&gt;Traditionally CP/M systems are "rebooted" by pressing Ctrl-C at the CCP prompt.  I thought that was something I'd press by accident so I implemented the restart behaviour only when the user pressed Ctrl-C twice in a row.  But then I added a custom syscall that lets you change hte value:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;A&amp;gt;ctrlc
The Ctrl-C count is currently set to 2
A&amp;gt;ctrlc 1
The Ctrl-C count is currently set to 1
A&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So you can now change the value at runtime.  Similarly there is support for switching CCP at runtime, and even changing the default output-device from ADM-3A to ANSI, or vice-versa.  It's kinda neat to make these kind of extensions, and happily the traditional BIOS has two syscalls reserved for custom use so I just used one of those.&lt;/p&gt;

&lt;p&gt;I've added support for testing whether a binary is running under my emulator, or not, using a custom syscall.   So I can run:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;A&amp;gt;test
This binary is running under cpmulator:

cpmulator unreleased
https://github.com/skx/cpmulator/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On another emulator I see this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;A&amp;gt;test
Illegal BIOS call 31
No, this binary is not running under cpmulator.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Anyway I'm happy with the current state of things, and I fixed a couple of bugs which means I now have support for &lt;code&gt;SUBMIT.COM&lt;/code&gt; which is a real time-saver.&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Sat, 25 May 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/the_cp_m_emulator_is_working_well.html</guid></item><item><title>Confuddlement: Download Confluence Spaces as Markdown, Summarise with Ollama</title><link>https://smcleod.net/2024/05/confuddlement-download-confluence-spaces-as-markdown-summarise-with-ollama/</link><description>Download Confluence Spaces as Markdown, Summarise with Ollama</description><author>smcleod.net</author><pubDate>Thu, 23 May 2024 08:45:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/05/confuddlement-download-confluence-spaces-as-markdown-summarise-with-ollama/</guid></item><item><title>Abandoning Highfleet Optimization Tool</title><link>https://jodavaho.io/posts/hfopt-intro.html</link><description>&lt;p&gt;You know, it was fun while it lasted, but I just don&amp;rsquo;t have time to work on the Highfleet Optimizer anymore. It&amp;rsquo;s not like anyone was using it!
We had grand ambitions though, and I learned a ton.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the stuff I do not regret at all about this project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Writing a fast optimizer in C, and learning how to get &lt;em&gt;that&lt;/em&gt; deployed to AWS Lambda&lt;/li&gt;
&lt;li&gt;Getting quite good with FastAPI, Lambda, Cognito, and DynamoDB to build a fully functional, authenticated backend.&lt;/li&gt;
&lt;li&gt;Learning to model the game&amp;rsquo;s mechanics regression and model fitting&lt;/li&gt;
&lt;li&gt;Learning to use Lark to parse the game&amp;rsquo;s data files&lt;/li&gt;
&lt;li&gt;Working with Altho&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s the stuff I do regret about this project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not many people are that interested in using computers to explore the trade space of video-game fleets. &lt;code&gt;WHO KNEW&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Not seeing through the visualization part - the community would have loved that&lt;/li&gt;
&lt;li&gt;Not getting the backend integrated with a discord bot - the search functionality alone would have been worth it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll leave it running as is. It cost me less than a buck a month to run the whole thing.&lt;/p&gt;
&lt;p&gt;The reasons?&lt;/p&gt;
&lt;p&gt;Well I&amp;rsquo;m super busy working at &lt;a href="https://shield.ai"&gt;shield.ai&lt;/a&gt;, for one. For another, I just don&amp;rsquo;t play Highfleet anymore. I&amp;rsquo;ve moved on to &lt;a href="https://store.steampowered.com/app/887570/NEBULOUS_Fleet_Command/"&gt;Nebulous: Fleet Command&lt;/a&gt;, because it has a lot of the same focus on realism and immense attention to detail. I&amp;rsquo;m having a blast with that.  Finally, I&amp;rsquo;ve got a few other projects that I&amp;rsquo;m cooking up, so stay tuned for those.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Thu, 23 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/hfopt-intro.html</guid></item><item><title>Writing a simple Python async message queue server - Part III</title><link>https://bytepawn.com/writing-a-simple-python-async-message-queue-server-part-iii.html</link><description>&lt;p&gt;In this final post on the toy Python async message queue server implementation, I make further feature and code improvements.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/async_echo.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Wed, 22 May 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-python-async-message-queue-server-part-iii.html</guid></item><item><title>ServerlessDays ANZ 2024 - All slides, notes and links</title><link>https://boyter.org/posts/serverlessdays-anz-2024-content/</link><description>&lt;p&gt;Welcome. If you are reading this you either follow me on RSS, watch my blog or were directed here at the start of my ServerlessDaysANZ 2024 talk about &amp;ldquo;Abusing Go, AWS Lambda and bloom filters to make a true Australian serverless search engine&amp;rdquo;. Whatever the case may be, welcome!&lt;/p&gt;
&lt;p&gt;For the slides visit &lt;a href="https://boyter.org/static/serverlessdaysanz2024/"&gt;https://boyter.org/static/serverlessdaysanz2024/&lt;/a&gt; then just use the space bar to advance, the arrow keys to move around or the ESC key to see everything and then enter to select a slide. Press S to see the speaker notes, which are more or less the script I will be sticking to.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 20 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/serverlessdays-anz-2024-content/</guid></item><item><title>CodeAid: A classroom deployment of an LLM-based programming assistant</title><link>https://austinhenley.com/blog/codeaid.html</link><description>&lt;a href="https://austinhenley.com/blog/codeaid.html"&gt;https://austinhenley.com/blog/codeaid.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 19 May 2024 22:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/codeaid.html</guid></item><item><title>NVApi - Nvidia GPU Monitoring API</title><link>https://smcleod.net/2024/05/nvapi-nvidia-gpu-monitoring-api/</link><description>Nvidia GPU Monitoring API</description><author>smcleod.net</author><pubDate>Sat, 18 May 2024 14:30:03 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/05/nvapi-nvidia-gpu-monitoring-api/</guid></item><item><title>Experiment in Java native calls</title><link>https://www.marginalia.nu/log/a_106_native_calls/</link><description>&lt;p&gt;I&amp;rsquo;ve experimentally replaced some of the Java implementations of quicksort and binary search with calls to C++ code, and saw huge benefits for the sorting code but the same or worse performance for binary search.&lt;/p&gt;
&lt;p&gt;The Marginalia Search engine is mainly written in Java, which is language that is good at many things, but not particularly pleasant to work with when it comes to low level systems programming.&lt;/p&gt;
&lt;p&gt;Unfortunately, a part of building an internet search engine involves database-adjacent low level programming.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 16 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_106_native_calls/</guid></item><item><title>Implementing MVCC and major SQL transaction isolation levels</title><link>http://notes.eatonphil.com/2024-05-16-mvcc.html</link><description>&lt;p&gt;In this post we'll build a database in 400 lines of code with basic
support for five standard SQL transaction levels: Read Uncommitted,
Read Committed, Repeatable Read, Snapshot Isolation and
Serializable. We'll use multi-version concurrency control (MVCC) and
optimistic concurrency control (OCC) to accomplish this. The goal
isn't to be perfect but to explain the basics in a minimal way.&lt;/p&gt;
&lt;p&gt;You don't need to know what these terms mean in advance. I did not
understand them before doing this project. But if you've ever dealt
with SQL databases, transaction isolation levels are likely one of the
dark corners you either 1) weren't aware of or 2) wanted not to think
about. At least, this is how I felt.&lt;/p&gt;
&lt;p&gt;While there are many blog posts that list out isolation levels, I
haven't been able to internalize their lessons. So I built this little
database to demonstrate the common isolation levels for myself. It
turned out to be simpler than I expected, and made the isolation
levels much easier to reason about.&lt;/p&gt;
&lt;p&gt;Thank you to Justin Jaffray, Alex Miller, Sujay Jayakar, Peter
Veentjer, and Michael Gasch for providing feedback and suggestions.&lt;/p&gt;
&lt;p&gt;All code is &lt;a href="https://github.com/eatonphil/gomvcc"&gt;available&lt;/a&gt; on
GitHub.&lt;/p&gt;
&lt;h3 id="why-do-we-need-transaction-isolation?"&gt;Why do we need transaction isolation?&lt;/h3&gt;&lt;p&gt;If you already know the answer, feel free to skip this section.&lt;/p&gt;
&lt;p&gt;When I first started working with databases in CRUD applications, I
did not understand the point of transactions. I was fairly certain
that transactions are locks. I was wrong about that, but more on that
later.&lt;/p&gt;
&lt;p&gt;I can't remember exact code I wrote, but here's something I could have
written:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT * FROM users WHERE group = 'admin';&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;some_complex_logic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UPDATE users SET metadata = 'some value' WHERE id IN ($1);&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I would have thought that all users that were seen from the initial
&lt;code&gt;SELECT&lt;/code&gt; who matched the &lt;code&gt;some_complex_logic&lt;/code&gt; filter would be exactly
the same that are updated in my second SQL statement.&lt;/p&gt;
&lt;p&gt;And if I were using SQLite, my guess would have been correct. But if I
were using MySQL or Postgres or Oracle or SQL Server, and hadn't made
any changes to defaults, that wouldn't necessarily be true! We'll
discover exactly why throughout this post.&lt;/p&gt;
&lt;p&gt;For example, some other connection and transaction could have set a
&lt;code&gt;user&lt;/code&gt;'s &lt;code&gt;group&lt;/code&gt; to &lt;code&gt;admin&lt;/code&gt; after the initial &lt;code&gt;SELECT&lt;/code&gt; was
executed. It would then be missed from the &lt;code&gt;some_complex_logic&lt;/code&gt; check
and from the subsequent &lt;code&gt;UPDATE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Or, again after our initial &lt;code&gt;SELECT&lt;/code&gt;, some other connection could have
modified the &lt;code&gt;group&lt;/code&gt; for some user that previously was &lt;code&gt;admin&lt;/code&gt;. It
would then be incorrectly part of the second &lt;code&gt;UPDATE&lt;/code&gt; statement.&lt;/p&gt;
&lt;p&gt;These are just a few examples of what could go wrong.&lt;/p&gt;
&lt;p&gt;This is the realm of transaction isolation. How do multiple
transactions running at the same time, interacting with the same data,
interact with each other?&lt;/p&gt;
&lt;p&gt;The answer is: it depends. The SQL standard itself loosely prescribes
four isolation levels. But every database implements these four levels
slightly differently. Sometimes using entirely different
algorithms. And even among the standard levels, the default isolation
level for each database differs.&lt;/p&gt;
&lt;p&gt;Funky bugs that can show up across databases and across isolation
levels, often dependent on particular details of common ways of
implementing isolation levels, create what are called
"anomalies". Examples include intimidating terms like "dirty reads"
and "write cycles" and G2-Item.&lt;/p&gt;
&lt;p&gt;The topic is so complex that we've got decades of research papers
&lt;a href="https://15721.courses.cs.cmu.edu/spring2019/papers/02-transactions/p1-berenson.pdf"&gt;critiquing&lt;/a&gt;
SQL isolation levels,
&lt;a href="https://pmg.csail.mit.edu/papers/icde00.pdf"&gt;categorization&lt;/a&gt; of
common isolation anomalies, walkthroughs of anomalies by Martin
Kleppmann in &lt;a href="https://dataintensive.net/"&gt;Designing Data-Intensive
Applications&lt;/a&gt;, Martin Kleppman's
&lt;a href="https://github.com/ept/hermitage"&gt;Hermitage&lt;/a&gt; project documenting
common anomalies across isolation levels in major databases, and the
&lt;a href="http://www.bailis.org/papers/acidrain-sigmod2017.pdf"&gt;ACIDRain paper&lt;/a&gt;
showing isolation-related bugs in major open-source ecommerce
projects.&lt;/p&gt;
&lt;p&gt;These aren't just random links. They're each quite interesting. And
particularly for practitioners who don't know why they should care,
check out Designing Data-Intensive Applications and the last link on
ACIDRain.&lt;/p&gt;
&lt;p&gt;And this is only a small list of some of the most interesting research
and writing on the topic.&lt;/p&gt;
&lt;p&gt;So there's a wide variety of things to consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not every database implements transaction isolation levels
identically, resulting in different behavior&lt;/li&gt;
&lt;li&gt;Not all researchers agree, and not all database developers agree, on
what any given isolation level means&lt;/li&gt;
&lt;li&gt;Not every database has the same default isolation level, and most
developers tend not to change the default&lt;/li&gt;
&lt;li&gt;Not every developer is correctly using the isolation level they pick
(default or not)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Transaction isolation levels are basically vibes. The only truth for
real projects is Martin Kleppmann's &lt;a href=""&gt;Hermitage&lt;/a&gt; project that
catalogs behavior across databases. And a truth some people align with
is &lt;a href="https://pmg.csail.mit.edu/papers/icde00.pdf"&gt;Generalized Isolation Level
Definitions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So while all these linked works above are authoritative, and even
though we can see that there might be some anomalies we have to worry
about, the research can still be difficult to internalize. And many
developers, my recent self included, do not have a great understanding
of isolation levels.&lt;/p&gt;
&lt;p&gt;Throughout this post we'll stick to informal definitions of isolation
levels to keep things simple.&lt;/p&gt;
&lt;p&gt;Let's dig in.&lt;/p&gt;
&lt;h3 id="locks?-mvcc?"&gt;Locks? MVCC?&lt;/h3&gt;&lt;p&gt;Historically, databases implemented isolation with locking algorithms
such as &lt;a href="https://faculty.cc.gatech.edu/~jarulraj/courses/8803-s22/slides/13-two-phase-locking-annotated.pdf"&gt;Two-Phase
Locking&lt;/a&gt;
(not the same thing as &lt;a href="https://www.cs.princeton.edu/courses/archive/fall16/cos418/docs/L6-2pc.pdf"&gt;Two-Phase
Commit&lt;/a&gt;). Multi-version
concurrency control (MVCC) is an approach that lets us completely
avoid locks.&lt;/p&gt;
&lt;p&gt;It's worthwhile to note that while we will validly not use locks
(implementing what is called optimistic concurrency control or OCC),
most MVCC databases do still use locks for certain things
(implementing what is called pessimistic concurrency control).&lt;/p&gt;
&lt;p&gt;But this is the story of databases in general. There are numerous ways
to implement things.&lt;/p&gt;
&lt;p&gt;We will take the simpler lockless route.&lt;/p&gt;
&lt;p&gt;Consider a key-value database. With MVCC, rather than storing only the
value for a key, we would store versions of the value.  The version
includes the transaction id (a monotonic incrementing integer) wherein
the version was created, and the transaction id wherein the version
was deleted.&lt;/p&gt;
&lt;p&gt;With MVCC, it is possible to express transaction isolation levels
almost solely as a set of different visibility rules for a version of
a value; rules that vary by isolation level.&lt;/p&gt;
&lt;p&gt;So we will build up a general framework first and discuss and
implement each isolation level last.&lt;/p&gt;
&lt;h3 id="scaffolding"&gt;Scaffolding&lt;/h3&gt;&lt;p&gt;We'll build an in-memory key-value system that acts on transactions. I
usually try to stick with only the standard library for projects like
this but I really wanted a sorted data structure and Go doesn't
implement one.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;main.go&lt;/code&gt;, let's set up basic helpers for assertions and debugging:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;slices&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/tidwall/btree&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s '%v' != '%v'&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--debug&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[DEBUG]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As mentioned previously, a value in the database will be defined with
start and end transaction ids.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;txStartId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;txEndId&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Every transaction will be in an in-progress, aborted, or committed
state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TransactionState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;InProgressTransaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TransactionState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;iota&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;AbortedTransaction&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;CommittedTransaction&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we'll support a few major isolation levels.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Loosest isolation at the top, strictest isolation at the bottom.&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IsolationLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;ReadUncommittedIsolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IsolationLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;iota&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;ReadCommittedIsolation&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;RepeatableReadIsolation&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;SnapshotIsolation&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;SerializableIsolation&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We'll get into detail about the meaning of the levels later.&lt;/p&gt;
&lt;p&gt;A transaction has an isolation level, an id (monotonic increasing
integer), and a current state. And although we won't make use of this
data yet, transactions at stricter isolation levels will need some
extra info. Specifically, stricter isolation levels need to know about
other transactions that were in-progress when this one started. And
stricter isolation levels need to know about all keys read and written
by a transaction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;isolation&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;IsolationLevel&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;TransactionState&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Used only by Repeatable Read and stricter.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;inprogress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Used only by Snapshot Isolation and stricter.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;writeset&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;readset&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We'll discuss why later.&lt;/p&gt;
&lt;p&gt;Finally, the database itself will have a default isolation level that
each transaction will inherit (for our own convenience in tests).&lt;/p&gt;
&lt;p&gt;The database will have a mapping of keys to an array of value
versions. Later elements in the array will represent newer versions of
a value.&lt;/p&gt;
&lt;p&gt;The database will also store the next free transaction id it will use
to assign ids to new transactions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;defaultIsolation&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;IsolationLevel&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;nextTransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;defaultIsolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ReadCommittedIsolation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;// The `0` transaction id will be used to mean that&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;// the id was not set. So all valid transaction ids&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;// must start at 1.&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;nextTransactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  To be thread-safe: &lt;code&gt;store&lt;/code&gt;, &lt;code&gt;transactions&lt;/code&gt;,
  and &lt;code&gt;nextTransactionId&lt;/code&gt; should be guarded by a mutex. But
  to keep the code small, this post will not use goroutines and thus
  does not need mutexes.
&lt;/p&gt;&lt;p&gt;There's a bit of book-keeping when creating a transaction, so we'll
make a dedicated method for this. We must give the new transaction an
id, store all in-progress transactions, and add it to database
transaction history.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inprogress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;iter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InProgressTransaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newTransaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultIsolation&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InProgressTransaction&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Assign and increment transaction id.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextTransactionId&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextTransactionId&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Store all inprogress transaction ids.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inprogress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inprogress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Add this transaction to history.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;starting transaction&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we'll add a few more helpers for completing a transaction, for
fetching a transaction by id, and for validating a transaction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;completeTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TransactionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;completing transaction &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Update transactions.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transactionState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;valid transaction&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assertValidTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;valid id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InProgressTransaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;in progress&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The final bit of scaffolding we'll set up is an abstraction for
database connections. A connection will have at most associated one
transaction. Users must ask the database for a new connection. Then
within the connection they can manage a transaction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// TODO&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unimplemented&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unexpected error&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unimplemented&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that's it for scaffolding. Now set up the go module and make sure
this builds.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;gomvcc
&lt;span class="go"&gt;go: creating new go.mod: module gomvcc&lt;/span&gt;
&lt;span class="go"&gt;go: to add module requirements and sums:&lt;/span&gt;
&lt;span class="go"&gt;        go mod tidy&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;tidy
&lt;span class="go"&gt;go: finding module for package github.com/tidwall/btree&lt;/span&gt;
&lt;span class="go"&gt;go: found github.com/tidwall/btree in github.com/tidwall/btree v1.7.0&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build
&lt;span class="gp"&gt;$ &lt;/span&gt;./gomvcc
&lt;span class="go"&gt;panic: unimplemented&lt;/span&gt;

&lt;span class="go"&gt;goroutine 1 [running]:&lt;/span&gt;
&lt;span class="go"&gt;main.main()&lt;/span&gt;
&lt;span class="go"&gt;        /Users/phil/tmp/main.go:166 +0x2c&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Great!&lt;/p&gt;
&lt;h3 id="transaction-management"&gt;Transaction management&lt;/h3&gt;&lt;p&gt;When the user asks to begin a transaction, we ask the database for a
new transaction and assign it to the current connection.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;func (c *Connection) execCommand(command string, args []string) (string, error) {
&lt;span class="w"&gt; &lt;/span&gt;        debug(command, args)

&lt;span class="gi"&gt;+       if command == &amp;quot;begin&amp;quot; {&lt;/span&gt;
&lt;span class="gi"&gt;+               assertEq(c.tx, nil, &amp;quot;no running transactions&amp;quot;)&lt;/span&gt;
&lt;span class="gi"&gt;+               c.tx = c.db.newTransaction()&lt;/span&gt;
&lt;span class="gi"&gt;+               c.db.assertValidTransaction(c.tx)&lt;/span&gt;
&lt;span class="gi"&gt;+               return fmt.Sprintf(&amp;quot;%d&amp;quot;, c.tx.id), nil&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        // TODO
&lt;span class="w"&gt; &lt;/span&gt;        return &amp;quot;&amp;quot;, fmt.Errorf(&amp;quot;unimplemented&amp;quot;)
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To abort a transaction, we call the &lt;code&gt;completeTransaction&lt;/code&gt; method
(which makes sure the database transaction history gets updated) with
the &lt;code&gt;AbortedTransaction&lt;/code&gt; state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;               return fmt.Sprintf(&amp;quot;%d&amp;quot;, c.tx.id), nil
&lt;span class="w"&gt; &lt;/span&gt;       }

&lt;span class="gi"&gt;+       if command == &amp;quot;abort&amp;quot; {&lt;/span&gt;
&lt;span class="gi"&gt;+               c.db.assertValidTransaction(c.tx)&lt;/span&gt;
&lt;span class="gi"&gt;+               err := c.db.completeTransaction(c.tx, AbortedTransaction)&lt;/span&gt;
&lt;span class="gi"&gt;+               c.tx = nil&lt;/span&gt;
&lt;span class="gi"&gt;+               return &amp;quot;&amp;quot;, err&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        // TODO
&lt;span class="w"&gt; &lt;/span&gt;        return &amp;quot;&amp;quot;, fmt.Errorf(&amp;quot;unimplemented&amp;quot;)
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And to commit a transaction is similar.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;               return &amp;quot;&amp;quot;, err
&lt;span class="w"&gt; &lt;/span&gt;       }

&lt;span class="gi"&gt;+       if command == &amp;quot;commit&amp;quot; {&lt;/span&gt;
&lt;span class="gi"&gt;+               c.db.assertValidTransaction(c.tx)&lt;/span&gt;
&lt;span class="gi"&gt;+               err := c.db.completeTransaction(c.tx, CommittedTransaction)&lt;/span&gt;
&lt;span class="gi"&gt;+               c.tx = nil&lt;/span&gt;
&lt;span class="gi"&gt;+               return &amp;quot;&amp;quot;, err&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        // TODO
&lt;span class="w"&gt; &lt;/span&gt;        return &amp;quot;&amp;quot;, fmt.Errorf(&amp;quot;unimplemented&amp;quot;)
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The neat thing about MVCC is that beginning, committing, and aborting
a transaction is metadata work. Committing a transaction will get a
bit more complex when we add support for Snapshot Isolation and
Serializable Isolation, but we'll get to that later. Even then, it
will not involve modifying any values we get, set, or delete.&lt;/p&gt;
&lt;h3 id="get,-set,-delete"&gt;Get, set, delete&lt;/h3&gt;&lt;p&gt;Here is where things get fun. As mentioned earlier, the key-value
store is actually &lt;code&gt;map[string][]Value&lt;/code&gt;. With the more recent versions
of a value at the end of the list of values for the key.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;get&lt;/code&gt; support, we'll iterate the list of value versions backwards
for the key. And we'll call a special new &lt;code&gt;isvisible&lt;/code&gt; method to
determine if this transaction can see this value. The first value that
passes the &lt;code&gt;isvisible&lt;/code&gt; test is the correct value for the transaction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;               return &amp;quot;&amp;quot;, err
&lt;span class="w"&gt; &lt;/span&gt;       }

&lt;span class="gi"&gt;+       if command == &amp;quot;get&amp;quot; {&lt;/span&gt;
&lt;span class="gi"&gt;+               c.db.assertValidTransaction(c.tx)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               key := args[0]&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               c.tx.readset.Insert(key)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               for i := len(c.db.store[key]) - 1; i &amp;gt;= 0; i-- {&lt;/span&gt;
&lt;span class="gi"&gt;+                       value := c.db.store[key][i]&lt;/span&gt;
&lt;span class="gi"&gt;+                       debug(value, c.tx, c.db.isvisible(c.tx, value))&lt;/span&gt;
&lt;span class="gi"&gt;+                       if c.db.isvisible(c.tx, value) {&lt;/span&gt;
&lt;span class="gi"&gt;+                               return value.value, nil&lt;/span&gt;
&lt;span class="gi"&gt;+                       }&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               return &amp;quot;&amp;quot;, fmt.Errorf(&amp;quot;cannot get key that does not exist&amp;quot;)&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        // TODO
&lt;span class="w"&gt; &lt;/span&gt;        return &amp;quot;&amp;quot;, fmt.Errorf(&amp;quot;unimplemented&amp;quot;)
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I snuck in tracking which keys are read, and we'll also soon sneak in
tracking which keys are written. This is necessary in stricter
isolation levels. More on that later.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; are similar to get. But this time when we walk the
list of value versions, we will set the &lt;code&gt;txEndId&lt;/code&gt; for the value to the
current transaction id if the value version is visible to this
transaction.&lt;/p&gt;
&lt;p&gt;Then, for &lt;code&gt;set&lt;/code&gt;, we'll append to the value version list with the new
version of the value that starts at this current transaction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;               return &amp;quot;&amp;quot;, err
&lt;span class="w"&gt; &lt;/span&gt;       }

&lt;span class="gi"&gt;+       if command == &amp;quot;set&amp;quot; || command == &amp;quot;delete&amp;quot; {&lt;/span&gt;
&lt;span class="gi"&gt;+               c.db.assertValidTransaction(c.tx)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               key := args[0]&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               // Mark all visible versions as now invalid.&lt;/span&gt;
&lt;span class="gi"&gt;+               found := false&lt;/span&gt;
&lt;span class="gi"&gt;+               for i := len(c.db.store[key]) - 1; i &amp;gt;= 0; i-- {&lt;/span&gt;
&lt;span class="gi"&gt;+                       value := &amp;amp;c.db.store[key][i]&lt;/span&gt;
&lt;span class="gi"&gt;+                       debug(value, c.tx, c.db.isvisible(c.tx, *value))&lt;/span&gt;
&lt;span class="gi"&gt;+                       if c.db.isvisible(c.tx, *value) {&lt;/span&gt;
&lt;span class="gi"&gt;+                               value.txEndId = c.tx.id&lt;/span&gt;
&lt;span class="gi"&gt;+                               found = true&lt;/span&gt;
&lt;span class="gi"&gt;+                       }&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+               if command == &amp;quot;delete&amp;quot; &amp;amp;&amp;amp; !found {&lt;/span&gt;
&lt;span class="gi"&gt;+                       return &amp;quot;&amp;quot;, fmt.Errorf(&amp;quot;cannot delete key that does not exist&amp;quot;)&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               c.tx.writeset.Insert(key)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               // And add a new version if it's a set command.&lt;/span&gt;
&lt;span class="gi"&gt;+               if command == &amp;quot;set&amp;quot; {&lt;/span&gt;
&lt;span class="gi"&gt;+                       value := args[1]&lt;/span&gt;
&lt;span class="gi"&gt;+                       c.db.store[key] = append(c.db.store[key], Value{&lt;/span&gt;
&lt;span class="gi"&gt;+                               txStartId: c.tx.id,&lt;/span&gt;
&lt;span class="gi"&gt;+                               txEndId:   0,&lt;/span&gt;
&lt;span class="gi"&gt;+                               value:     value,&lt;/span&gt;
&lt;span class="gi"&gt;+                       })&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                       return value, nil&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               // Delete ok.&lt;/span&gt;
&lt;span class="gi"&gt;+               return &amp;quot;&amp;quot;, nil&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;       if command == &amp;quot;get&amp;quot; {
&lt;span class="w"&gt; &lt;/span&gt;               c.db.assertValidTransaction(c.tx)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This time rather than modifying the &lt;code&gt;readset&lt;/code&gt; we modify the &lt;code&gt;writeset&lt;/code&gt;
for the transaction.&lt;/p&gt;
&lt;p&gt;And that is how commands get executed!&lt;/p&gt;
&lt;p&gt;Let's zoom in to the core of the problem we have mentioned but not
implemented: MVCC visibility rules and how they differ by isolation
levels.&lt;/p&gt;
&lt;h3 id="isolation-levels-and-mvcc-visibility-rules"&gt;Isolation levels and MVCC visibility rules&lt;/h3&gt;&lt;p&gt;To varying degrees, transaction isolation levels prevent concurrent
transactions from messing with each other. The looser isolation levels
prevent this almost not at all.&lt;/p&gt;
&lt;p&gt;Here is what the &lt;a href="https://web.cecs.pdx.edu/~len/sql1999.pdf"&gt;1999 ANSI SQL
standard&lt;/a&gt; (page 84) has to
say.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/sql99isolation.png" src="/sql99isolation.png" /&gt;&lt;/p&gt;
&lt;p&gt;But as I mentioned in the beginning of the post, we're going to be a
bit informal. And we'll mostly refer to
&lt;a href="https://jepsen.io/consistency"&gt;Jepsen&lt;/a&gt; summaries of each isolation
levels.&lt;/p&gt;
&lt;h4 id="read-uncommitted"&gt;Read Uncommitted&lt;/h4&gt;&lt;p&gt;According to
&lt;a href="https://jepsen.io/consistency/models/read-uncommitted"&gt;Jepsen&lt;/a&gt;, the
loosest isolation level, Read Uncommitted, has almost no
restrictions. We can merely read the most recent (non-deleted) version
of a value, regardless of if the transaction that set it has committed
or aborted or not.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isvisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Read Uncommitted means we simply read the last value&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// written. Even if the transaction that wrote this value has&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// not committed, and even if it has aborted.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReadUncommittedIsolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;// We must merely make sure the value has not been&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;// deleted.&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;txEndId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unsupported isolation level&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's write a test that demonstrates this. We create two transactions,
&lt;code&gt;c1&lt;/code&gt; and &lt;code&gt;c2&lt;/code&gt;, and set a key in &lt;code&gt;c1&lt;/code&gt;. The value set for the key in
&lt;code&gt;c1&lt;/code&gt; should be immediately visible if &lt;code&gt;c2&lt;/code&gt; asks for that key. In
main_test.go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;testing&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestReadUncommitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultIsolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReadUncommittedIsolation&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Update is visible to self.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c1 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// But since read uncommitted, also available to everyone else.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// And if we delete, that should be respected.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;delete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c1 delete x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c1 sees no x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c1 sees no x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 sees no x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 sees no x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  Thank you to @glaebhoerl
  for &lt;a href="https://twitter.com/glaebhoerl/status/1792912649304388043"&gt;pointing
  out&lt;/a&gt; that in an earlier version of this post, Read Uncommitted
  incorrectly made deleted values visible.
&lt;/p&gt;&lt;p&gt;That's pretty simple! But also pretty useless if your workload has
conflicts. If you can arrange your workload in a way where you know no
concurrent transactions will ever read or write conflicting keys
though, this could be pretty efficient! The rules will only get more
complex (and thus potentially more of a bottleneck) from here on.&lt;/p&gt;
&lt;p&gt;But for the most part, people don't use this isolation level. SQLite,
Yugabyte, Cockroach, and Postgres &lt;a href="https://github.com/ept/hermitage?tab=readme-ov-file#summary-of-test-results"&gt;don't
even&lt;/a&gt;
implement it. It is also not the default for any major database that
does implement it.&lt;/p&gt;
&lt;p&gt;Let's get a little stricter.&lt;/p&gt;
&lt;h4 id="read-committed"&gt;Read Committed&lt;/h4&gt;&lt;p&gt;We'll pull again from &lt;a href="https://jepsen.io/consistency/models/read-committed"&gt;Jepsen&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Read committed is a consistency model which strengthens read
uncommitted by preventing dirty reads: transactions are not allowed
to observe writes from transactions which do not commit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This sounds pretty simple. In &lt;code&gt;isvisible&lt;/code&gt; we'll make sure that the
value has a &lt;code&gt;txStartId&lt;/code&gt; that is either this transaction or a
transaction that has committed. Moreover we will now begin checking
against &lt;code&gt;txEndId&lt;/code&gt; to make sure the value wasn't deleted by any
relevant transaction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;               return value.txEndId == 0
&lt;span class="w"&gt; &lt;/span&gt;       }

&lt;span class="gi"&gt;+       // Read Committed means we are allowed to read any values that&lt;/span&gt;
&lt;span class="gi"&gt;+       // are committed at the point in time where we read.&lt;/span&gt;
&lt;span class="gi"&gt;+       if t.isolation == ReadCommittedIsolation {&lt;/span&gt;
&lt;span class="gi"&gt;+               // If the value was created by a transaction that is&lt;/span&gt;
&lt;span class="gi"&gt;+               // not committed, and not this current transaction,&lt;/span&gt;
&lt;span class="gi"&gt;+               // it's no good.&lt;/span&gt;
&lt;span class="gi"&gt;+               if value.txStartId != t.id &amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="gi"&gt;+                       d.transactionState(value.txStartId).state != CommittedTransaction {&lt;/span&gt;
&lt;span class="gi"&gt;+                       return false&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               // If the value was deleted in this transaction, it's no good.&lt;/span&gt;
&lt;span class="gi"&gt;+               if value.txEndId == t.id {&lt;/span&gt;
&lt;span class="gi"&gt;+                       return false&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               // Or if the value was deleted in some other committed&lt;/span&gt;
&lt;span class="gi"&gt;+               // transaction, it's no good.&lt;/span&gt;
&lt;span class="gi"&gt;+               if value.txEndId &amp;gt; 0 &amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="gi"&gt;+                       d.transactionState(value.txEndId).state == CommittedTransaction {&lt;/span&gt;
&lt;span class="gi"&gt;+                       return false&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               // Otherwise the value is good.&lt;/span&gt;
&lt;span class="gi"&gt;+               return true&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;       assert(false, &amp;quot;unsupported isolation level&amp;quot;)
&lt;span class="w"&gt; &lt;/span&gt;       return false
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This begins to look useful! We will never read a value that isn't part
of a committed transaction (or isn't part of our own
transaction). Indeed this is the
&lt;a href="https://github.com/ept/hermitage"&gt;default&lt;/a&gt; isolation level for many
databases including Postgres, Yugabyte, Oracle, and SQL Server.&lt;/p&gt;
&lt;p&gt;Let's add a test to &lt;code&gt;main_test.go&lt;/code&gt;. This is a bit long, but give it a
slow read. It is thoroughly commented.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestReadCommitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultIsolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReadCommittedIsolation&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Local change is visible locally.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c1 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Update not available to this transaction since this is not&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// committed.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Now that it's been committed, it's visible in c2.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Local change is visible locally.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;yall&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;yall&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c3 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// But not on the other commit, again.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;abort&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// And still not, if the other transaction aborted.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// And if we delete it, it should show up deleted locally.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;delete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// It should also show up as deleted in new transactions now&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// that it has been committed.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c4 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c4 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Again this seems great. However! You can easily get inconsistent data
within a transaction at this isolation level. If the transaction A has
multiple statements it can see different results per statement, even
if the transaction A did not modify data. Another transaction B may
have committed changes between two statements in this transaction A.&lt;/p&gt;
&lt;p&gt;Let's get a little stricter.&lt;/p&gt;
&lt;h4 id="repeatable-read"&gt;Repeatable Read&lt;/h4&gt;&lt;p&gt;Again as Jepsen says, Repeatable Read is the same as Read Committed
but with the following anomaly not allowed (quoting from the &lt;a href="https://web.cecs.pdx.edu/~len/sql1999.pdf"&gt;ANSI SQL
1999 standard&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;P2 (“Non-repeatable read”): SQL-transaction T1 reads a
row. SQL-transaction T2 then modifies or deletes that row and
performs a COMMIT. If T1 then attempts to reread the row, it may
receive the modified value or discover that the row has been
deleted.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To support this, we will add additional checks for the Read Committed
logic that make sure the value was not created and not deleted within
a transaction that started before this transaction started.&lt;/p&gt;
&lt;p&gt;As it happens, this is the same logic that will be necessary for
Snapshot Isolation and Serializable Isolation. The additional logic
(that makes Snapshot Isolation and Serializable Isolation different)
happens at commit time.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;               return true
&lt;span class="w"&gt; &lt;/span&gt;       }

&lt;span class="gd"&gt;-       assert(false, &amp;quot;unsupported isolation level&amp;quot;)&lt;/span&gt;
&lt;span class="gd"&gt;-       return false&lt;/span&gt;
&lt;span class="gi"&gt;+       // Repeatable Read, Snapshot Isolation, and Serializable&lt;/span&gt;
&lt;span class="gi"&gt;+       // further restricts Read Committed so only versions from&lt;/span&gt;
&lt;span class="gi"&gt;+       // transactions that completed before this one started are&lt;/span&gt;
&lt;span class="gi"&gt;+       // visible.&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+       // Snapshot Isolation and Serializable will do additional&lt;/span&gt;
&lt;span class="gi"&gt;+       // checks at commit time.&lt;/span&gt;
&lt;span class="gi"&gt;+       assert(t.isolation == RepeatableReadIsolation ||&lt;/span&gt;
&lt;span class="gi"&gt;+               t.isolation == SnapshotIsolation ||&lt;/span&gt;
&lt;span class="gi"&gt;+               t.isolation == SerializableIsolation, &amp;quot;invalid isolation level&amp;quot;)&lt;/span&gt;
&lt;span class="gi"&gt;+       // Ignore values from transactions started after this one.&lt;/span&gt;
&lt;span class="gi"&gt;+       if value.txStartId &amp;gt; t.id {&lt;/span&gt;
&lt;span class="gi"&gt;+               return false&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+       // Ignore values created from transactions in progress when&lt;/span&gt;
&lt;span class="gi"&gt;+       // this one started.&lt;/span&gt;
&lt;span class="gi"&gt;+       if t.inprogress.Contains(value.txStartId) {&lt;/span&gt;
&lt;span class="gi"&gt;+               return false&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+       // If the value was created by a transaction that is not&lt;/span&gt;
&lt;span class="gi"&gt;+       // committed, and not this current transaction, it's no good.&lt;/span&gt;
&lt;span class="gi"&gt;+       if d.transactionState(value.txStartId).state != CommittedTransaction &amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="gi"&gt;+               value.txStartId != t.id {&lt;/span&gt;
&lt;span class="gi"&gt;+               return false&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+       // If the value was deleted in this transaction, it's no good.&lt;/span&gt;
&lt;span class="gi"&gt;+       if value.txEndId == t.id {&lt;/span&gt;
&lt;span class="gi"&gt;+               return false&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+       // Or if the value was deleted in some other committed&lt;/span&gt;
&lt;span class="gi"&gt;+       // transaction that started before this one, it's no good.&lt;/span&gt;
&lt;span class="gi"&gt;+       if value.txEndId &amp;lt; t.id &amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="gi"&gt;+               value.txEndId &amp;gt; 0 &amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="gi"&gt;+               d.transactionState(value.txEndId).state == CommittedTransaction &amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="gi"&gt;+               !t.inprogress.Contains(value.txEndId) {&lt;/span&gt;
&lt;span class="gi"&gt;+               return false&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+       return true&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;type Connection struct {
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;How do I derive these rules? Mostly by writing tests that should pass
or fail and seeing what doesn't make sense. I tried to steal from
existing projects but these rules were not so simple to
discover. Which is part of what I hope makes this project particularly
useful to look at.&lt;/p&gt;
&lt;p&gt;Let's write a test for Repeatable Read. Again, the test is long but
well commented.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestRepeatableRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultIsolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RepeatableReadIsolation&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Local change is visible locally.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c1 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Update not available to this transaction since this is not&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// committed.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Even after committing, it's not visible in an existing&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// transaction.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// But is available in a new transaction.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c3 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Local change is visible locally.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;yall&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;yall&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c3 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// But not on the other commit, again.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;abort&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// And still not, regardless of abort, because it's an older&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// transaction.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// And again still the aborted set is still not on a new&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// transaction.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c4 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;delete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// But the delete is visible to new transactions now that this&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// has been committed.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c5 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c5 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's get stricter!&lt;/p&gt;
&lt;h4 id="snapshot-isolation"&gt;Snapshot Isolation&lt;/h4&gt;&lt;p&gt;Back to
&lt;a href="https://jepsen.io/consistency/models/snapshot-isolation"&gt;Jepsen&lt;/a&gt; for a
definition:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;In a snapshot isolated system, each transaction appears to operate
on an independent, consistent snapshot of the database. Its changes
are visible only to that transaction until commit time, when all
changes become visible atomically to any transaction which begins at
a later time. If transaction T1 has modified an object x, and
another transaction T2 committed a write to x after T1’s snapshot
began, and before T1’s commit, then T1 must abort.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So Snapshot Isolation is the same as Repeatable Read but with one
additional rule: the keys written by any two concurrent committed
transactions must not overlap.&lt;/p&gt;
&lt;p&gt;This is why we tracked &lt;code&gt;writeset&lt;/code&gt;. Every time a transaction modified
or deleted a key, we added it to the transaction's &lt;code&gt;writeset&lt;/code&gt;. To make
sure we abort correctly, we'll add a conflict check to the commit
step. (This idea is also well documented in &lt;a href="https://dl.acm.org/doi/abs/10.1145/2168836.2168853"&gt;A critique of snapshot
isolation&lt;/a&gt;. This
paper can be hard to find. Email me if you want a copy.)&lt;/p&gt;
&lt;p&gt;When a transaction A goes to commit, it will run a conflict test for
any transaction B that has committed since this transaction A started.&lt;/p&gt;
&lt;p&gt;Serializable Isolation is going to have a similar check. So we'll add
a helper for iterating through all relevant transactions, running a
check function for any transaction that has committed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;func (d *Database) hasConflict(t1 *Transaction, conflictFn func(*Transaction, *Transaction) bool) bool {
&lt;span class="w"&gt; &lt;/span&gt;       iter := d.transactions.Iter()

&lt;span class="w"&gt; &lt;/span&gt;       // First see if there is any conflict with transactions that
&lt;span class="w"&gt; &lt;/span&gt;       // were in progress when this one started.
&lt;span class="w"&gt; &lt;/span&gt;       inprogressIter := t1.inprogress.Iter()
&lt;span class="w"&gt; &lt;/span&gt;       for ok := inprogressIter.First(); ok; ok = inprogressIter.Next() {
&lt;span class="w"&gt; &lt;/span&gt;               id := inprogressIter.Key()
&lt;span class="w"&gt; &lt;/span&gt;               found := iter.Seek(id)
&lt;span class="w"&gt; &lt;/span&gt;               if !found {
&lt;span class="w"&gt; &lt;/span&gt;                       continue
&lt;span class="w"&gt; &lt;/span&gt;               }
&lt;span class="w"&gt; &lt;/span&gt;               t2 := iter.Value()
&lt;span class="w"&gt; &lt;/span&gt;               if t2.state == CommittedTransaction {
&lt;span class="w"&gt; &lt;/span&gt;                       if conflictFn(t1, &amp;amp;t2) {
&lt;span class="w"&gt; &lt;/span&gt;                               return true
&lt;span class="w"&gt; &lt;/span&gt;                       }
&lt;span class="w"&gt; &lt;/span&gt;               }
&lt;span class="w"&gt; &lt;/span&gt;       }

&lt;span class="w"&gt; &lt;/span&gt;       // Then see if there is any conflict with transactions that
&lt;span class="w"&gt; &lt;/span&gt;       // started and committed after this one started.
&lt;span class="w"&gt; &lt;/span&gt;       for id := t1.id; id &amp;lt; d.nextTransactionId; id++ {
&lt;span class="w"&gt; &lt;/span&gt;               found := iter.Seek(id)
&lt;span class="w"&gt; &lt;/span&gt;               if !found {
&lt;span class="w"&gt; &lt;/span&gt;                       continue
&lt;span class="w"&gt; &lt;/span&gt;               }

&lt;span class="w"&gt; &lt;/span&gt;               t2 := iter.Value()
&lt;span class="w"&gt; &lt;/span&gt;               if t2.state == CommittedTransaction {
&lt;span class="w"&gt; &lt;/span&gt;                       if conflictFn(t1, &amp;amp;t2) {
&lt;span class="w"&gt; &lt;/span&gt;                               return true
&lt;span class="w"&gt; &lt;/span&gt;                       }
&lt;span class="w"&gt; &lt;/span&gt;               }
&lt;span class="w"&gt; &lt;/span&gt;       }

&lt;span class="w"&gt; &lt;/span&gt;       return false
}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It was around this point that I decided I did really need a B-Tree
implementation and could not just stick to vanilla Go data structures.&lt;/p&gt;
&lt;p&gt;Now we can modify &lt;code&gt;completeTransaction&lt;/code&gt; to do this check if the
transaction intends to commit. If the current transaction A's write
set intersects with any other transaction B committed since
transaction A started, we must abort.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;func (d *Database) completeTransaction(t *Transaction, state TransactionState) error {
&lt;span class="w"&gt; &lt;/span&gt;        debug(&amp;quot;completing transaction &amp;quot;, t.id)

&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+       if state == CommittedTransaction {&lt;/span&gt;
&lt;span class="gi"&gt;+               // Snapshot Isolation imposes the additional constraint that&lt;/span&gt;
&lt;span class="gi"&gt;+               // no transaction A may commit after writing any of the same&lt;/span&gt;
&lt;span class="gi"&gt;+               // keys as transaction B has written and committed during&lt;/span&gt;
&lt;span class="gi"&gt;+               // transaction A's life.&lt;/span&gt;
&lt;span class="gi"&gt;+               if t.isolation == SnapshotIsolation &amp;amp;&amp;amp; d.hasConflict(t, func(t1 *Transaction, t2 *Transaction) bool {&lt;/span&gt;
&lt;span class="gi"&gt;+                       return setsShareItem(t1.writeset, t2.writeset)&lt;/span&gt;
&lt;span class="gi"&gt;+               }) {&lt;/span&gt;
&lt;span class="gi"&gt;+                       d.completeTransaction(t, AbortedTransaction)&lt;/span&gt;
&lt;span class="gi"&gt;+                       return fmt.Errorf(&amp;quot;write-write conflict&amp;quot;)&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+       }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        // Update transactions.
&lt;span class="w"&gt; &lt;/span&gt;        t.state = state
&lt;span class="w"&gt; &lt;/span&gt;        d.transactions.Set(t.id, *t)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Lastly, the definition of &lt;code&gt;setsShareItem&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setsShareItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s1Iter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s2Iter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s1Iter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s1Iter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s1Key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s1Iter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s2Iter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s1Key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since Snapshot Isolation shares all the same visibility rules as
Repeatable Read, the tests get to be a little simpler! We'll simply
test that two transactions attempting to commit a write to the same
key fail. Or specifically: that the second transaction cannot commit.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestSnapshotIsolation_writewrite_conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultIsolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SnapshotIsolation&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;write-write conflict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// But unrelated keys cause no conflict.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;no conflict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not bad! But let's get stricter.&lt;/p&gt;
&lt;p class="note note--edit"&gt;
  Upon further discussion with Alex Miller, and after reviewing &lt;a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-95-51.pdf"&gt;A
  Critique of ANSI SQL Isolation Levels&lt;/a&gt;, the difference I am
  trying to suggest (between Repeatable Read and Snapshot Isolation)
  likely does not exist. A Critique of ANSI SQL Isolation Levels
  mentions Repeatable Read must not exhibit P4 (Lost Update)
  anomalies. And it mentions that you must check for read-write
  conflicts to avoid these. Therefore it seems likely that you can't
  easily separate Repeatable Read from Snapshot Isolation when
  implemented using MVCC. The differences between Repeatable Read and
  Snapshot Isolation may more readily show up when implementing
  transactions the classical way with Two-Phase Locking.
  &lt;br /&gt;
  &lt;br /&gt;
  To reiterate, with MVCC and optimistic concurrency control, correct
  implementations of Repeatable Read and Snapshot Isolation do not
  seem to be distinguishable. Both require write-write conflict
  detection.
&lt;/p&gt;&lt;h4 id="serializable-isolation"&gt;Serializable Isolation&lt;/h4&gt;&lt;p&gt;In terms of end-result, this is the simplest isolation level to reason
about. Serializable Isolation must appear as if only a single
transaction were executing at a time. Some systems, like SQLite and
TigerBeetle, do Actually Serial Execution where only one transaction
runs at a time. But few databases implement Serializable like this
because it removes a number of fair concurrent execution
histories. For example, two concurrent read-only transactions.&lt;/p&gt;
&lt;p&gt;Postgres implements serializability via &lt;a href="https://drkp.net/papers/ssi-vldb12.pdf"&gt;Serializable Snapshot
Isolation&lt;/a&gt;. MySQL implements
serializability via &lt;a href="https://distributed-computing-musings.com/2022/02/transactions-two-phase-locking/"&gt;Two-Phase
Locking&lt;/a&gt;. FoundationDB
implements serializability via &lt;a href="https://apple.github.io/foundationdb/developer-guide.html"&gt;sequential timestamp assignment and
conflict
detection&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But the paper, &lt;a href="https://dl.acm.org/doi/abs/10.1145/2168836.2168853"&gt;A critique of snapshot
isolation&lt;/a&gt;,
provides a simple (though not necessarily efficient; I have no clue)
approach via what they call Write Snapshot Isolation. In their
algorithm, if any two transactions read and write set intersect (but
not write and write set intersect), the transaction should be
aborted. And this (plus Repeatable Read rules) is sufficient for
Serializability.&lt;/p&gt;
&lt;p&gt;I leave it to that paper for the proof of correctness. In terms of
implementing it though it's quite simple and very similar to the
Snapshot Isolation we already mentioned.&lt;/p&gt;
&lt;p&gt;Inside of &lt;code&gt;completeTransaction&lt;/code&gt; add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;               }) {
&lt;span class="w"&gt; &lt;/span&gt;                       d.completeTransaction(t, AbortedTransaction)
&lt;span class="w"&gt; &lt;/span&gt;                       return fmt.Errorf(&amp;quot;write-write conflict&amp;quot;)
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+               // Serializable Isolation imposes the additional constraint that&lt;/span&gt;
&lt;span class="gi"&gt;+               // no transaction A may commit after reading any of the same&lt;/span&gt;
&lt;span class="gi"&gt;+               // keys as transaction B has written and committed during&lt;/span&gt;
&lt;span class="gi"&gt;+               // transaction A's life, or vice-versa.&lt;/span&gt;
&lt;span class="gi"&gt;+               if t.isolation == SerializableIsolation &amp;amp;&amp;amp; d.hasConflict(t, func(t1 *Transaction, t2 *Transaction) bool {&lt;/span&gt;
&lt;span class="gi"&gt;+                       return setsShareItem(t1.readset, t2.writeset) ||&lt;/span&gt;
&lt;span class="gi"&gt;+                               setsShareItem(t1.writeset, t2.readset)&lt;/span&gt;
&lt;span class="gi"&gt;+               }) {&lt;/span&gt;
&lt;span class="gi"&gt;+                       d.completeTransaction(t, AbortedTransaction)&lt;/span&gt;
&lt;span class="gi"&gt;+                       return fmt.Errorf(&amp;quot;read-write conflict&amp;quot;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;               }
&lt;span class="w"&gt; &lt;/span&gt;       }
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And if we add a test for read-write conflicts:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestSerializableIsolation_readwrite_conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultIsolation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SerializableIsolation&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot get key that does not exist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 get x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;read-write conflict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c2 commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// But unrelated keys cause no conflict.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;no conflict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustExecCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We see it work! And that's it for a basic implementation of MVCC and
major transaction isolation levels.&lt;/p&gt;
&lt;h3 id="production-quality-testing"&gt;Production-quality testing&lt;/h3&gt;&lt;p&gt;There are two major projects I'm aware of that help you test
transaction implementations: &lt;a href="https://github.com/jepsen-io/elle"&gt;Elle&lt;/a&gt;
and &lt;a href="https://github.com/ept/hermitage"&gt;Hermitage&lt;/a&gt;. These are probably
where I'd go looking if I were implementing this for real.&lt;/p&gt;
&lt;p&gt;This project took me long enough on its own and I felt reasonably
comfortable with my tests that the gist of my logic was right that I
did not test further. For that reason it surely has bugs.&lt;/p&gt;
&lt;h3 id="vacuuming-and-cleanup"&gt;Vacuuming and cleanup&lt;/h3&gt;&lt;p&gt;One of the major things this implementation does not do is cleaning up
old data. Eventually, older versions of values will be required by no
transactions. They should be removed from the value version
array. Similarly, eventually older transactions will be required by no
transactions. They should be removed from the database transaction
history list.&lt;/p&gt;
&lt;p&gt;Even if we had the vacuuming process in place though, what about some
extreme use patterns. What if a key's value was always going to be 1GB
long. And what if multiple transactions made only small changes to the
1GB data. We'd be duplicating a lot of the value across versions.&lt;/p&gt;
&lt;p&gt;It sounds less extreme when thinking about storing rows of data rather
than key-value data. If a user has 100 columns and only updates one
column a number of times, in our scheme we'd end up storing a ton of
duplicate cell data for a row.&lt;/p&gt;
&lt;p&gt;This is a real-world issue in Postgres that was &lt;a href="https://ottertune.com/blog/the-part-of-postgresql-we-hate-the-most"&gt;called
out&lt;/a&gt;
by Andy Pavlo and the Ottertune folks. It turns out that Postgres
alone among major databases stores the entire value for every
version. In contrast, other major databases like MySQL store a diff.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;This post only begins to demonstrate that database behavior differs
quite a bit both in terms of results and in terms of
optimizations. Everyone implements the ideas differently and to
varying degrees.&lt;/p&gt;
&lt;p&gt;Moreover, we have only begun to implement the behavior a real SQL
database supports. For example, how do visibility rules and conflict
detection work with range queries? What about sub-transactions, and
save points? These will have to be covered another time.&lt;/p&gt;
&lt;p&gt;Hopefully seeing this simple implementation of MVCC and visibility rules
helps to clarify at least some of the basics.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Here's a new post walking through an implementation of MVCC and major SQL transaction isolation levels, in 400 lines of Go code.&lt;br /&gt;&lt;br /&gt;These ideas might sound esoteric, but they impact almost every developer using any database.&lt;a href="https://t.co/crFKM74R5h"&gt;https://t.co/crFKM74R5h&lt;/a&gt; &lt;a href="https://t.co/o9awTPpvvx"&gt;pic.twitter.com/o9awTPpvvx&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1791225675287867742?ref_src=twsrc%5Etfw"&gt;May 16, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 16 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-05-16-mvcc.html</guid></item><item><title>Using build.rs to integrate rust applications with system libraries like a pro</title><link>https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/</link><description>&lt;p&gt;I&amp;#8217;m happy to announce the release of version 0.2 of the rsconf crate, with new support for informing Cargo about the presence of custom cfg keys and values (to work around a major change that has resulted in hundreds of &amp;#8230; &lt;a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/"&gt;Using &lt;code&gt;build.rs&lt;/code&gt; to integrate rust applications with system libraries like a pro&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Mon, 13 May 2024 19:24:30 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/</guid></item><item><title>Writing a simple Javascript async message queue server - Part II</title><link>https://bytepawn.com/writing-a-simple-javascript-async-message-queue-server-part-ii.html</link><description>&lt;p&gt;I write a somewhat more complicated, but still relatively simple async message queue server in Javascript.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/js-mq-advanced.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 11 May 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-javascript-async-message-queue-server-part-ii.html</guid></item><item><title>CLI text processing with GNU Coreutils book announcement</title><link>https://learnbyexample.github.io/cli-text-processing-coreutils-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;CLI text processing with GNU Coreutils&lt;/strong&gt; ebook. Examples, descriptions and external links were updated/corrected and 100+ exercises were added.&lt;/p&gt;
&lt;p&gt;You might be already aware of popular coreutils commands like &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;tr&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt; and so on. This book will teach you more than twenty of such specialized text processing tools provided by the &lt;code&gt;GNU coreutils&lt;/code&gt; package.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 10-April-2024. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/cli_coreutils"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/cli_coreutils/c/new_coreutils_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following bundles are heavily discounted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_coreutils_release"&gt;All books bundle&lt;/a&gt; is $12 (normal price $32)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/new_coreutils_release"&gt;Linux CLI Text Processing&lt;/a&gt; bundle is $6 (normal price $20)&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GNU coreutils&lt;/code&gt; package version updated to &lt;strong&gt;9.1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Added 100+ exercises&lt;/li&gt;
&lt;li&gt;In general, many of the examples, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;CLI text processing with GNU Coreutils&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;In my opinion the book does a great job of quickly presenting examples of how commands can be used and then paired up to achieve new or interesting ways of manipulating data. Throughout the text there are little highlights offering tips on extra functionality or limitations of certain commands. For instance, when discussing the &lt;em&gt;shuf&lt;/em&gt; command we're warned that &lt;em&gt;shuf&lt;/em&gt; will not work with multiple files. However, we can merge multiple files together (using the &lt;em&gt;cat&lt;/em&gt; command) and then pass them to &lt;em&gt;shuf&lt;/em&gt;. These little gems of wisdom add a dimension to the book and will likely save the reader some time wondering why their scripts are not working as expected.&lt;/p&gt;
&lt;p&gt;— book review by Jesse Smith on &lt;a href="https://distrowatch.com/weekly.php?issue=20211206#book"&gt;distrowatch.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I discovered your books recently and they’re awesome, thank you! As a 20 year *nix they made me realize how much more there are to these rock solid and ancient tools, once you spend the time to actually learn the intricacies of them.&lt;/p&gt;
&lt;p&gt;— feedback on &lt;a href="https://old.reddit.com/r/commandline/comments/1byumd6/learn_gnu_coreutils_text_processing_tools_like/l2pk5bd/"&gt;reddit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;cat and tac&lt;/li&gt;
&lt;li&gt;head and tail&lt;/li&gt;
&lt;li&gt;tr&lt;/li&gt;
&lt;li&gt;cut&lt;/li&gt;
&lt;li&gt;seq&lt;/li&gt;
&lt;li&gt;shuf&lt;/li&gt;
&lt;li&gt;paste&lt;/li&gt;
&lt;li&gt;pr&lt;/li&gt;
&lt;li&gt;fold and fmt&lt;/li&gt;
&lt;li&gt;sort&lt;/li&gt;
&lt;li&gt;uniq&lt;/li&gt;
&lt;li&gt;comm&lt;/li&gt;
&lt;li&gt;join&lt;/li&gt;
&lt;li&gt;nl&lt;/li&gt;
&lt;li&gt;wc&lt;/li&gt;
&lt;li&gt;split&lt;/li&gt;
&lt;li&gt;csplit&lt;/li&gt;
&lt;li&gt;expand and unexpand&lt;/li&gt;
&lt;li&gt;basename and dirname&lt;/li&gt;
&lt;li&gt;What next?&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/cli_text_processing_coreutils/introduction.html"&gt;https://learnbyexample.github.io/cli_text_processing_coreutils/introduction.html&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/cli_text_processing_coreutils"&gt;https://github.com/learnbyexample/cli_text_processing_coreutils&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/cli_text_processing_coreutils/issues"&gt;https://github.com/learnbyexample/cli_text_processing_coreutils/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 08 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/cli-text-processing-coreutils-announcement/</guid></item><item><title>Exploring 50 user interfaces for AI code suggestions</title><link>https://austinhenley.com/blog/intellicode.html</link><description>&lt;a href="https://austinhenley.com/blog/intellicode.html"&gt;https://austinhenley.com/blog/intellicode.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Wed, 08 May 2024 00:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/intellicode.html</guid></item><item><title>Using DuckDB to seamlessly query a large parquet file over HTTP</title><link>https://www.marginalia.nu/log/a_105_duckdb_parquet/</link><description>&lt;p&gt;A neat property of the parquet file format is that it&amp;rsquo;s designed with block I/O in mind,
so that when you are interested in only parts of the contents of a file, it&amp;rsquo;s possible to
some extent to only read that data. Many tools are aware of this property, and DuckDB
is one of them. Depending on which circles you run in, a lesser known aspect of HTTP
is range requests, where you specify which bytes in a file to be retrieved. It&amp;rsquo;s possible
to combine this trio of properties to read remote parquet files directly in DuckDB.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sun, 05 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_105_duckdb_parquet/</guid></item><item><title>The Rabbit R1 Pricing Myth</title><link>https://3059274a.danpalmer-me.pages.dev/2024-05-05-rabbit-r1-pricing/</link><description>&lt;p&gt;Two new &amp;ldquo;AI&amp;rdquo; devices have just launched, the &lt;a href="https://humane.com/"&gt;Humane AI Pin&lt;/a&gt;, and the &lt;a href="https://www.rabbit.tech/"&gt;Rabbit R1&lt;/a&gt;. Both have &lt;a href="https://www.theverge.com/2024/5/2/24147159/rabbit-r1-review-ai-gadget"&gt;received&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=TitZV6k8zfA"&gt;broadly&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=ddTV12hErTc"&gt;negative&lt;/a&gt; &lt;a href="https://www.theverge.com/24126502/humane-ai-pin-review"&gt;reviews&lt;/a&gt;, criticising a lack of features, alongside the classic issues with modern AI systems – hallucination and confidently incorrect answers. The main distinguishing factor between the two devices has been price. Where the AI Pin sells for $700 with a $24 per month subscription, the R1 is &amp;ldquo;only&amp;rdquo; $199 with &lt;em&gt;no subscription&lt;/em&gt;. This is however, a myth, and while it might be temporarily true, something is going to have to change.&lt;/p&gt;</description><author>Dan Palmer</author><pubDate>Sun, 05 May 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://3059274a.danpalmer-me.pages.dev/2024-05-05-rabbit-r1-pricing/</guid></item><item><title>Writing a simple Javascript async message queue server - Part I</title><link>https://bytepawn.com/writing-a-simple-javascript-async-message-queue-server.html</link><description>&lt;p&gt;I write a simple, bi-directional async message queue server in Javascript.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/js-mq-simple.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 05 May 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-javascript-async-message-queue-server.html</guid></item><item><title>The CP/M emulator development continues</title><link>https://blog.steve.fi/the_cp_m_emulator_development_continues.html</link><description>&lt;p&gt;In &lt;a href="https://blog.steve.fi/a_simple_golang_cp_m_emulator" rel="nofollow"&gt;my previous post&lt;/a&gt; I introduced a toy &lt;a href="https://github.com/skx/cpmulator" rel="nofollow"&gt;CP/M Emulator&lt;/a&gt; I'd been working on.&lt;/p&gt;

&lt;p&gt;At the time it was capable of running the Infocom text-based adventure games, so I thought it was done.  Of course I also wanted to run Microsoft's original BASIC and it turned out that was a challenge because the coding of their interpreter didn't use the standard CP/M entry-point for making syscalls (&lt;code&gt;call 0x0005&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Instead of calling &lt;code&gt;0x0005&lt;/code&gt; to invoke the BDOS/BIOS functions the BASIC interpreter used the single-byte CALL instructions which are available on the Z80 processor.  There are a bunch of these instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RST 00&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RST 08&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RST 10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RST 18&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RST 20&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RST 28&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RST 30&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RST 38&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those instructions is equivalent to a call instruction with a fixed offset, "&lt;code&gt;call 0x0010&lt;/code&gt;", "&lt;code&gt;call 0x0020&lt;/code&gt;", etc.  I had to rework the emulator to cope with this approach, which causes repetition but nothing too surprising.  The end result is that now my emulator can run Microsoft Basic, &lt;a href="https://github.com/dimitrit/tastybasic/" rel="nofollow"&gt;Tasty Basic&lt;/a&gt;, and some more programs.&lt;/p&gt;

&lt;p&gt;Things work but a couple of the syscalls are of the form "Return true if there is a pending keystroke", or "wait until there is keyboard input present and return the first character".  I have some busy-loops which peg the CPU, which sucks but works.  On the downside running the code on a MacOS machine has some weird issues with repeated keys and similar.  So I need to look into fixing that for my own sense of peace.&lt;/p&gt;

&lt;p&gt;I put together a little repository of binaries for playing with though, and that's been helpful.  My emulator has a special flag which treats sub-directories as "Drives".  So A: points to A/, B: points to B/, etc.  That makes distributing and working with things easy!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/skx/cpm-dist" rel="nofollow"&gt;https://github.com/skx/cpm-dist&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Binaries for running on CP/M systems, organized by theme.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Steve Kemp's Blog</author><pubDate>Sat, 04 May 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/the_cp_m_emulator_development_continues.html</guid></item><item><title>The Simply Complicated Automatic Sprinkler</title><link>https://www.petekeen.net/simply-complicated-automatic-sprinkler/</link><description>&lt;p&gt;I've never owned a sprinkler before.
        I've just never had a need for one.&lt;/p&gt;
        &lt;p&gt;Until this past Monday, when I installed some grass seed and couldn't promise myself that I'd water it twice a day every day for the next month as one is supposed to do.&lt;/p&gt;
        &lt;p&gt;A normal person would just buy a mechanical or stand-alone timer device and be done with it.
        Not me.
        No way.&lt;/p&gt;
        &lt;p&gt;My first thought (a completely normal one, mind you) was to grab an esp32 and a relay board out of my parts bin and liberally apply ESPHome to the problem.
        This didn't happen because I recently cleaned my office and put those parts in a bin that I can't quite find right now.&lt;/p&gt;
        &lt;p&gt;&lt;em&gt;cough&lt;/em&gt;. Anyway.&lt;/p&gt;
        &lt;p&gt;My fallback plan was to grab a Z-Wave relay that was more readily at hand and just (&amp;quot;just&amp;quot;) use Home Assistant as the timer.&lt;/p&gt;
        &lt;!--more--&gt;
        &lt;h2 id="parts-list" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/simply-complicated-automatic-sprinkler/#parts-list"&gt;Parts List&lt;/a&gt;&lt;/h2&gt;
        &lt;ul&gt;
        &lt;li&gt;&lt;a href="https://www.getzooz.com/zooz-zen17-universal-relay/"&gt;Zooz Z-Wave ZEN17 Universal Relay&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://www.orbitonline.com/products/l-series-automatic-inline-valve"&gt;Orbit 3/4&amp;quot; inline sprinker valve&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;3/4&amp;quot; NPT to garden hose thread adapters&lt;/li&gt;
        &lt;li&gt;Generic 24VAC transformer&lt;/li&gt;
        &lt;li&gt;5 position terminal block&lt;/li&gt;
        &lt;li&gt;18AWG stranded two conductor silicone jacketed wire&lt;/li&gt;
        &lt;li&gt;some random crimp on butt connectors I had laying around&lt;/li&gt;
        &lt;li&gt;A sprinkler&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;In-ground sprinkler valves in the US pretty universally use 24V AC power.
        This is a very common signaling voltage for HVAC and other controls in residental settings so it makes sense.
        For example, non-smart doorbells are also generally 24-ish VAC.&lt;/p&gt;
        &lt;p&gt;The neatest thing about the Zooz ZEN17 (and it's friend the ZEN16) is that it can be powered by USB-C or 12-24 volt DC &lt;em&gt;or AC&lt;/em&gt;.
        That means I can get away with just one power supply for the whole contraption without having to get any kind of fancy voltage regulator.&lt;/p&gt;
        &lt;p&gt;Sprinker valves come in a variety of sizes, none of which are directly compatible with garden hoses.
        In the US and Canada residental garden hoses have a universal connector size and thread: 3/4&amp;quot; NH (national hose) (or GHT (garden hose thread)), otherwise known as &lt;a href="https://webstore.ansi.org/standards/asme/asmeb1201991r2013"&gt;ANSI B1.20.7&lt;/a&gt;.
        So, I needed some thread adapters.
        Your local hardware store or supermegaglobal online bazaar named after a river in Brazil will provide you with many options.&lt;/p&gt;
        &lt;h2 id="assembly" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/simply-complicated-automatic-sprinkler/#assembly"&gt;Assembly&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;I didn't take any good pictures of the prototyping and assembly process.
        Hopefully these will be sufficient.&lt;/p&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="Relay and power supply" class="thumbnail" src="https://www.petekeen.net/images/sprinkler/inside.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;p&gt;As you can see, the power supply is connected directly to the relay (via a small terminal block affixed to the relay with a command strip) and the red and black wires lead to the valve.
        One of the valve wires is connected directly to the power supply and the other is connected to the NO (normal open) terminal on one of the relays in the ZEN17.
        A short jumper wire is connected from the other leg of the power supply to the C (common) terminal on the same relay.&lt;/p&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="Outside valve assembly" class="thumbnail" src="https://www.petekeen.net/images/sprinkler/valve.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;p&gt;Here's a shot of the valve itself.
        You can see the green valve body, the black solenoid that activates the valve, and the brass thread adapters.
        You can also see the craptastic butt splice things I used, which will be replaced with proper waterproof junctions as soon as they arrive.&lt;/p&gt;
        &lt;p&gt;The grey and orange piece on the right is a quick release adapter.
        I have quick releases on the spigot and all of the hoses, which lets me easily install and remove the valve as needed.&lt;/p&gt;
        &lt;h2 id="home-assistant" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/simply-complicated-automatic-sprinkler/#home-assistant"&gt;Home Assistant&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;A generic relay isn't much good without something to control it, so let's turn to Home Assistant.&lt;/p&gt;
        &lt;p&gt;The central component of the sprinkler controller is a 5 minute timer helper which ties with two automations:&lt;/p&gt;
        &lt;ol&gt;
        &lt;li&gt;
        &lt;p&gt;Sprinkler Start triggers at 8:30am and 5:30pm and starts the timer, but only if the forecast precipitation probability from OpenWeatherMap is less than 50%.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
        &lt;p&gt;Sprinkler Relay triggers whenever the timer changes state.&lt;/p&gt;
        &lt;/li&gt;
        &lt;/ol&gt;
        &lt;p&gt;Sprinkler Relay uses a Choice action with two branches:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;Timer active and relay off -&amp;gt; turn the relay on&lt;/li&gt;
        &lt;li&gt;Timer idle and relay on -&amp;gt; turn the relay off&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;I also have a Time Pattern trigger on Sprinkler Relay that runs every minute.
        This acts as sort of a failsafe, so that if the timer ends during a Home Assistant restart, the sprinkler will shut off with at most an extra minute of runtime.&lt;/p&gt;
        &lt;p&gt;Here's Sprinkler Start in YAML:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-yaml"&gt;alias: Sprinkler Start
        description: &amp;quot;&amp;quot;
        trigger:
        - platform: time
        at: &amp;quot;08:30:00&amp;quot;
        - platform: time
        at: &amp;quot;17:30:00&amp;quot;
        condition:
        - condition: numeric_state
        entity_id: sensor.openweathermap_forecast_precipitation_probability
        below: 50
        action:
        - service: timer.start
        metadata: {}
        data: {}
        target:
        entity_id: timer.sprinkler_runtime
        - service: notify.all_phones
        metadata: {}
        data:
        message: Sprinkler has started
        title: Sprinkler
        mode: single
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;and here's Sprinkler Relay:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-yaml"&gt;alias: Sprinkler Relay
        description: &amp;quot;&amp;quot;
        trigger:
        - platform: state
        entity_id:
        - timer.sprinkler_runtime
        - platform: time_pattern
        minutes: &amp;quot;*&amp;quot;
        condition: []
        action:
        - choose:
        - conditions:
        - condition: state
        entity_id: timer.sprinkler_runtime
        state: idle
        - condition: state
        entity_id: switch.universal_relay_1
        state: &amp;quot;on&amp;quot;
        sequence:
        - service: switch.turn_off
        metadata: {}
        data: {}
        target:
        entity_id: switch.universal_relay_1
        - conditions:
        - condition: state
        entity_id: timer.sprinkler_runtime
        state: active
        - condition: state
        entity_id: switch.universal_relay_1
        state: &amp;quot;off&amp;quot;
        sequence:
        - service: switch.turn_on
        metadata: {}
        data: {}
        target:
        entity_id: switch.universal_relay_1
        mode: single
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;So, what do I have after all of that?
        I have a sprinkler valve that &lt;em&gt;could&lt;/em&gt; be automated with a wrist watch but is &lt;em&gt;actually&lt;/em&gt; automated by a Rube Goldberg-esque stack of technology, including radio protocols, ethernet, virtual machines, Python, Javascript, and Tailscale (because of course).&lt;/p&gt;
        &lt;p&gt;Simple!&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Thu, 02 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/simply-complicated-automatic-sprinkler/</guid></item><item><title>Sloc Cloc and Code - ULOC Unique Lines of Code</title><link>https://boyter.org/posts/sloc-cloc-code-new-metic-uloc/</link><description>&lt;p&gt;I recently pushed out a new release of Sloc Cloc and Code (scc) with two main pieces of functionality. The first being accurate .gitignore support. The latter being the first new feature to hit the codebase in a long time in the form of a new metric you can access Unique Lines of Code or ULOC.&lt;/p&gt;
&lt;p&gt;A few years ago exwhyz (who appears to have dropped off the internet) posted a &lt;a href="https://github.com/boyter/scc/issues/355"&gt;feature request&lt;/a&gt; to include this new metric into &lt;a href="https://github.com/boyter/scc"&gt;scc&lt;/a&gt;&amp;rsquo;s outputs.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 01 May 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/sloc-cloc-code-new-metic-uloc/</guid></item><item><title>Paper review: A Comparison of Approaches to Advertising Measurement</title><link>https://bytepawn.com/a-comparison-of-approaches-to-advertising-measurement.html</link><description>&lt;p&gt;Why are Randomized Controlled Trials (RCTs, known as A/B testing in much of the industry) testing is widely regarded as the golden standard of causal inference? What else can a Data Scientist do if A/B  testing is not possible, and why are those alternatives inferior to A/B testing?&lt;br /&gt;&lt;br /&gt;This papers shows, using 15 experiments (for ads on Facebook) where a RCT was conducted, that common observational methods (run on the Facebook data, by ignoring the control group) severely mis-estimate the true treatment life (as measured by the RCT), often by a factor of 3x or more. This is true, even though Facebook has (i) very large sample sizes, and, (ii) very high quality data (per-user feature vector) about its users which are used in the observational methods. This should be a major red flag for Data Scientists working on common marketing measurements (such as marketing campaigns) using observational methods.&lt;br /&gt;&lt;br /&gt;&lt;img alt="" src="/images/fb-comparison-10.png" style="width: 600px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Wed, 01 May 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/a-comparison-of-approaches-to-advertising-measurement.html</guid></item><item><title>Interactive GNU awk tutorial</title><link>https://learnbyexample.github.io/interactive-awk-tutorial/</link><description>&lt;p&gt;Know command line basics and want to learn the &lt;code&gt;GNU awk&lt;/code&gt; command? Check out my interactive &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/AwkTutorial"&gt;TUI app&lt;/a&gt; that gives a brief tour of this popular text processing command.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for interactive awk tutorial" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/AwkTutorial/awk_tutorial.png" /&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This app is available on PyPI as &lt;a href="https://pypi.org/project/awktutorial/"&gt;awktutorial&lt;/a&gt;. Example installation instructions are shown below, adjust them based on your preferences and OS.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# virtual environment
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; python3&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -m&lt;/span&gt;&lt;span&gt; venv textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cd textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source bin/activate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pip install awktutorial
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# launch the app
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; awktutorial
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the app without having to enter the virtual environment again, add this alias to &lt;code&gt;.bashrc&lt;/code&gt; (or equivalent):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# you'll have to change the path
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;alias &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;awktutorial&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/path/to/textual_apps/bin/awktutorial'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternative to manually managing such virtual environments, you can use &lt;a href="https://github.com/pypa/pipx"&gt;https://github.com/pypa/pipx&lt;/a&gt; instead:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pipx install awktutorial
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; awktutorial
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As yet another alternative, you can install &lt;code&gt;textual&lt;/code&gt; (see &lt;a href="https://textual.textualize.io/getting_started/"&gt;Textual documentation&lt;/a&gt; for more details), clone my &lt;a href="https://github.com/learnbyexample/TUI-apps"&gt;TUI-apps repository&lt;/a&gt; repository and run the &lt;code&gt;awk_tutorial.py&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines).&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="ebook"&gt;Ebook&lt;a class="zola-anchor" href="#ebook"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See my &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; ebook to learn &lt;code&gt;GNU awk&lt;/code&gt; with hundreds of examples and exercises.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd highly appreciate your feedback. Please file &lt;a href="https://github.com/learnbyexample/TUI-apps/issues"&gt;an issue&lt;/a&gt; if there are bugs, crashes, etc.&lt;/p&gt;
&lt;p&gt;Hope you'll find this TUI app useful. Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 30 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/interactive-awk-tutorial/</guid></item><item><title>How I would redesign Copilot Workspace</title><link>https://austinhenley.com/blog/copilotworkspace.html</link><description>&lt;a href="https://austinhenley.com/blog/copilotworkspace.html"&gt;https://austinhenley.com/blog/copilotworkspace.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Tue, 30 Apr 2024 01:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/copilotworkspace.html</guid></item><item><title>Mistakes that data science students make</title><link>https://austinhenley.com/blog/datasciencemistakes.html</link><description>&lt;a href="https://austinhenley.com/blog/datasciencemistakes.html"&gt;https://austinhenley.com/blog/datasciencemistakes.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 28 Apr 2024 23:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/datasciencemistakes.html</guid></item><item><title>Barton Bike Lanes Letter</title><link>http://pxtl.ca/2024/04/28/barton-bike-lanes/</link><description>&lt;p&gt;So the other day I sent in an e-mail to the cycling office at the City of
Hamilton (as well as the relevant councilors) to request a cycling accommodation
in my area.  I figured I may as well include the text of the letter here as
well.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Barton between Wellington and Victoria" src="http://pxtl.ca/images/2024-04-28-Barton-bike-lanes/barton-at-the-general.png" /&gt;&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Sun, 28 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2024/04/28/barton-bike-lanes/</guid></item><item><title>Open Source Summit: Bringing CI/CD Practices to Machine Learning with MLOps</title><link>https://blog.tedivm.com/appearances/2024/04/open-source-summit-bringing-ci-cd-practices-to-machine-learning-with-mlops/</link><description>Robert Hafner at the Open Source Summit: Bringing CI/CD Practices to Machine Learning with MLOps</description><author>tedious ramblings</author><pubDate>Sat, 27 Apr 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/appearances/2024/04/open-source-summit-bringing-ci-cd-practices-to-machine-learning-with-mlops/</guid></item><item><title>“Reading” Articles via Podcast Software</title><link>https://blog.nawaz.org/posts/2024/Apr/reading-articles-via-podcast-software/</link><description>&lt;p&gt;I
&lt;a class="reference external" href="https://blog.nawaz.org/posts/2021/Dec/consuming-articles-offline/"&gt;wrote&lt;/a&gt;
a while ago on reading articles offline by printing them on paper. The
benefit was reduced eye strain and a superior reading experience,
without being tethered to any&amp;nbsp;device.&lt;/p&gt;
&lt;p&gt;I continue to consume articles this way, with one exception: Long-form
articles that are 20+ pages long. I often …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Fri, 26 Apr 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2024/Apr/reading-articles-via-podcast-software/</guid></item><item><title>One hundred million little queries</title><link>https://boyter.org/posts/one-hundred-million-little-queries/</link><description>&lt;p&gt;I recently X&amp;rsquo;ed/Tweeted about how my searchcode.com since Nov 2022 using its custom index &lt;a href="https://boyter.org/posts/how-i-built-my-own-index-for-searchcode/"&gt;https://boyter.org/posts/how-i-built-my-own-index-for-searchcode/&lt;/a&gt; had processed over 50 million queries with a rolling average time around 80ms to process them.&lt;/p&gt;
&lt;p&gt;That was a low-ball estimate though. I actually checked and its actually just over 100,000,000 queries. Which I find astonishing considering its running though a index I wrote.&lt;/p&gt;
&lt;p&gt;However that number excludes all of the attempted queries. As you may expect any public search engine, especially one with a public unauthenticated API is pretty constantly hammered by bots. So what about those?&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Tue, 23 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/one-hundred-million-little-queries/</guid></item><item><title>The Culture of Powerpoint</title><link>https://bytepawn.com/the-culture-of-powerpoint.html</link><description>&lt;p&gt;Last year, in 2023, Dennis Austin, one of the original developers of Powerpoint passed away. The news made it to the front page on Hacker News, and prompted a lively discussion of the merits of Powerpoint itself. I posted a top-level comment, which itself sparked a lively thread of responses. In this post I will expand and extend my points about Powerpoint in a business settings, after pointing to hard-learned lessons from Edward Tufte and Jeff Bezos.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="https://www.edwardtufte.com/bboard/images/0001yB-2238.gif" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 18 Apr 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/the-culture-of-powerpoint.html</guid></item><item><title>Query Parsing and Understanding</title><link>https://www.marginalia.nu/log/a_103_query_parsing/</link><description>&lt;p&gt;Been working on improving Marginalia Search query parsing and understanding. This is going to be a pretty long update, as it&amp;rsquo;s a few months&amp;rsquo; work.&lt;/p&gt;
&lt;p&gt;Apart from cleaning up the somewhat messy query parsing code, a problem I&amp;rsquo;m trying to address is that the search engine is currently only good at dealing with fairly focused queries, they don&amp;rsquo;t need to be short, but if you try to qualify a search that is too broad by adding more terms, it often doesn&amp;rsquo;t produce anything useful.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 17 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_103_query_parsing/</guid></item><item><title>A simple golang CP/M emulator</title><link>https://blog.steve.fi/a_simple_golang_cp_m_emulator.html</link><description>&lt;p&gt;A couple of years ago I &lt;a href="https://blog.steve.fi/writing_a_text_based_adventure_game_for_cp_m" rel="nofollow"&gt;wrote a simple text-based adventure game in Z80 assembly language&lt;/a&gt;, to amuse our child.  The game was written for CP/M, because that is the operating system my single-board Z80-based computer runs upon.&lt;/p&gt;

&lt;p&gt;Later &lt;a href="https://blog.steve.fi/porting_a_game_from_cp_m_to_the_zx_spectrum_48k" rel="nofollow"&gt;I ported the game&lt;/a&gt; to the ZX Spectrum 48k.&lt;/p&gt;

&lt;p&gt;Recently I went through a burst of enthusiasm and started to overhaul the code a little, adding word-wrapping and fixing a couple of bugs.  That lead to a new release, and also a brief amount of (positive) &lt;a href="https://news.ycombinator.com/item?id=39861525" rel="nofollow"&gt;feedback on hacker news&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After mulling it over I realized that the number of CP/M BIOS functions I was using was very minimal, almost only the minimum you'd expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write a character to STDOUT.&lt;/li&gt;
&lt;li&gt;Write a $-terminated string to STDOUT.&lt;/li&gt;
&lt;li&gt;Read a character from STDIN.&lt;/li&gt;
&lt;li&gt;Read a line from STDIN.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It crossed my mind that implementing those syscalls &lt;em&gt;should&lt;/em&gt; be trivial, and if I bundled implementations with a Z80 emulator library I'd have a means of running the game without a real CP/M installation, and without using the ZX Spectrum port.&lt;/p&gt;

&lt;p&gt;So I picked a &lt;a href="https://github.com/koron-go/z80" rel="nofollow"&gt;golang-based Z80 emulator&lt;/a&gt;, and started hacking.&lt;/p&gt;

&lt;p&gt;After a day I had a working system, and I added a few more syscalls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open File, Create File, Delete File, Close File.&lt;/li&gt;
&lt;li&gt;Console I/O.&lt;/li&gt;
&lt;li&gt;Read Record.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that?  I can now play Zork 1, Zork 2, Zork 3, and The Hitchhiker's guide to the Galaxy, from Infocom.&lt;/p&gt;

&lt;p&gt;I suspect I'm "done" for now, though it might be nice to add WriteRecord and the other missing functions there's no obvious use for yet another CP/M, especially with a CCP.&lt;/p&gt;

&lt;p&gt;Still CP/M.  In Golang.  For text-based adventures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/skx/cpmulator" rel="nofollow"&gt;https://github.com/skx/cpmulator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Steve Kemp's Blog</author><pubDate>Tue, 16 Apr 2024 00:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/a_simple_golang_cp_m_emulator.html</guid></item><item><title>Student paper: The Exciting Physics of an Excited Universe</title><link>https://bytepawn.com/the-exciting-physics-of-an-excited-universe.html</link><description>&lt;p&gt;Based on the book Compact Stars by N. K. Glendenning a general relativistic treatment of pressure and energy in compact stars is given, followed by simple models of neutron and quark stars. Hypothetical strange quark matter and strange stars are introduced highlighting the importance of sub-millisecond pulsar detections. (Note: this is a student paper I wrote sometime in 2008-2010.) &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="https://media.springernature.com/full/springer-static/cover-hires/book/978-1-4684-0491-3?as=webp" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 11 Apr 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/the-exciting-physics-of-an-excited-universe.html</guid></item><item><title>Deep Bug</title><link>https://www.marginalia.nu/log/a_104_dep_bug/</link><description>&lt;p&gt;The project has been haunted by a mysterious bug since sometime February. It relates to the code that constructs the index, particularly the code that merges partial indices.&lt;/p&gt;
&lt;p&gt;In short the search engine constucts the reverse index through successive merging of smaller indices, which reduces the overall memory requirement.&lt;/p&gt;
&lt;p&gt;You can conceptualize the revese index itself as two files, one with offset pointers into another file, which has sorted numbers. This code runs after each partition finishes crawling and processing its data, and has a run time of about 4 hours.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 10 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_104_dep_bug/</guid></item><item><title>What makes a great technical blog</title><link>http://notes.eatonphil.com/2024-04-10-what-makes-a-great-tech-blog.html</link><description>&lt;p&gt;I want to explain why the blogs in &lt;a href="https://lists.eatonphil.com/blogs.html"&gt;My favorite technical
blogs&lt;/a&gt; are my favorite. That
page is solely about non-corporate tech blogs. So this post is
too. I'll have to make another list for favorite corporate tech blogs.&lt;/p&gt;
&lt;p&gt;In short, they:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tackle hard and confusing topics&lt;/li&gt;
&lt;li&gt;Show working code&lt;/li&gt;
&lt;li&gt;Make things simpler&lt;/li&gt;
&lt;li&gt;Write regularly&lt;/li&gt;
&lt;li&gt;Talk about tradeoffs and downsides&lt;/li&gt;
&lt;li&gt;Avoid internet slang, memes, swearing, sarcasm, and ranting&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tackle-hard-and-confusing-topics"&gt;Tackle hard and confusing topics&lt;/h3&gt;&lt;p&gt;There are a number of problems in programming and computer science
where otherwise knowledgeable programmers have to start mumbling
about, or revert to cliches or group-think, because they aren't
sure.&lt;/p&gt;
&lt;p&gt;These are the best topics you can possibly dive deep into. And my
favorite writers do exactly this.&lt;/p&gt;
&lt;p&gt;They write about durability guarantees of disks and filesystems. They
write about common pitfalls in benchmarking. They write about database
consistency anomalies. They write about threading and IO models.&lt;/p&gt;
&lt;p&gt;And they write about it by showing concrete examples and concrete logic
so you can learn how to stop handwaving on the topic.&lt;/p&gt;
&lt;p&gt;Their writing helps you come out with a useful mental model you can
apply to your own problems.&lt;/p&gt;
&lt;p&gt;And you know, sometimes it's not about the topic being
obscure. Good writers have the ability to tackle a boring topic in an
interesting light. Maybe by digging deeper into a root cause. Or
showing you the history behind the scenes.&lt;/p&gt;
&lt;p&gt;Moreover, my favorite writers don't know everything. But they also
don't pretend to know everything. They're quick to admit they don't
understand something and ask for help from their readers.&lt;/p&gt;
&lt;h3 id="show-working-code"&gt;Show working code&lt;/h3&gt;&lt;p&gt;I love to see complete working code in a post. In contrast there are
many projects that start out simple and people write an article that
covers the project at a high level. But they keep working on the
project and it becomes more complex.&lt;/p&gt;
&lt;p&gt;It's not always easy to follow commits over time.&lt;/p&gt;
&lt;p&gt;Eli Bendersky and Serge Zaitsev are particularly great at developing
small but meaningful projects in a single post or short series.&lt;/p&gt;
&lt;p&gt;On the other hand, if people only did this, we wouldn't hear about the
development of long-running projects like V8 or Postgres. So I guess
this style has limits. And I don't penalize people talking about
long-running projects for not showing working code.&lt;/p&gt;
&lt;h3 id="make-things-simpler"&gt;Make things simpler&lt;/h3&gt;&lt;p&gt;One of the marks of a good writer is that you can make complex
topics simple. And not just by being reductive. Though sometimes even
being reductive is useful for education.&lt;/p&gt;
&lt;p&gt;In contrast I sometimes see articles by less experienced writers and I
marvel how they make a simple topic so complex. I recognize this
because I was &lt;em&gt;absolutely&lt;/em&gt; like that 10 years ago, if not 5 years ago.&lt;/p&gt;
&lt;h3 id="write-regularly"&gt;Write regularly&lt;/h3&gt;&lt;p&gt;My favorite blogs typically get a new post at least once a month. Some
people, like Murat, write once a week.&lt;/p&gt;
&lt;p&gt;I think the practice probably does improve your writing but mostly
it's that they keep my attention by publishing regularly!&lt;/p&gt;
&lt;h3 id="talk-about-tradeoffs-and-downsides"&gt;Talk about tradeoffs and downsides&lt;/h3&gt;&lt;p&gt;Nothing builds trust like talking about the issues with something you
built. No project is perfect. And to ignore the downsides risks
seeming like you don't know or understand them.&lt;/p&gt;
&lt;p&gt;So the writers I like the most talk about decisions in context. They
talk about the good and the bad.&lt;/p&gt;
&lt;h3 id="avoid-internet-slang,-memes,-swearing,-sarcasm,-and-ranting"&gt;Avoid internet slang, memes, swearing, sarcasm, and ranting&lt;/h3&gt;&lt;p&gt;There's no way I can think of talking about this without sounding
super lame.&lt;/p&gt;
&lt;p&gt;One thing I've noticed, particularly among younger colleagues, is the
use of memes or swearing or using 4chan slang or using sarcasm. I
used to write like this 10 years ago too.&lt;/p&gt;
&lt;p&gt;There is a chunk of your audience who won't care. The problem is that
there's also a chunk of your (potential) audience who definitely does
care. There's even a chunk of your audience who may not care but just
won't understand (i.e. non-native English speakers).&lt;/p&gt;
&lt;p&gt;I have friends and folks I respect who write very well. But that also
are also overly, unnecessarily edgy when they write. I don't like
sharing these posts because I don't want to unnecessarily offend or
turn off people either.&lt;/p&gt;
&lt;h3 id="closing-thoughts"&gt;Closing thoughts&lt;/h3&gt;&lt;p&gt;It would be boring if everyone wrote the same way. I'm glad the
internet is fun and weird. But I wanted to share a few things
that go into my favorite technical blogs that I'm always happy to
refer people to.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a short post on what I think makes a great technical blog.&lt;a href="https://t.co/QRFtQyQyU5"&gt;https://t.co/QRFtQyQyU5&lt;/a&gt; &lt;a href="https://t.co/QpsQC90EX5"&gt;pic.twitter.com/QpsQC90EX5&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1778184061447774328?ref_src=twsrc%5Etfw"&gt;April 10, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 10 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-04-10-what-makes-a-great-tech-blog.html</guid></item><item><title>Paper: Monte Carlo Experiments of Network Effects in Randomized Controlled Trials</title><link>https://bytepawn.com/paper-monte-carlo-experiments-of-network-effects-in-randomized-controlled-trials.html</link><description>&lt;p&gt;I run Monte Carlo simulations of content production over random Watts-Strogatz graphs to show various effects relevant to modeling and understanding Randomized Controlled Trials on social networks: the network effect, spillover effect, experiment dampening effect, intrinsic dampening effect, clustering effect, degree distribution effect and the experiment size effect. I will also define some simple metrics to measure their strength. When running experiments these potentially unexpected effects must be understood and controlled for in some manner, such as modeling the underlying graph structure to establish a baseline. &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/paper-monte-carlo-experiments-of-network-effects-in-randomized-controlled-trials.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Tue, 09 Apr 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/paper-monte-carlo-experiments-of-network-effects-in-randomized-controlled-trials.html</guid></item><item><title>Connecting the dots</title><link>https://blog.bayindirh.io/blog/connecting-the-dots/</link><description>&lt;blockquote&gt;
&lt;p&gt;You can’t connect the dots looking forward; you can only connect them looking backwards.
-- Steve Jobs&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Normally, I’m not a quote person, and don’t get inspired by them much. Sometimes they provide great insights, but even in that case, I prefer to strain them through my own experience and see what remains.&lt;/p&gt;
&lt;p&gt;However, in recent times, I’m experiencing a domino effect in my digital life and organization. I was able to realize almost all my dreams without realizing. This made me wonder how I did this in a short amount of time, at least for me.&lt;/p&gt;
&lt;p&gt;I have a homepage (or landing page if you prefer the term) for a long time now. It all started with flavors.me, a now defunct service providing customizable landing/link pages which I used with a subdomain of a domain owned by a family member. After they closed shop, I continued to use the same subdomain directly. Then, one late night I decided to buy my own domain on a whim, and &lt;a href="https://www.bayindirh.io"&gt;bayindirh.io&lt;/a&gt; was born.&lt;/p&gt;
&lt;p&gt;The domain sat idle at first. Then I installed a web server to my “utility” cloud server which I used as an external eye to test things. This allowed to move my homepage there. Gotten bit by the self-hosting bug, I started to move services onto that server, streamlining tons of things in the process.&lt;/p&gt;
&lt;p&gt;Next was my blog. I have experimented with Jekyll for a long time. While the idea was to generate “static” web pages, none of the useful templates were truly static, and making your own template was requesting an amount of time I didn’t have. As a result, the blog idea was frozen until which I found this platform, which allowed me to use my own domain, and being equally minimalist at the same time.&lt;/p&gt;
&lt;p&gt;Same story happened for my &lt;a href="https://notes.bayindirh.io"&gt;digital garden&lt;/a&gt;: After finding a suitable solution which allowed to bring my own domain, everything clicked.&lt;/p&gt;
&lt;p&gt;As it turned out, the act of buying a domain in the middle of a night because of me wanting a “permanent address” on the internet changed everything. Slowly in the beginning, but faster as I walked along the path. Now, that domain serves (directly or indirectly) all the things I wanted to have and grants me freedom to tinker, explore and do things by myself.&lt;/p&gt;
&lt;p&gt;Without realizing, I have removed a great deal of friction from a lot of processes by buying that domain, and this allowed me to find and explore more services with the same frictionless nature and connect under my own domain, giving me a distributed yet interconnected infrastructure which allows me to create and communicate with the world.&lt;/p&gt;
&lt;p&gt;While this process was a small step for humanity, it was a big one for me, and started a nice domino effect allowing me to realize my dreams and learn a lot, which I can only see when I look backwards.&lt;/p&gt;
&lt;p&gt;Perhaps, Steve Jobs was right. Everybody should look back now and then. Because doing it makes you understand what happened for what reason, and makes finding the next dot easier. However, this is a subject for a future post.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Mon, 08 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/connecting-the-dots/</guid></item><item><title>Peak personal computing, or, my current workstation setup</title><link>https://bytepawn.com/peak-personal-computing.html</link><description>&lt;p&gt;Even since I've had a 386 in my room as a teenager, I've taken computers apart and modified and upgraded them. When getting a new workstation computer, I have never bought a pre-assembled one (like a Dell or HP), I always buy the components and build it myself. I've been doing this for 30+ years, so I'm used to it, it's part of my lifecycle. I built my current workstation in 2022, but due to reasons I describe in this post, I've been making a fair amount of modifications to it ever since. Finally, now in 2024 April, I feel that the system has come together and is perfect — I don't want to or plan to make any more changes to it. &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/my-desktop.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 08 Apr 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/peak-personal-computing.html</guid></item><item><title>Dieting and Differential Equations: Part 3, Tracking Calories using LLM</title><link>https://jodavaho.io/posts/dieting-differential-equations-3.html</link><description>&lt;h2 id="executing-the-strategy"&gt;Executing the strategy&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s really hard to know how much you&amp;rsquo;re eating.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s really annoying seeing weight go up, but noise around the signal is normal, using a &amp;ldquo;predictor&amp;rdquo; helps me ralize this&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s beneficial to have a running estimate for when you&amp;rsquo;ll reach your goal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I set out and used this to calculate a daily budget of food. I&amp;rsquo;ve been at it 40 days, and I&amp;rsquo;ve lost 11 lbs (average 0.275 lbs/day).&lt;/p&gt;
&lt;p&gt;There are two very hard things about calorie restricted diets. The first &lt;em&gt;actually knowing&lt;/em&gt; how many calories you&amp;rsquo;re eating. And the second is &lt;em&gt;knowing if it&amp;rsquo;s working&lt;/em&gt;. Math is all well and good, but if it doesn&amp;rsquo;t match reality, it&amp;rsquo;s not useful.&lt;/p&gt;
&lt;h3 id="tracking-calories"&gt;Tracking calories&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve been using ChatGPT-4 to estimate my calorie intake from pictures, descriptions, and rough notes. It&amp;rsquo;s been &lt;em&gt;amazing&lt;/em&gt;. Somehow, training on language has resulting in memorizing the calorie content of every food item in the world, or so it would seem.&lt;br /&gt;
There&amp;rsquo;s three things ChatGPT-4 does for me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It answers adhoc questions about calorie estimates with prompts like &amp;ldquo;How many calories are in this plate of food (via picture), or &amp;ldquo;How many calories are in a McDonald&amp;rsquo;s Big Mac?&amp;rdquo;. These results help me order food.&lt;/li&gt;
&lt;li&gt;It helps me track my daily intake. The initial prompt is below, then I fire off curt messages like &amp;ldquo;I ate a banana&amp;rdquo; or &amp;ldquo;I had a 20oz soda&amp;rdquo; or &amp;ldquo;new day: weight 212.2&amp;rdquo; and it accumulates data for me.&lt;/li&gt;
&lt;li&gt;Finally, at the end of every day, I can pull out the summary, put it into a file, and run a plotter on it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The prompt is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Here is some json that represents what I need your help tracking. I will say things like "add a bowl of cheesits" and you will estimate the calories in that food item, add it to the running total for each day, and dump the revised json. I will also provide a weight once a day, and will occasionally provide exercises, for which you must also update the exercise field. The "net" field can be ignored but please include it with value zero. Is any part of this unclear? You're helping me track diet progress every day, with additional data from activities and meals throughout each day. 

{
  "Date": "2020-01-01",
  "Weight": 0,
  "Calories": 0,
  "Exercise": 0,
  "Pushups": 0,
  "Notes": "",
  "Net": 0 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And a typical interaction just happened as I was writing this, here it is with typos and all:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Me: 
Add a pb sandwich, and two chicken nuggest, target brand. 
ChatGPT:
Adding a peanut butter sandwich and two Target brand chicken nuggets to your day's intake:

1. Peanut Butter Sandwich:
   - Bread (2 slices): Approximately 160 calories for two slices of wheat bread.
   - Peanut Butter: For about 2 tablespoons, it's roughly 188 calories.

   Total for the peanut butter sandwich: approximately 348 calories.

2. Two Target Brand Chicken Nuggets: Target brand (such as Market Pantry) chicken nuggets typically contain about 45-50 calories each. For two nuggets, let's use 100 calories as an estimate.

Adding these items to the day's total:

- Peanut Butter Sandwich: 348 calories
- Two Chicken Nuggets: 100 calories

Total additional intake: 448 calories

Here's the updated information:

json [copy]
{
  "Date": "2024-04-01",
  "Weight": REDACTED,
  "Calories": 1008,
  "Exercise": 0,
  "Pushups": 0,
  "Notes": "",
  "Net": 0
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;How accurate is this? The chicken nuggets are about 80 each, and the brad is
70/ slice. But I was misleading. I actually ate Good and Gather, and Market
Pantry actually is about 50-60 cal each.  But for my purposes, it&amp;rsquo;s so easy to
use that I don&amp;rsquo;t care if we&amp;rsquo;re off by 20%, since I am also back-estimating
calories from my weight loss, as we&amp;rsquo;ll see next.&lt;/p&gt;
&lt;p&gt;At this point, I&amp;rsquo;m content with the process. But I&amp;rsquo;d like a better answer to
the accuracy question. To get this, we can do some statistical magic and
estimate total calories and activity levels from my weight data (and the prior
estimates). This might allow us to pin down the accuracy of the ChatGPT-4
estimates and my own exercise estimates.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s next time!&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Sat, 06 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dieting-differential-equations-3.html</guid></item><item><title>A paper reading club at work; databases and distributed systems research</title><link>http://notes.eatonphil.com/2024-04-05-company-paper-club.html</link><description>&lt;p&gt;I started a paper reading club this week at work, focused on databases
and distributed systems research. I posted in a general channel about
the premise and asked if anyone was interested. I clarified the
intended topic and that discussions would be asynchronous over email,
run fortnightly.&lt;/p&gt;
&lt;p&gt;Weekly seemed way too often. A month seemed way too
infrequent. Fortnightly seemed decent.&lt;/p&gt;
&lt;p&gt;I was nervous to do this because I've been here about 2 months. In the
past I would have waited 6 months or a year to do this. But I don't
know. If you see something you think should exist, why wait?&lt;/p&gt;
&lt;p&gt;The only other consideration was &lt;a href="https://notes.eatonphil.com/eight-years-of-tech-meetups.html"&gt;past experiences I've written
about&lt;/a&gt;
having difficulty getting engagement with clubs at work. But EDB has
near 1,000 employees. I figured there might at least be a couple
interested.&lt;/p&gt;
&lt;p&gt;Furthermore I figured if I only got a few people this entire idea
would at least benefit myself, since I have been wanting to force
myself to build a paper reading habit. And if no one responded, it
would be only mildly embarassing and I'd not pursue it further.&lt;/p&gt;
&lt;p&gt;But after a day, about 6 people showed interest. Which was better than
I hoped! Folks from product management, support, development, and
beyond.&lt;/p&gt;
&lt;p&gt;So I opened a dedicated channel and asked people to start submitting
papers and voting on them. One of my teammates started submitting some
great papers on caches and reference counting.&lt;/p&gt;
&lt;p&gt;I picked a first one, the Redshift paper, to get us started.
Demonstrating the process to avoid confusion. And I made a calendar
invite for everyone in the channel, the paper linked in the invite. I
clarified in the invite that it was just a reminder and that the real
discussion would still be async over email. (I've found it's best to
repeatedly clarify process stuff.)&lt;/p&gt;
&lt;p&gt;Once I had these first few folks interested I was able to post again
in a broader company channel that a couple of us were starting this
paper club. By the end of the day the dedicated channel was 29
folks. All in about 2 days.&lt;/p&gt;
&lt;p&gt;Mailing lists are nicer than Slack or Discord in my opinion because
they sort of force you to slow down, they are harder to miss (if
someone starts a thread after you've seen a message in Slack or
Discord, you tend to miss it), and easier to manage
(read/unread).&lt;/p&gt;
&lt;p&gt;Engineers often seem to get overwhelmed by a mass of Slack
messages. Whereas they seem to be a bit more comfortable with email
threads.&lt;/p&gt;
&lt;p&gt;All of this is all the more important when you're running a global
group. EDB has people everywhere.&lt;/p&gt;
&lt;p&gt;Why do this?&lt;/p&gt;
&lt;p&gt;Before I dropped out of college I did a research internship with a
VLSI group at Harvard SEAS. And my favorite part was that they had a
weekly (or biweekly?) Wednesday paper reading session where 15 people
from the lab and adjacent labs would eat pizza after hours and discuss
a paper.&lt;/p&gt;
&lt;p&gt;I've been dying to recreate this at a company ever since. Since EDB is
so distributed, we won't be discussing over pizza. But I'm still
excited.&lt;/p&gt;
&lt;p&gt;And I hope my experience serves as a blueprint for others.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I started a paper reading club at work, wrote about it as a possible blueprint for others.&lt;br /&gt;&lt;br /&gt;I'm excited! I've wanted to have a gang at work with whom to read papers for a long time.&lt;a href="https://t.co/vpwERj8pHe"&gt;https://t.co/vpwERj8pHe&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1776415593173782938?ref_src=twsrc%5Etfw"&gt;April 6, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Fri, 05 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-04-05-company-paper-club.html</guid></item><item><title>I ❤️ open source, and, Cloud9 still rocks</title><link>https://bytepawn.com/i-love-open-source.html</link><description>&lt;p&gt;For the past 8 years I've been using Cloud9 for writing code in the cloud, and I can wholeheartedly recommend it: it just works for me. 7 years ago AWS acquired the company, so the project is no longer developed. However, this is not a problem, since it still just works. The only problem I ever had was a minor UI bug, which I fixed in a couple of minutes, as detailed in this post. This was possible because Cloud9 is open-source. I ❤️ open source. &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/cloud9.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 05 Apr 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/i-love-open-source.html</guid></item><item><title>Dieting and Differential Equations: Part 2, Closed Loop diets</title><link>https://jodavaho.io/posts/dieting-differential-equations-2.html</link><description>&lt;p&gt;OK, let&amp;rsquo;s talk about my diet.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve started a diet, with the goal of losing 32 lbs, or ~15% of my bodyweight,
to get back to my circa 2000-2005 weight. I&amp;rsquo;m happy if I achieve 80% of that
loss, ending up under 200. This is a long post, in which I describe the
plan, and some misunderstandings I had about dieting and weight loss.&lt;/p&gt;
&lt;p&gt;Along the way, I built an estimator that uses data to determine my real calorie
intake, needs, and activity level. The results, for me, were surprising.&lt;/p&gt;
&lt;p&gt;In practice, the hardest part about calorie deficits is &lt;em&gt;tracking&lt;/em&gt; calories.
Burying the lede a little, it turns out LLM / ChatGPT-4 is &lt;em&gt;fantastic&lt;/em&gt; at
counting calories for you from pictures, descriptions, or rough notes.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll cover all that.&lt;/p&gt;
&lt;h2 id="whats-a-diet"&gt;What&amp;rsquo;s a diet&lt;/h2&gt;
&lt;p&gt;My definition of diet is &lt;em&gt;extended calorie deficit for the purposes of losing
bodyfat&lt;/em&gt;. I do not differentiate types of food, but I prefer high protein for
appetite control. I also don&amp;rsquo;t do time-based restriction, though I find that
skipping breakfast and not eating after dinner is sustainable, and that&amp;rsquo;s
roughly an 18/6.&lt;/p&gt;
&lt;p&gt;From the first two &lt;a href="https://jodavaho.io/tags/diet.html"&gt;parts&lt;/a&gt; of this series, we have a few equations to work with:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mifflin-St Jeor&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; equation for Basal Metabolic Rate (BMR) is
\( 10 \times weight(kg) + 6.25 \times height(cm) - 5 \times age(y) + G\),
where \(G\) is 5 for men, and -161 for women.&lt;/li&gt;
&lt;li&gt;Total Daily Energy Expenditure (TDEE) is
\(BMR \times L\), where \(L\) is a multiplier for your daily activity level with specific values ranging from &amp;ldquo;Sedentary&amp;rdquo; to &amp;ldquo;Extra Active&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Steady state weight&amp;rdquo; (SSW) = \(\frac{1}{10}\left(cal - L \times [6.25 \times height - 5 \times age + G]\right)\)&lt;/li&gt;
&lt;li&gt;A weight loss model, \(w(t) = SSW + (w_0 - SSW) e^{-\frac{10L}{B}(t)}\)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or, tidied up a bit:&lt;/p&gt;
$$
\begin{aligned}
BMR =&amp; 10 w + 6.25 H - 5A + G \\\\
&amp;\text{  with } G \in \\{5, -161\\}\\\\
TDEE =&amp;amp;L \cdot BMR\\\\
&amp;\text{  with } L \in \\{1.2, 1.375, 1.55, 1.725, 1.9\\}\\\\
 SSW =&amp; \frac{cal - L\cdot [6.25 H - 5 A + G]}{10} \\\\
w(t) =&amp; SSW + (w_0 - SSW) e^{-\frac{10L}{B}(t)} \\\\
&amp;\text{  with } B = 3500 | 7700 \text{ calories per unit lb|kg}\\\\
\end{aligned}
$$&lt;p&gt;See &lt;a href="https://jodavaho.io/tags/diet.html"&gt;previous posts&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;From this we&amp;rsquo;re accumulating python code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;from&lt;/span&gt; enum &lt;span style="color: #f92672;"&gt;import&lt;/span&gt; Enum
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #66d9ef;"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;bmr_m&lt;/span&gt;(weight, height, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; weight &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;bmr_f&lt;/span&gt;(weight, height, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; weight &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;161&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;class&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;Activity&lt;/span&gt;(Enum):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    SEDENTARY &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    LIGHT &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.375&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    MODERATE &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.55&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    VERY &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.725&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    EXTRA &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;calc_tdee&lt;/span&gt;(bmr, activity):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Calculate Total Daily Energy Expenditure
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (activity &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.2&lt;/span&gt; &lt;span style="color: #f92672;"&gt;or&lt;/span&gt; activity &lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.9&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;raise&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;ValueError&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;"Activity must be between 1.2 and 1.9"&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; bmr &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; activity
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;steady_state_weight&lt;/span&gt;(calories, activity, height, gender, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Reverse BMR to find weight given calories
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Accepts calories per day, activity level, height in cm, 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    gender='m'|'f', age in years
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Returns weight in kg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    L &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; activity
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; gender&lt;span style="color: #f92672;"&gt;==&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;'m'&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; calories &lt;span style="color: #f92672;"&gt;/&lt;/span&gt;  L &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; wt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; gender &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;'f'&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; calories &lt;span style="color: #f92672;"&gt;/&lt;/span&gt;  L &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;161&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; wt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;raise&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;ValueError&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;'sex: m|f only'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;weight_over_time&lt;/span&gt;(days, calories, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                     ht, age, gender, wt0, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                     activity&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;Activity&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;SEDENTARY&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;value):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Given you want to consume `calories` per day, over the range of days, what
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    will your weight be?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Accepts wt0 in kg, ht in cm, gender='m'|'f', age in years
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Returns a list of weights over time, one per day, in kg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    SSW &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; steady_state_weight(calories, activity, ht, gender, age)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    t &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; np&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;arange(&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;, days)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    exparr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; np&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;exp(&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;activity&lt;span style="color: #f92672;"&gt;/&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;7700&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt; t)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    res &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; SSW &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; exparr &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; (wt0&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;SSW)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; res
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We now have a vague sense that &amp;ldquo;in a few years&amp;rdquo; we&amp;rsquo;ll hit a steady state
weight, and that we can lose weight faster or slower by choosing a different
calorie intake.&lt;/p&gt;
&lt;p&gt;We can also manually tweak the &lt;code&gt;Activity&lt;/code&gt; enum to see how different activity
levels affect our weight loss, or try different calorie intakes to try to hit a
goal.&lt;/p&gt;
&lt;p&gt;As hinted when we started, what people want is: &amp;ldquo;When will I hit my goal
weight?&amp;rdquo; or &amp;ldquo;How many calories do I need to cut to hit my goal weight by a
certain date?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;These are &lt;em&gt;real&lt;/em&gt; optimization problems, and now we have the math to solve them.&lt;/p&gt;
&lt;h2 id="deriving-calorie-intake-to-hit-a-goal-weight-by-a-certain-date"&gt;Deriving calorie intake to hit a goal weight by a certain date&lt;/h2&gt;
&lt;p&gt;We derived this equation:&lt;/p&gt;
$$w(t) = SSW(cal,L)\cdot\left(1-e^{-\frac{10L}{B}(t)}  \right)+ w_0 e^{-\frac{10L}{B}(t)} \text{} $$&lt;p&gt;Given above closed form equation, we can answer the question &amp;ldquo;If I want to lose
30 lbs by mid-summer, how many calories do I need to cut?&amp;rdquo; What we&amp;rsquo;re asking
is: &amp;ldquo;Given t=&lt;code&gt;days to end of diet&lt;/code&gt;, what is the value of &lt;code&gt;calories&lt;/code&gt; that will
result in &lt;code&gt;w(t) = w_0 - &amp;lt;amount to lose&amp;gt;&lt;/code&gt;?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;To lose 10 kg in the next 90 days, form:&lt;/p&gt;
$$w_0 - 10 = SSW(x,L)\cdot\left(1-e^{-\frac{10L}{B}(90)}  \right)+ w_0 e^{-\frac{10L}{B}(90)} \text{} $$&lt;p&gt;and solve for \(x\).&lt;/p&gt;
&lt;p&gt;The algorithm is essentially:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Solve \(C_e = e^{-\frac{10L}{B}(90)}\)&lt;/li&gt;
&lt;li&gt;Calculate \(C_p = (6.25 H - 5 A + G) / 10L\)&lt;/li&gt;
&lt;li&gt;Plug into \(w_0 - 10 = \left(\frac{cal}{10L} - C_p\right)\cdot\left(1-C_e  \right)+ w_0 C_e \)&lt;/li&gt;
&lt;li&gt;which reduces to \(cal = 10L \left( w_0 - \frac{10}{1-C_e} + C_p\right) \)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;# import numpy as np # already imported above, but just remember&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;predict_calories_to_lose&lt;/span&gt;(days, wt0, wt_target, ht, age, activity&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;Activity&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;SEDENTARY&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;value):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Given you want to lose `wt0 - wt_target` (kg!) in `days`, what should your
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    daily calorie intake be?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Accepts wt0 in kg, ht in cm, age in years
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Returns calories per day
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    C_e &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; np&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;exp(&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;activity&lt;span style="color: #f92672;"&gt;/&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;7700&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; days)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    G &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #75715e;"&gt;# or -161 for biological females&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    C_p &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; ht &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; G) &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; activity &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; (wt0 &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; (wt0 &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; wt_target) &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt; &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; C_e) &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; C_p)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="determining-arrival-time-at-a-given-weight-from-a-given-strategy"&gt;Determining arrival time at a given weight from a given strategy&lt;/h2&gt;
&lt;p&gt;You may also want to know when you&amp;rsquo;ll hit a given weight, given a strategy. This is no more difficult, we just solve for \(t\) in the above equation.&lt;/p&gt;
$$\begin{aligned}
w(t) &amp;= SSW + (w_0 - SSW) e^{-\frac{10L}{B}(t)}  &amp;\text{} \\\\
w_{target} &amp;= SSW(calories,L) + (w_0 - SSW(calories,L)) e^{-\frac{10L}{7700}(t)}  &amp;\text{} \\\\
w_{target} - SSW(calories,L) &amp;= (w_0 - SSW(calories,L)) e^{-\frac{10L}{7700}(t)}  &amp;\text{} \\\\
\frac{w_{target} - SSW(calories,L)}{w_0 - SSW(calories,L)} &amp;= e^{-\frac{10L}{7700}(t)}  &amp;\text{} \\\\
\ln\left(\frac{w_{target} - SSW(calories,L)}{w_0 - SSW(calories,L)}\right) &amp;= -\frac{10L}{7700}(t)  &amp;\text{} \\\\
t &amp;= -\frac{7700}{10L} \ln\left(\frac{w_{target} - SSW(calories,L)}{w_0 - SSW(calories,L)}\right)  &amp;\text{} \\\\
\end{aligned}$$&lt;p&gt;Note, the fraction \(\frac{w_{target} - SSW(calories,L)}{w_0 - SSW(calories,L)}\)
is the fraction of the way to the target weight, and, assuming you&amp;rsquo;re losing
weight, is always less than 1. It somewhat curiously represents &amp;ldquo;target percent
excess after diet&amp;rdquo; in a way.&lt;/p&gt;
&lt;p&gt;This yields a simple algorithm:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;predict_days_to_weight&lt;/span&gt;(wt_target, calories, wt0, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                           ht, gender, age, activity&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;Activity&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;SEDENTARY&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;value):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Given you want to hit `wt_target` (kg!) on a diet of `calories` per day and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    activity level `activity`, how many days will it take?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Accepts wt0 in kg, ht in cm, age in years, gender='m'|'f'
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Returns days
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    SSW &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; steady_state_weight(calories, activity, ht, gender, age)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    numerator &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt_target &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; SSW
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    denominator &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt0 &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; SSW
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #f92672;"&gt;-&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;7700&lt;/span&gt;&lt;span style="color: #f92672;"&gt;/&lt;/span&gt;(&lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;activity) &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; np&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;log(numerator&lt;span style="color: #f92672;"&gt;/&lt;/span&gt;denominator)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So there you have it! A mathy way to come up with your own activity / calorie balance that makes sense for you.&lt;/p&gt;
&lt;p&gt;In case you&amp;rsquo;re curious I ran my situation as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Start &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; to_kg(&lt;span style="color: #ae81ff;"&gt;222&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;End &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; to_kg(&lt;span style="color: #ae81ff;"&gt;190&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Diff &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; End &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; Start
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;calsed &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; predict_calories_to_lose(&lt;span style="color: #ae81ff;"&gt;158&lt;/span&gt;, Start, End, to_cm(&lt;span style="color: #ae81ff;"&gt;77&lt;/span&gt;), &lt;span style="color: #ae81ff;"&gt;43&lt;/span&gt;, Activity&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;SEDENTARY&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;value)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;calslight &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; predict_calories_to_lose(&lt;span style="color: #ae81ff;"&gt;158&lt;/span&gt;, Start, End, to_cm(&lt;span style="color: #ae81ff;"&gt;77&lt;/span&gt;), &lt;span style="color: #ae81ff;"&gt;43&lt;/span&gt;, Activity&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;LIGHT&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;value)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;calsmod &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; predict_calories_to_lose(&lt;span style="color: #ae81ff;"&gt;158&lt;/span&gt;, Start, End, to_cm(&lt;span style="color: #ae81ff;"&gt;77&lt;/span&gt;), &lt;span style="color: #ae81ff;"&gt;43&lt;/span&gt;, Activity&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;MODERATE&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;value)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and got:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SED Calories to lose -32.0 lbs in 158 days: 1625.19
LIGHT Calories to lose -32.0 lbs in 158 days: 1964.75
MOD Calories to lose -32.0 lbs in 158 days: 2304.17
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, I&amp;rsquo;ve chosen 2000 as a target, revived the running and weightlifting, and I&amp;rsquo;m on my way.&lt;/p&gt;
&lt;p&gt;In the next part, we&amp;rsquo;ll discuss how I track caloires, which is the hardest part, honestly.&lt;/p&gt;
&lt;p&gt;But for now, a teaser on my progress, for which the goal arrival is updated
daily. We&amp;rsquo;ll talk about this in the next post.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Weight Loss" src="https://jodavaho.io/img/dieting-2-predicted_weight_loss.png" /&gt;&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Basal_metabolic_rate"&gt;https://en.wikipedia.org/wiki/Basal_metabolic_rate&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Thu, 04 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dieting-differential-equations-2.html</guid></item><item><title>Resolve "database is locked" with Go and SQLite</title><link>https://boyter.org/posts/go-sqlite-database-is-locked/</link><description>&lt;p&gt;I have always been a big fan of SQLite and recently was using it with Go/Golang. However due to fact that goroutines are very convenient I quickly ran into the &amp;ldquo;database is locked&amp;rdquo; issue where multiple writes would try to update the database at any one time. Multiple reads worked without issue.&lt;/p&gt;
&lt;p&gt;I could have solved this by throwing a mutex lock around the database writes, but thats something I could easily mess up by missing the mutex and locking the database at the wrong time. I could also have retries with a backoff which isn&amp;rsquo;t a bad idea for resilience, but overkill for my situation.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 04 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/go-sqlite-database-is-locked/</guid></item><item><title>Dieting and Differential Equations: Part 1, Predicting Weight Loss</title><link>https://jodavaho.io/posts/dieting-differential-equations-1.html</link><description>&lt;p&gt;OK, let&amp;rsquo;s talk about my diet some more.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve started a diet, with the goal of losing 32 lbs, or ~15% of my bodyweight,
to get back to my circa 2000-2005 weight. I&amp;rsquo;m happy if I achieve 80% of that
loss, ending up under 200. This is a long post, in which I describe the
plan, and some misunderstandings I had about dieting and weight loss.&lt;/p&gt;
&lt;p&gt;Along the way, I built an estimator that uses data to determine my real calorie
intake, needs, and activity level. In practice, the hardest part about calorie
deficits is &lt;em&gt;tracking&lt;/em&gt; calories. Burying the lede a little, it turns out LLM /
ChatGPT-4 is &lt;em&gt;fantastic&lt;/em&gt; at counting calories for you from pictures,
descriptions, or rough notes.&lt;/p&gt;
&lt;p&gt;This is part two of a series. The first part disucsses basic math for calorie
needs and steady state weight, and is &lt;a href="https://jodavaho.io/posts/dieting-differential-equations-0.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="whats-a-diet"&gt;What&amp;rsquo;s a diet&lt;/h2&gt;
&lt;p&gt;My definition of diet is &lt;em&gt;extended calorie deficit for the purposes of losing
bodyfat&lt;/em&gt;. I do not differentiate types of food, but I prefer high protein for
appetite control. I also don&amp;rsquo;t do time-based restriction, though I find that
skipping breakfast and not eating after dinner is sustainable, and that&amp;rsquo;s
roughly an 18/6.&lt;/p&gt;
&lt;p&gt;From the first part, we have a few equations to work with:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mifflin-St Jeor&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; \( BMR = 10 \times weight(kg) + 6.25 \times height(cm) - 5 \times age(y) + G\),
where \(G\) is 5 for men, and -161 for women.&lt;/li&gt;
&lt;li&gt;\(TDEE = BMR \times L\), where \(L\) is a multiplier for your daily activity level.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hereafter, shortened to&lt;/p&gt;
$$
\begin{aligned}
BMR =&amp; 10 w + 6.25 H - 5A + G \\\\
TDEE =&amp;amp;L \cdot BMR\\\\
&amp;\text{  with } L \in \\{1.2, 1.375, 1.55, 1.725, 1.9\\}
\end{aligned}
$$&lt;p&gt;We also know we can calculate the steady state weight, which is the weight at
which you will neither gain nor lose weight. This is a function of your
activity, age
and height, and the calories you consume.&lt;/p&gt;
$$ 
\begin{aligned}
 SSW &amp;= \frac{cal - L\cdot [6.25 H - 5 A + G]}{10} \\\\
\end{aligned}
$$&lt;p&gt;See &lt;a href="https://jodavaho.io/posts/dieting-differential-equations-0.html"&gt;part 0&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;From this we&amp;rsquo;re accumulating python code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;from&lt;/span&gt; enum &lt;span style="color: #f92672;"&gt;import&lt;/span&gt; Enum
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;bmr_m&lt;/span&gt;(weight, height, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; weight &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;bmr_f&lt;/span&gt;(weight, height, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; weight &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;161&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;class&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;Activity&lt;/span&gt;(Enum):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    SEDENTARY &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    LIGHT &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.375&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    MODERATE &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.55&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    VERY &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.725&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    EXTRA &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;calc_tdee&lt;/span&gt;(bmr, activity):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Calculate Total Daily Energy Expenditure
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (activity &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.2&lt;/span&gt; &lt;span style="color: #f92672;"&gt;or&lt;/span&gt; activity &lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.9&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;raise&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;ValueError&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;"Activity must be between 1.2 and 1.9"&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; bmr &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; activity
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;steady_state_weight&lt;/span&gt;(calories, activity, height, gender, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Reverse BMR to find weight given calories
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    L &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; activity
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; gender&lt;span style="color: #f92672;"&gt;==&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;'m'&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; calories &lt;span style="color: #f92672;"&gt;/&lt;/span&gt;  L &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; wt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; gender &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;'f'&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; calories &lt;span style="color: #f92672;"&gt;/&lt;/span&gt;  L &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;161&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; wt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;raise&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;ValueError&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;'sex: m|f only'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We left of with the &lt;em&gt;big question&lt;/em&gt;: how &lt;em&gt;long&lt;/em&gt; does it take for the effects to
be fully realized? Now, let&amp;rsquo;s figure that out, and, that will allow me to,
answer, &lt;strong&gt;if I want to lose 30 lbs by mid-summer, how many calories do I need
to cut?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="deriving-a-predictive-model-for-weight-loss"&gt;Deriving a predictive model for weight loss&lt;/h2&gt;
&lt;p&gt;Alright, using only BMR / TDEE, what can we say about weight loss over time?&lt;/p&gt;
&lt;p&gt;Given weight loss is a function of the difference between &lt;code&gt;TDEE&lt;/code&gt; and
&lt;code&gt;calories&lt;/code&gt;, thinking it through, if we&amp;rsquo;re &lt;em&gt;far&lt;/em&gt; from our target weight, the
difference between &lt;code&gt;TDEE&lt;/code&gt; and our calorie intake is large, so we should shed
weight quickly. As we approach our target weight, the difference shrinks, and
we should lose weight more slowly.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s plot this. Assuming a 300 calorie deficit will shed \(300/7700\) kgs, we have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;wt_change&lt;/span&gt;(calories, tdee):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Given calories consumed, and calculated TDEE (using kg!), how much weight
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    will you lose?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Returns delta-weight in kg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    calories_per_kg &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;7700&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; (calories &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; tdee) &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; calories_per_kg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;predict_wt&lt;/span&gt;(days, ht, age, wt0, calorie_target, activity&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;Activity&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;SEDENTARY&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;value):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Given you want to cut calories by calorie_cut below your starting BMR, over
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    the range of days, what will your weight be?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Accepts wt0 in kg, ht in cm, age in years
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Returns a list of weights over time in kg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    wts &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; [wt0]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt; i &lt;span style="color: #f92672;"&gt;in&lt;/span&gt; range(&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;, days):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        current_wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wts[&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        bmr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; calc_bmr(current_wt, ht, age, &lt;span style="color: #e6db74;"&gt;'m'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        tdee &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; activity &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; bmr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        dwt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt_change(tdee, calorie_target)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wts&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;append(wts[&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;] &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; dwt)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; wts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And if we plot out the weight loss over 3 years, we get:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;plotxy&lt;/span&gt;(x,y):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;import&lt;/span&gt; plotille &lt;span style="color: #66d9ef;"&gt;as&lt;/span&gt; plt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    fig &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; plt&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;Figure()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    fig&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;width &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;40&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    fig&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;height &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    fig&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;plot(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    print(fig&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;show())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;wts &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; predict_wt(&lt;span style="color: #ae81ff;"&gt;365&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;, to_cm(&lt;span style="color: #ae81ff;"&gt;77&lt;/span&gt;), &lt;span style="color: #ae81ff;"&gt;43&lt;/span&gt;, to_kg(&lt;span style="color: #ae81ff;"&gt;222&lt;/span&gt;), &lt;span style="color: #ae81ff;"&gt;2100&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;plotxy(range(&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;, &lt;span style="color: #ae81ff;"&gt;365&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;), wts)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the result is:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Weight Loss Over Time" src="https://jodavaho.io/img/dieting-1-curved.png" /&gt;&lt;/p&gt;
&lt;p&gt;Well, it definitely flattens out. And for my taste, that&amp;rsquo;s far too slow in the
second or third year. When do we reach our &lt;code&gt;SSW&lt;/code&gt;? Well, never, actually. We
asymptotically approach it. So to get something like linear progression, you
want to be the early stages, where the difference between &lt;code&gt;TDEE&lt;/code&gt; and &lt;code&gt;calories&lt;/code&gt;
is large.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Light bulb #3: If you want to hit your target weight, you have to aim &lt;em&gt;past&lt;/em&gt;
it. If you want to hit it quickly, you have to aim &lt;em&gt;far&lt;/em&gt; past it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That gradual flattening out is a classic exponential decay. The equation for
&amp;ldquo;weight over time&amp;rdquo; must certainly be of the form \(w(t) \propto w_0 e^{-kt}\), where
\(w_0\) is the initial weight, \(k\) is the decay constant, and \(t\) is time.&lt;/p&gt;
&lt;p&gt;I can happily continue plotting by iterating over days, but that&amp;rsquo;s not very
fun, and I distrust my discrete simulation skills. Let&amp;rsquo;s derive the equation.
Let&amp;rsquo;s start with the &amp;ldquo;Delta Weight&amp;rdquo; equation, and see if we can integrate that
to find the weight over time.&lt;/p&gt;
$$ \frac{dw(t)}{dt} = \frac{calories - tdee(t)}{7700} $$&lt;p&gt;Expanding that out, we get (using the \(TDEE\) equation from above):&lt;/p&gt;
$$ \frac{dw(t)}{dt} = \frac{cal - L (10 w(t) + 6.25 H - 5 A + G) }{7700} $$&lt;p&gt;Where \(cal\) is the calories, \(L\) is the activity level, \(w(t)\) is the weight at
time \(t\), \(H\) is height, \(A\) is age, and \(G\) is the &amp;ldquo;gender constant&amp;rdquo; from the
BMR equation.&lt;/p&gt;
&lt;p&gt;The fact that we have \(dx/dt\) on one side and \(x\) on the other is a hint that
this is a first order linear differential equation. You can use automated tools to solve this, but it will generally be ugly if you don&amp;rsquo;t prepare the equation first.&lt;/p&gt;
&lt;p&gt;So, we can prepare and solve it by separation of variables&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.  We can break
out \(w(t)\) and tuck some constants away to get:&lt;/p&gt;
$$ \frac{dw(t)}{dt} = \frac{C}{B} - \frac{10L}{B} w(t) $$&lt;p&gt;where \(C = cal - L [6.25 H - 5 A + G] \) and \(B = 7700 \). This can be separated to:&lt;/p&gt;
$$\begin{aligned} 
&amp;\left(\frac{1}{ \frac{C}{B} - \frac{10L}{B} w(t)  }\right) dw(t) = dt\\\\
&amp;\left(\frac{B}{ C - 10L w(t)  }\right) dw(t) = dt 
\end{aligned}$$&lt;p&gt;Wolfram Alpha made me dig way back to Calc II u-substitution, with \(u = C - 10L w(t)\), and \(du = -10L dw(t) \implies
dw(t) = \frac{1}{-10L} du\). Subbing those in, we get:&lt;/p&gt;
$$\begin{aligned}
\left(\frac{B}{ u }\right)\frac{1}{-10L} &amp;amp;du(t) = dt \\\\
\left(-\frac{B}{ 10L }\right) &amp;\frac{du(t)}{u} = dt 
\end{aligned}$$&lt;p&gt;And since \(\int\frac{du}{u} = ln(u) + K\)&lt;sup id="fnref1:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, we have \(=ln(C-10Lw(t)) + K\) so we get:&lt;/p&gt;
$$\begin{aligned}
-\frac{B}{10L} ln(C-10L w(t)) &amp;= t + K \\\\
 ln(C-10L w(t)) &amp;= -\frac{10L}{B} (t + K)\\\\
 C-10L w(t) &amp;= e^{-\frac{10L}{B} (t + K)}\\\\
 w(t) &amp;= \frac{C - e^{-\frac{10L}{B} (t + K)}}{10L}\\\\
w(t) &amp;= \frac{C}{10L} - e^{-\frac{10L}{B}(t)} \left(\frac{e^{-\frac{10LK}{B}}}{10L}\right) &amp; \text{❓ ugly...} \\\\
\end{aligned}$$&lt;p&gt;Note that \(K\), the integration constant, is hiding up there. Let&amp;rsquo;s use initial conditions of \(w(0)\) to find solve the previous equation to eliminate arbitrary constants.&lt;/p&gt;
$$\begin{aligned}
w(0) &amp;= \frac{C}{10L} - e^{-\frac{10L}{B}(0)} \left(\frac{e^{-\frac{10LK}{B}}}{10L}\right) \\\\
w(0) &amp;= \frac{C}{10L} - \frac{e^{-\frac{10LK}{B}}}{10L} \\\\
10 L w(0) &amp;= C - e^{-\frac{10LK}{B}}&amp;\text{ } \\\\
e^{-\frac{10LK}{B}} &amp;= C - 10 L w(0)&amp;\text{✅ }  \\\\
%e^{-\frac{10LK}{B}} &amp;= cal - L[6.25 H - 5 A + G ] - 10 L w(0) \\\\
%e^{-\frac{10LK}{B}} &amp;= cal - L [6.25 H - 5 A + G  + 10 w(0)] \\\\
%e^{-\frac{10LK}{B}} &amp;= cal - TDEE(0) \\\\
%-\frac{10LK}{B} &amp;= ln(cal - TDEE(0)) \\\\
%K &amp;= \frac{-B}{10L} \cdot \text{ln}(cal - TDEE(0))/(10L)&amp;\text{✅ Solved K}  \\\\
%\implies e^{-\frac{10LK}{B}} &amp;= cal - TDEE &amp;\text{✅ Simplifies!}  \\\\
\end{aligned}$$&lt;p&gt;That&amp;rsquo;s nice! So subbing back, we have just solved \(w(t)\) as:&lt;/p&gt;
$$\begin{aligned}
w(t) &amp;= \frac{C}{10L} - e^{-\frac{10L}{B}(t)} \left(\frac{e^{-\frac{10LK}{B}}}{10L}\right) &amp;\text{} \\\\
w(t) &amp;= \frac{C}{10L} - e^{-\frac{10L}{B}(t)} \left(\frac{C-10Lw_0}{10L}\right) &amp; \text{} \\\\
\end{aligned}$$&lt;p&gt;Now, \(C\) is a constant I introduced, with value \(cal - L[6.25 H - 5 A + G]\), and \(B\) is the 7700 calories per kg.
This means:&lt;/p&gt;
$$
\begin{aligned}
\frac{C}{10L} &amp;= \frac{cal - L[6.25 H - 5 A + G]}{10L} &amp;\text{} \\\\
\frac{C}{10L} &amp;= SSW\text{} \\\\
\end{aligned}
$$&lt;p&gt;Let&amp;rsquo;s expand, using \(SSW\) for \(C/10L\):&lt;/p&gt;
$$
\begin{aligned}
w(t) &amp;= \frac{C}{10L} - e^{-\frac{10L}{B}(t)} \left(\frac{C-10Lw_0}{10L}\right) &amp; \text{} \\\\
w(t) &amp;= SSW - e^{-\frac{10L}{B}(t)} \left(SSW -w_0\right) &amp;\text{} \\\\
\\\\
w(t) &amp;= SSW\cdot\left(1-e^{-\frac{10L}{B}(t)}  \right)+ w_0 e^{-\frac{10L}{B}(t)} &amp;\text{[✅] closed form!} \\\\
w(t) &amp;= SSW + (w_0 - SSW) e^{-\frac{10L}{B}(t)}  &amp;\text{[✅] smaller form!} \\\\
&amp;\text{w/ }SSW=cal - L[6.25 H - 5 A + G]/10L &amp;\text{} \\\\
\end{aligned}
$$&lt;p&gt;OK, so we have a closed form equation for weight over time. With time = 0:
&lt;/p&gt;
$$
\begin{aligned}
w(0) &amp;= SSW + (w_0 - SSW) e^{-\frac{10L}{B}(0)}  &amp;\text{} \\\\
w(0) &amp;= SSW + (w_0 - SSW)\cdot 1  &amp;\text{} \\\\
w(0) &amp;= w_0 &amp;\text{} \\\\
\end{aligned}
$$&lt;p&gt;&amp;hellip; it gives us our starting weight, and as time goes to infinity:&lt;/p&gt;
$$
\begin{aligned}
w(\infty) &amp;= SSW + (w_0 - SSW) e^{-\frac{10L}{B}(\infty)}  &amp;\text{} \\\\
w(\infty) &amp;= SSW + (w_0 - SSW) \cdot 0  &amp;\text{} \\\\
w(\infty) &amp;= SSW &amp;\text{} \\\\
\end{aligned}
$$&lt;p&gt;&amp;hellip; it gives us our steady state weight. Perfect!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #66d9ef;"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;weight_over_time&lt;/span&gt;(days, calories, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                     ht, age, wt0, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                     activity&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;Activity&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;SEDENTARY&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;value):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Given you want to consume `calories` per day, over the range of days, what
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    will your weight be?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Accepts wt0 in kg, ht in cm, age in years
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Returns a list of weights over time, one per day, in kg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    SSW &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; steady_state_weight(calories, activity, ht, &lt;span style="color: #e6db74;"&gt;'m'&lt;/span&gt;, age)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    t &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; np&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;arange(&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;, days)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    exparr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; np&lt;span style="color: #f92672;"&gt;.&lt;/span&gt;exp(&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;activity&lt;span style="color: #f92672;"&gt;/&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;7700&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt; t)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    res &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; SSW &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; exparr &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; (wt0&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;SSW)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; res
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;OK, let&amp;rsquo;s have a look!
Given my weight loss goal, target calories, and starting weight, we can plot it out!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Weight Loss Over Time" src="https://jodavaho.io/img/dieting-1-joshplan.png" /&gt;&lt;/p&gt;
&lt;p&gt;My current calorie goal / activity level goal combine to give a \(SSW\) of 161 lbs. This is the weight I would asymptotically approach if I ate 2100 calories per day and maintained my current activity level, and represents a weight loss of 61 lbs.&lt;/p&gt;
&lt;p&gt;Give my goal was to lose 32 lbs,
I &lt;em&gt;massively&lt;/em&gt; overshot my goal weight with this calorie / activity level. This is intentional, to lose weight quickly. After reaching ~190 or so, I&amp;rsquo;ll probably switch to a sustainable level of food/activity (if I even can). But who knows? Now I know.&lt;/p&gt;
&lt;p&gt;This is probably not super healthy. I suspect this is what most people have in mind when they think &amp;ldquo;diet&amp;rdquo; - something that moves quick by massive over-compensation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Light bulb #4: A more sustainable diet is to pick calories given steady-state-weight and activity level, and let years do the work&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note, given all the closed form equations above, it&amp;rsquo;s natural to start plugging in different parameters and solving for different things. Let&amp;rsquo;s do that next.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Basal_metabolic_rate"&gt;https://en.wikipedia.org/wiki/Basal_metabolic_rate&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref1:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/First-order_differential_equation#Separation_of_variables"&gt;https://en.wikipedia.org/wiki/First-order_differential_equation#Separation_of_variables&lt;/a&gt; I was &lt;em&gt;not&lt;/em&gt; great at this in college, so it helps me feel better when I get to use it in real life.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Tue, 02 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dieting-differential-equations-1.html</guid></item><item><title>Learning to code with and without AI</title><link>https://austinhenley.com/blog/learningwithai.html</link><description>&lt;a href="https://austinhenley.com/blog/learningwithai.html"&gt;https://austinhenley.com/blog/learningwithai.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Mon, 01 Apr 2024 06:45:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/learningwithai.html</guid></item><item><title>Dieting and Differential Equations: Part 0, Calories and Steady State Weight</title><link>https://jodavaho.io/posts/dieting-differential-equations-0.html</link><description>&lt;p&gt;OK, let&amp;rsquo;s talk about my diet.&lt;/p&gt;
&lt;p&gt;After I stopped boxing, and biking to work, and in general doing anything, food
became a problem. Snacking was constant, meals were added on when someone else
made them, and treats were prevalent. Turns out, when you&amp;rsquo;re burning 2500
calories in roadwork every day, this isn&amp;rsquo;t  a problem. No longer.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve started a diet, with the goal of losing 32 lbs, or ~15% of my bodyweight,
to get back to my circa 2000-2005 weight. I&amp;rsquo;m happy if I achieve 80% of that
loss, ending up under 200. This is a long post, in which I describe the
plan, and some misunderstandings I had about dieting and weight loss.&lt;/p&gt;
&lt;p&gt;Along the way, I built an estimator that uses data to determine my real calorie
intake, needs, and activity level. The results, for me, were surprising.&lt;/p&gt;
&lt;p&gt;In practice, the hardest part about calorie deficits is &lt;em&gt;tracking&lt;/em&gt; calories.
Burying the lede a little, it turns out LLM / ChatGPT-4 is &lt;em&gt;fantastic&lt;/em&gt; at
counting calories for you from pictures, descriptions, or rough notes.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll cover all that.&lt;/p&gt;
&lt;h2 id="whats-a-diet"&gt;What&amp;rsquo;s a diet&lt;/h2&gt;
&lt;p&gt;My definition of diet is &lt;em&gt;extended calorie deficit for the purposes of losing
bodyfat&lt;/em&gt;. I do not differentiate types of food, but I prefer high protein for
appetite control. I also don&amp;rsquo;t do time-based restriction, though I find that
skipping breakfast and not eating after dinner is sustainable, and that&amp;rsquo;s
roughly an 18/6.&lt;/p&gt;
&lt;h2 id="first-step-how-many-calories-do-i-need"&gt;First step: How many calories do I need?&lt;/h2&gt;
&lt;p&gt;A 222lb, tall biological male my age requires &lt;em&gt;at least&lt;/em&gt; 2025 calories to
maintain his weight. How do I know that, and what do I mean by &lt;em&gt;at least&lt;/em&gt;? The
value is given by the Basal Metabolic Rate equation, for which the most
accurate form from Mifflin-St Jeor&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; \( BMR = 10 \times weight(kg) + 6.25 \times height(cm) - 5 \times age(y) + G\),
where \(G\) is 5 for men, and -161 for women.&lt;/p&gt;
&lt;p&gt;Hereafter, shortened to&lt;/p&gt;
$$ BMR = 10 w + 6.25 H - 5A + G $$&lt;p&gt;Let&amp;rsquo;s get started calculating, using Python of course.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;bmr_m&lt;/span&gt;(weight, height, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Given weight (kg), height (cm), and age (years), return
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    the 'sustaining calories' for males
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; weight &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;bmr_f&lt;/span&gt;(weight, height, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Given weight (kg), height (cm), and age (years), return
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    the 'sustaining calories' for females
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; weight &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;161&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A close approximation of me, &lt;code&gt;bmr_m(JoshWeight, JoshHeight, JoshAge)~=2000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is the amount of calories you need to maintain your weight if you were in
a coma. Your daily activity increases this need. The activity factor varies
from 1.2 (sedentary) to 1.9 (strenuous exercise daily).&lt;/p&gt;
&lt;p&gt;Factoring in activity, we have &amp;ldquo;&lt;code&gt;TDEE&lt;/code&gt;&amp;rdquo; (Total Daily Energy Expenditure), as:&lt;/p&gt;
$$ TDEE = BMR \cdot L \label{tdee}$$&lt;p&gt;Where \(L\) is Activity Level, a constant from 1.2 to 1.9.&lt;/p&gt;
&lt;p&gt;A helpful enum here (to match the literature) is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;from&lt;/span&gt; enum &lt;span style="color: #f92672;"&gt;import&lt;/span&gt; Enum
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;class&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;Activity&lt;/span&gt;(Enum):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    SEDENTARY &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    LIGHT &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.375&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    MODERATE &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.55&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    VERY &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.725&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    EXTRA &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;calc_tdee&lt;/span&gt;(bmr, activity):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (activity &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.2&lt;/span&gt; &lt;span style="color: #f92672;"&gt;or&lt;/span&gt; activity &lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1.9&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;raise&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;ValueError&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;"Activity must be between 1.2 and 1.9"&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; bmr &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; activity
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re like me, you jumped to 1.3 or so, thinking &amp;ldquo;well of course I&amp;rsquo;m not
&lt;em&gt;super&lt;/em&gt; active, but I &lt;em&gt;do&lt;/em&gt; play with my kids and run around a bit, etc&amp;rdquo;. In a
bit, we&amp;rsquo;ll build an estimator to bound these free-ish variables, so let&amp;rsquo;s
proceed with &lt;code&gt;SEDENTARY=1.2&lt;/code&gt; and see how that shakes out.&lt;/p&gt;
&lt;p&gt;Or, in my case, &lt;code&gt;calc_tdee(2000, Activity.SEDENTARY.value) ~= 2400&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="losing-weight-on-calorie-restriction-and-steady-state-weight"&gt;Losing weight on calorie restriction and &amp;ldquo;steady state&amp;rdquo; weight&lt;/h2&gt;
&lt;p&gt;OK, any amount of daily calories below your TDEE will result in weight loss,
right? The generally accepted value is 3500 calories per pound of bodyfat, so
if I cut 300 calories a day, I can expect to lose 1 lb every 12 days, right?&lt;/p&gt;
&lt;p&gt;Just eat 2100 calories and lose 1 lb/12 days!&lt;/p&gt;
&lt;p&gt;Wait a minute. If that&amp;rsquo;s true I&amp;rsquo;ll reach negative weights in about 7 years.
There&amp;rsquo;s clearly something else going on. That something is the fact that your
BMR is a function of your weight. So your &amp;ldquo;steady state&amp;rdquo; weight where you&amp;rsquo;ll
stop losing weight is a function of gender, age, height, and calories. It&amp;rsquo;s the
weight at which (ideally) &lt;code&gt;bmr_m(wt,ht,age)*1.375 == 2100&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Stated another way, there is a 6&amp;rsquo; 6&amp;quot; male somewhere that eats 2100 calories
/day , is not very active, and loses/gains no weight. That could be future me!
So, BMR can tell us how much that man weighs.&lt;/p&gt;
&lt;p&gt;Rearrange the TDEE equation to solve for weight:&lt;/p&gt;
$$ 
\begin{aligned}
TDEE &amp; = L [ BMR ] \\\\
cal &amp;= L [ 10 w + 6.25 H - 5 A + G ]\\\\
 \frac{cal}{L} &amp;=  10 w + (6.25 H - 5 A + G) \\\\
 10 w &amp;= \frac{cal}{L} - (6.25 H - 5 A + G) \\\\
 w &amp;= \frac{cal - L\left[6.25 H - 5 A + G\right]}{10} &amp;\text{ ✅ } \\\\
\end{aligned}
$$&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;def&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;steady_state_weight&lt;/span&gt;(calories, activity, height, gender, age):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;"""
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Reverse BMR to find weight given calories,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    Returns kg!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #e6db74;"&gt;    """&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    L &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; activity
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; gender&lt;span style="color: #f92672;"&gt;==&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;'m'&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; calories &lt;span style="color: #f92672;"&gt;/&lt;/span&gt;  L &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; wt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; gender &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;'f'&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; calories &lt;span style="color: #f92672;"&gt;/&lt;/span&gt;  L &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;6.25&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; age &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;161&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        wt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; wt &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; wt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;raise&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;ValueError&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;'sex: m|f only'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Result for ~2100 calories given my person? ~171 lbs. Not bad! This uses &lt;code&gt;1.2&lt;/code&gt;
as activty. Look carefully at the above equation - the activity is in the
denominator. This should sound alarm bells of the &amp;ldquo;That&amp;rsquo;s a sensitive
arrangment&amp;rdquo; variety. What happens with &amp;ldquo;Light activity&amp;rdquo; values of 1.375?&lt;/p&gt;
&lt;p&gt;The steady state weight drops 50 lbs. I think I knew this, from my boxing days,
but I didn&amp;rsquo;t &lt;em&gt;know&lt;/em&gt; it.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Light bulb #1, adding a tiny bit of exercise will shed a ton of extra weight over time.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s look at what a snack-a-day does to your &lt;code&gt;SSW&lt;/code&gt;. Adding 10% to your intake
(equivalent of a cookie, short IPA, or a 20oz soda /day) to an otherwise
perfect diet, would raise my Sedentary &lt;code&gt;SSW&lt;/code&gt; from 171 to 210 lbs.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s a gain of 40 lbs over time from a small treat daily.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Light bulb #2, a tiny bit of extra food will add a ton of extra weight over time.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&amp;rsquo;s a lot of weight. Taking the stairs might burn 50 calories, which is how
much you lose each decade from your BMR. Walking 30 extra minutes would burn
200, offsetting that 40 lb weight gain from the IPA. The battle of the bulge is
won and lost in these tiny daily decisions. Anyone can tell you though, that
the feedback is over years, not days, so your daily habits set in before you
see the noticeable weight gain (or loss).&lt;/p&gt;
&lt;p&gt;So the &lt;em&gt;big question&lt;/em&gt; is, how &lt;em&gt;long&lt;/em&gt; does it take for the effects to be fully
realized? This goes back to the original question - how long does it take to
lose 1 lb if I cut 300 calories / day?&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s figure that out, and, that will allow me to, answer, &lt;strong&gt;if I want to lose 30
lbs by mid-summer, how many calories do I need to cut?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s &lt;a href="https://jodavaho.io/tags/diet.html"&gt;next&lt;/a&gt;.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Basal_metabolic_rate"&gt;https://en.wikipedia.org/wiki/Basal_metabolic_rate&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;For reference, if my &lt;code&gt;SSW&lt;/code&gt; was 205, and I was doing a couple hours of workouts / day and biking everywhere, my calories must have been ~3500-3800/day.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Mon, 01 Apr 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dieting-differential-equations-0.html</guid></item><item><title>The Upload (short story)</title><link>https://www.swyx.io/upload</link><description>&gt; My first attempt at bringing back the short story in 30 years.</description><author>swyx's site RSS Feed</author><pubDate>Sun, 31 Mar 2024 07:24:07 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/upload</guid></item><item><title>Papers</title><link>https://bytepawn.com/papers.html</link><description>&lt;p&gt;A compilation of academic-style papers I have written over the years.&lt;br /&gt;&lt;br /&gt;&lt;img alt="" src="/images/papers-gauge.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 30 Mar 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/papers.html</guid></item><item><title>Trying out Rye</title><link>https://burakku.com/blog/rye-test-and-python-tools/</link><description>&lt;p&gt;&lt;img alt="Testing out Rye and other Python tooling thoughts" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The catch in the Rye.&lt;/p&gt;
&lt;p&gt;One of my least popular takes as a Professional Python Developer™ seems to be that I don’t actually consider the Python ecosystem and its tooling to be "hot garbage". I also haven’t really bothered with project management tools like &lt;a href="https://python-poetry.org/"&gt;Poetry&lt;/a&gt;, since I think you can use virtual environments and pip and be &lt;em&gt;just fine&lt;/em&gt;. Really, the only tools in my arsenal that I consider to be absolutely essential have been &lt;a href="https://github.com/pypa/pipx"&gt;pipx&lt;/a&gt;, as distributing Python tools is kind of a pain, and &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;, because &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt; will absolutely break your Python installation without any mercy if you so much as blink. Of course, I do also use other tools too, but I don't consider them as essential as those two.&lt;/p&gt;
&lt;p&gt;However, I do recognise that there is some value in one-stop-shop tools like Rust’s Cargo, which bakes in a lot more project management stuff into your Rust development workflow and is generally pretty nice to use. For this reason I was quite intrigued by &lt;a href="https://rye-up.com/"&gt;&lt;strong&gt;Rye&lt;/strong&gt;&lt;/a&gt; after watching &lt;a href="https://www.youtube.com/watch?v=q99TYA7LnuA"&gt;Armin Ronacher’s demonstration video&lt;/a&gt;, since it seems like a version of Cargo (and to some degree Rustup) for the Python world.&lt;/p&gt;
&lt;p&gt;On a conceptual level, it sounds very good. Instead of installing a bunch of tools and managing/using them individually, Rye would bundle together all sorts of tools and functionality, and let me write Python software with basically Rye alone. The number of different tools that I use and Rye would replace is actually rather long:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;current tool&lt;/th&gt;
&lt;th&gt;The Rye Way&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Rye automatically installs whatever Python version your project uses.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pip&lt;/td&gt;
&lt;td&gt;Rye installs and pins your dependencies as you add them to your project.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/pypa/pipx"&gt;pipx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Rye allows installing Python tools in isolated environments just like pipx.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://virtualfish.readthedocs.io/en/latest/"&gt;VirtualFish&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Rye automatically sets up a virtualenv in your project directory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://black.readthedocs.io/en/stable/"&gt;Black&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Rye bundles Ruff for formatting (&lt;code&gt;rye fmt&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.astral.sh/ruff/"&gt;Ruff&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Rye bundles Ruff for linting (&lt;code&gt;rye lint&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I started off by migrating all of my pipx installs to Rye and it worked great. All tools were ready and usable on my &lt;code&gt;$PATH&lt;/code&gt; and I could even add optional dependencies with &lt;code&gt;--extra-requirement&lt;/code&gt; flags. Not sure if there’s a handy way of adding those extra requirements after the fact like you can in pipx with &lt;code&gt;pipx inject&lt;/code&gt; though. I imagine I could always just manually install them into the virtual environment but I’d prefer if there was a more straightforward and obvious way. Would make it even easier to use Rye as a drop-in replacement for pipx.&lt;/p&gt;
&lt;p&gt;As for actual development, I decided to start off small and convert a tiny Python script I use for fetching data from an API to work with Rye. Since this was just a tiny script that I run in a Docker container, it wasn't exactly organized in the way that Rye would organize a script project, so I decided to also make it a bit more structured with a script entry-point and everything. This isn't actually a requirement but I figured that I should try to do it like Rye would want me to. For what it's worth, the default Rye-tastic way of organizing code is fairly logical and probably good starting point for people new to Python. Personally, I've never really used &lt;code&gt;/src/&lt;/code&gt; directories in Python but I don't hate it either.&lt;/p&gt;
&lt;p&gt;For this tiny project, everything worked very nicely. Granted, there was only one dependency (requests), so it wasn't exactly a demanding test. But Rye would bootstrap whatever Python version I had in the &lt;code&gt;.python-version&lt;/code&gt; text file, create a virtualenv out of it, install my dependencies into it and then allow me to run my script with just &lt;code&gt;python&lt;/code&gt; like I didn't have a virtualenv at all.&lt;/p&gt;
&lt;p&gt;And if I upgraded or downgraded my Python version by changing the value in the &lt;code&gt;.python-version&lt;/code&gt; file, it would bootstrap everything again to make sure my development environment matched the requirements. This would make version upgrades a lot easier for projects with lots of different developers, as you could be pretty confident in everyone having an up-to-date development environment if they just run &lt;code&gt;rye sync&lt;/code&gt;. I've had to work on projects that upgraded between major versions of Python, and you'd always have at least some issues bringing everyone up to speed.&lt;/p&gt;
&lt;p&gt;Emboldened by the success of using Rye with this tiny script, I moved onto the next trial: using Rye to develop a Django project. And this is where the pain begins, and not (just) because I picked an example with an ancient Django 2.0 codebase.&lt;/p&gt;
&lt;p&gt;I bootstrapped my Django project as a virtual package with Rye and managed to install all of the dependencies for my Django project that are required to run the application locally. Then I started to add the dependencies I need to actually serve it on the web and I step on the big fat LEGO brick lying in wait: I just can't install &lt;a href="https://uwsgi-docs.readthedocs.io/en/latest/"&gt;uWSGI&lt;/a&gt;. The reason for this is actually quite simple: uWSGI needs to compile C extensions, Rye uses static builds of Python, and static Python builds + compiling C extensions is &lt;a href="https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html#references-to-build-time-paths"&gt;a known house of pain&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://rye-up.com/guide/faq/#references-to-build-time-paths"&gt;Rye FAQ&lt;/a&gt; even admits how the situation when it comes to C extensions is a problem, and one with no real solution at the moment. It does however offer a workaround: register a non-static Python build that you've obtained from somewhere as a Rye toolchain. Since Homebrew has decided to grace my Mac Studio with a Python 3.9 installation suitable for my purposes*, I decided to register that.&lt;/p&gt;
&lt;div style="font-size: 14px; font-style: italic;"&gt;* Technically Django 2.0 doesn't actually support Python 3.9 but fortune favours the bold.&lt;/div&gt;&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;python3.9
&lt;span class="go"&gt;/opt/homebrew/bin/python3.9&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;rye&lt;span class="w"&gt; &lt;/span&gt;toolchain&lt;span class="w"&gt; &lt;/span&gt;register&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;homebrew&lt;span class="w"&gt; &lt;/span&gt;/opt/homebrew/bin/python3.9
&lt;span class="go"&gt;Registered /opt/homebrew/bin/python3.9 as homebrew@3.9.18&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Obviously I would &lt;strong&gt;never&lt;/strong&gt; recommend actually using Homebrew-supplied Python installations for any kind of development work, but I figured it'd be fine for testing. Now I can just pin my custom toolchain to my project and create the virtualenv from that.&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;rye&lt;span class="w"&gt; &lt;/span&gt;pin&lt;span class="w"&gt; &lt;/span&gt;homebrew@3.9.18
&lt;span class="go"&gt;pinned homebrew@3.9.18 in project/.python-version&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;rye&lt;span class="w"&gt; &lt;/span&gt;sync
&lt;span class="go"&gt;Python version mismatch (found cpython@3.9.18, expected homebrew@3.9.18), recreating.&lt;/span&gt;
&lt;span class="go"&gt;error: failed fetching toolchain ahead of sync&lt;/span&gt;

&lt;span class="go"&gt;Caused by:&lt;/span&gt;
&lt;span class="go"&gt;    unknown version homebrew-aarch64-macos@3.9.18&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oops.&lt;/p&gt;
&lt;p&gt;Turns out that Rye is so set on managing your (static) Python toolchains that it will even attempt to download a toolchain already on your machine, and then fail because your custom toolchain doesn't actually exist beyond your machine. It won't even work even if you omit the custom name from &lt;code&gt;rye toolchain register&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So yeah, Rye kinda sucks and is all sorts of broken if you ever need dependencies that compile C code. Obviously I didn't test out a wide array of C extensions, but considering that it's a very well-known problem with static builds, you'll probably trip up yourself sooner or later if you do a lot of Python development.&lt;/p&gt;
&lt;p&gt;On one hand, I do understand that compiling software sucks because you need to hoard compile-time requirements and compiling software takes forever and makes your laptop burn your thighs and so on. However, I do think that compiling software like your Python development environment has some very significant upsides, such as the fact that your build of Python will reference paths actually present on your actual computer. Static Python builds are fine for some stuff but I feel like they're a bad idea to be the default and a truly awful idea to be the only option. The support for static compilation of Python just isn't there yet.&lt;/p&gt;
&lt;p&gt;Even if the custom toolchain registering worked like it's supposed to, I find this design approach to completely water down the "one-stop-shop for all Python users" idea put forth by Rye, since I'm still required to install pyenv on my machine in order to have a toolchain to register in the first place. I think Rye would be a much better tool if it offered pyenv-style compilation of Python, and preferably made that the default option. Or at the very least ask me which style I prefer during the installation process. It already asks you how you want the &lt;code&gt;python&lt;/code&gt; command work outside Rye-managed projects, so it's not unthinkable to have an option for compilation too.&lt;/p&gt;
&lt;hr /&gt;

&lt;p&gt;In terms of ergonomics when it came to the Django project (the parts that worked), I also kinda miss the &lt;a href="https://virtualfish.readthedocs.io/en/latest/plugins.html#environment-variables-environment"&gt;environment variable plugin&lt;/a&gt; that VirtualFish had, where it would set environment variables when you activated a virtualenv. As far as I can see, the way you're supposed to integrate environment variables as part of your Python development is by adding them to your Rye scripts. So if I want to set &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; for when I run any of the Django management commands, I'd chuck it in a separate file like &lt;code&gt;.env&lt;/code&gt; and add this to my &lt;code&gt;pyproject.toml&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;[tool.rye.scripts]&lt;/span&gt;
&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;manage&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.env&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You could also add environment variables directly to the script definition but that sounds very not-portable if you work with other developers. But now I can run tests against my development settings module. I just need to use &lt;code&gt;rye run manage&lt;/code&gt; to invoke the test management command instead of for example the &lt;code&gt;manage.py&lt;/code&gt; script that Django will give you.&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;rye&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="go"&gt;Creating test database for alias 'default'...&lt;/span&gt;
&lt;span class="go"&gt;System check identified no issues (0 silenced).&lt;/span&gt;
&lt;span class="go"&gt;..................&lt;/span&gt;
&lt;span class="go"&gt;---------------------------------------------------------------------&lt;/span&gt;
&lt;span class="go"&gt;Ran 18 tests in 0.215s&lt;/span&gt;

&lt;span class="go"&gt;OK&lt;/span&gt;
&lt;span class="go"&gt;Destroying test database for alias 'default'...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I think this is totally reasonable and maybe juggling shell session environment variables isn't the job of a Python development tool anyway, but I do still kinda wish for there to be some kind of a better way for it.&lt;/p&gt;
&lt;hr /&gt;

&lt;p&gt;All in all, I have mixed feelings about Rye. On one hand, many of the design ideas are good and I think I'd like to use a Cargo-but-for-Python to develop software, since I also like using Cargo. However, the insistence on using static Python builds leads to suffering and won't let me uninstall pyenv anyways. There's also some rough edges in implementation to cut yourself up further, like my attempt at getting Rye and Homebrew-Python to mingle shows. The fact that &lt;code&gt;rye test&lt;/code&gt; is also just an alias to run &lt;a href="https://docs.pytest.org/"&gt;pytest&lt;/a&gt; with no possibility for any other testing tool like the built-in &lt;code&gt;unittest&lt;/code&gt; module, which is totally and absolutely a fine tool for testing that you get out of the box, seems very indicative of just how early in Rye's life we are at the moment.&lt;/p&gt;
&lt;p&gt;If you have a working Python development environment, no matter how rudimentary its feature set might be, it might not be worth it to try out Rye now. Its best value at the moment would probably rather be for newbs, who can benefit from its easy setup and sane defaults for many things (like not using the system Python if you've got one). However, I think that there's also a good chance that Rye will actually be all-around great in like 12 months, so I also wouldn't invest too heavily in an alternative tool like Poetry if you're shopping around for project management tools.&lt;/p&gt;
&lt;p&gt;Nevertheless, Python is still a fun programming language and the tooling is &lt;em&gt;fine&lt;/em&gt;.&lt;/p&gt;</description><author>ブラック</author><pubDate>Fri, 29 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/rye-test-and-python-tools/</guid></item><item><title>CLI text editing with ed</title><link>https://learnbyexample.github.io/mini/cli-text-editing-with-ed/</link><description>&lt;p&gt;I'm finally writing a post on the &lt;code&gt;ed&lt;/code&gt; command. And I'm keeping it short so that I'll actually publish the post. The examples presented below will be easier to understand for those already familiar with Vim and &lt;code&gt;sed&lt;/code&gt;. See the links at the end for learning resources.&lt;/p&gt;
&lt;p&gt;Although I'm interested in getting to know &lt;code&gt;ed&lt;/code&gt; better, I don't really find myself in situations where it'd help me. But, I have used it a few times to answer questions on stackoverflow.&lt;/p&gt;
&lt;h2 id="moving-lines"&gt;Moving lines&lt;/h2&gt;
&lt;p&gt;Consider this sample input file:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat ip.txt
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span&gt;cherry
&lt;/span&gt;&lt;span&gt;fig
&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;span&gt;pineapple
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suppose, you want to move the third line to the top. If you are using Vim, you can execute &lt;code&gt;:3m0&lt;/code&gt; where &lt;code&gt;3&lt;/code&gt; is the input address, &lt;code&gt;m&lt;/code&gt; is the &lt;em&gt;move&lt;/em&gt; command and &lt;code&gt;0&lt;/code&gt; is the target address. To do the same with &lt;code&gt;ed&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'3m0\nwq\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; ed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s ip.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ cat ip.txt
&lt;/span&gt;&lt;span&gt;cherry
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span&gt;fig
&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;span&gt;pineapple
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;3m0&lt;/code&gt; part in the above &lt;code&gt;ed&lt;/code&gt; command is identical to the Vim solution. After that, another command &lt;code&gt;wq&lt;/code&gt; (write and quit) is issued to save the changes (again, Vim users would be familiar with this combination). The &lt;code&gt;-s&lt;/code&gt; option suppresses diagnostics and other details. &lt;code&gt;-&lt;/code&gt; is used to indicate that the &lt;code&gt;ed&lt;/code&gt; script is passed via stdin.&lt;/p&gt;
&lt;p&gt;You can also move lines based on a regexp match. Here's an example:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# move the first matching line containing 'an' to the top of the file
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/an/m0\nwq\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; ed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s ip.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ cat ip.txt
&lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span&gt;cherry
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;span&gt;fig
&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;span&gt;pineapple
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to move all the matching lines, you can use the &lt;code&gt;g&lt;/code&gt; command (same as Vim). Note that the first matching line will be moved first, then the next matching line and so on. So the order will be reversed after the move.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'g/app/m0\nwq\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; ed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s ip.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ cat ip.txt
&lt;/span&gt;&lt;span&gt;pineapple
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span&gt;cherry
&lt;/span&gt;&lt;span&gt;fig
&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's the &lt;a href="https://stackoverflow.com/q/67031062/4082052"&gt;stackoverflow link&lt;/a&gt; that inspired the above examples. See &lt;a href="https://stackoverflow.com/a/75222984/4082052"&gt;this stackoverflow answer&lt;/a&gt; for more examples of moving lines. See &lt;a href="https://stackoverflow.com/a/48840851/4082052"&gt;this one&lt;/a&gt; to learn how to copy a particular line to the end of the file. See &lt;a href="https://unix.stackexchange.com/a/759710/109046"&gt;this unix.stackexchange answer&lt;/a&gt; for an example of moving a range of lines, where the same regex matches both the starting and ending lines.&lt;/p&gt;
&lt;h2 id="negative-addressing"&gt;Negative addressing&lt;/h2&gt;
&lt;p&gt;There are plenty of &lt;a href="https://learnbyexample.github.io/learn_gnused/selective-editing.html"&gt;addressing features&lt;/a&gt; provided by the &lt;code&gt;GNU sed&lt;/code&gt; command, but negative addressing isn't one. Here's an example of deleting the last but second line using &lt;code&gt;ed&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat colors.txt
&lt;/span&gt;&lt;span&gt;red
&lt;/span&gt;&lt;span&gt;green
&lt;/span&gt;&lt;span&gt;blue
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;black
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$-2d\nwq\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; ed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s colors.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-
&lt;/span&gt;&lt;span&gt;$ cat colors.txt
&lt;/span&gt;&lt;span&gt;red
&lt;/span&gt;&lt;span&gt;green
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;black
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="resource-links"&gt;Resource links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.sanctum.geek.nz/actually-using-ed/"&gt;Actually using ed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raymii.org/s/tutorials/ed_cheatsheet.html"&gt;ed cheatsheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2018/05/11/batch-editing-files-with-ed/"&gt;Batch editing files with ed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Ed_(text_editor)"&gt;wikipedia entry for the ed command&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Thu, 28 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/mini/cli-text-editing-with-ed/</guid></item><item><title>My OneStream Journey So Far...</title><link>http://blog.pythonaro.com/2022/07/if-you-are-interested-in-integrating.html</link><description>&lt;p&gt;This blog has been quiet for some time. That's largely because I've done quite a bit of writing (and experimenting!) for my employer, &lt;a href="https://onestream.com" target="_blank"&gt;OneStream&lt;/a&gt;. If you're interested in that sort of thing, here's a brief list of my posts on the company blog:&lt;/p&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;&lt;a href="https://community.onestreamsoftware.com/t5/ONECommunity-Blog/Building-BiBlend-table-names-or-a-classic-tale-of-XFBR/ba-p/7843"&gt;Building BiBlend table names, or: a classic tale of XFBR escapology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.onestreamsoftware.com/t5/ONECommunity-Blog/The-Dutiful-Dead-or-A-Tale-Of-Mixed-DataSets/ba-p/15509"&gt;The Dutiful Dead, or: A Tale Of Mixed DataSets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.onestreamsoftware.com/t5/ONECommunity-Blog/Cultured-Numbers-or-How-To-Avoid-Problems-With-International/ba-p/16350"&gt;Cultured Numbers, or: How To Avoid Problems With International Users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.onestreamsoftware.com/t5/ONECommunity-Blog/Plant-a-Tree-in-your-Member-List/ba-p/22344"&gt;Plant a Tree in your Member List!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.onestreamsoftware.com/t5/ONECommunity-Blog/SELECT-Like-a-Boss-With-Query-Parameters/ba-p/22426"&gt;SELECT Like a Boss With Query Parameters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.onestreamsoftware.com/t5/ONECommunity-Blog/Cool-CubeViews-with-Custom-GetDataCell-Functions/ba-p/24705"&gt;Cool CubeViews with Custom GetDataCell Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.onestreamsoftware.com/t5/ONECommunity-Blog/So-You-Want-to-Learn-VB-NET-pt-3/ba-p/29585"&gt;So You Want to Learn VB.NET - pt.3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.onestreamsoftware.com/t5/ONECommunity-Blog/BiViewer-Parameters-Demystified-pt-2/ba-p/30775"&gt;BiViewer Parameters Demystified - pt 2&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Blogging about actual work stuff has been a lot of fun! And if you go reading those posts, check out other ones as well - some of the stuff my colleagues can do is just &lt;i&gt;amazing&lt;/i&gt;.&amp;nbsp;&lt;/p&gt;&lt;p&gt;This said, the only constant in life is change. That means my contributions will probably slow down in the next few months, as I transition into a new role. Or maybe not! We'll see.&lt;br /&gt;&lt;/p&gt;&lt;ul style="text-align: left;"&gt;
&lt;/ul&gt;</description><author>Subclassed</author><pubDate>Wed, 27 Mar 2024 17:29:44 GMT</pubDate><guid isPermaLink="true">http://blog.pythonaro.com/2022/07/if-you-are-interested-in-integrating.html</guid></item><item><title>Brute force text search optimizations</title><link>https://boyter.org/posts/brute-force-text-search-optimizations/</link><description>&lt;p&gt;One of the things I see commonly on any mockup is a search box, usually with vaguely defined requirements as to how this is meant to actually work. However most people tend to assume its an instant update as you type with a debounce option of 100ms or something.&lt;/p&gt;
&lt;p&gt;The fastest way to implement this with a database is a horrible concatenated like query. However where you can store things in memory you can do a much better brute force search. A possible implementation in Go could look like the below.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 27 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/brute-force-text-search-optimizations/</guid></item><item><title>Finding memory leaks in Postgres C code</title><link>http://notes.eatonphil.com/2024-03-27-finding-memory-leaks-in-postgres-c-code.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/finding-memory-leaks-postgres-c-code"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 27 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-03-27-finding-memory-leaks-in-postgres-c-code.html</guid></item><item><title>Trust in SaaS</title><link>https://3059274a.danpalmer-me.pages.dev/2024-03-23-trust-in-saas/</link><description>&lt;p&gt;There&amp;rsquo;s a lie at the core of the SaaS ecosystem. In such a wide ranging category of services there are few commonalities, but arguably the main differentiator between SaaS and more traditional businesses is the notion of self-service. A customer can sign up, provide an email address and credit card, and be successful with the product. This is increasingly proving to be a lie, and customers are losing out because of it.&lt;/p&gt;</description><author>Dan Palmer</author><pubDate>Sat, 23 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://3059274a.danpalmer-me.pages.dev/2024-03-23-trust-in-saas/</guid></item><item><title>Organizing State of Diffusion++</title><link>https://www.swyx.io/state-of-diffusion-2024</link><description>I manifested a "State of Diffusion++" meetup yesterday that I felt was a success. 200 people signed up! Here are some photos!</description><author>swyx's site RSS Feed</author><pubDate>Fri, 22 Mar 2024 19:09:15 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/state-of-diffusion-2024</guid></item><item><title>SuperPrompter - Supercharge your text prompts for AI/LLM image generation</title><link>https://smcleod.net/2024/03/superprompter-supercharge-your-text-prompts-for-ai/llm-image-generation/</link><description>&lt;p&gt;&lt;a href="https://github.com/sammcj/superprompter"&gt;SuperPrompter&lt;/a&gt; is a Python-based application that utilises the &lt;a href="https://huggingface.co/roborovski/superprompt-v1"&gt;SuperPrompt-v1&lt;/a&gt; model to generate optimised text prompts for AI/LLM image generation (for use with Stable Diffusion etc&amp;hellip;) from user prompts.&lt;/p&gt;
&lt;p&gt;&lt;img alt="screenshot" src="https://smcleod.net/2024/03/superprompter-supercharge-your-text-prompts-for-ai/llm-image-generation/superprompter-screenshot.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://brianfitzgerald.xyz/prompt-augmentation/"&gt;Brian Fitzgerald&amp;rsquo;s Blog&lt;/a&gt; for a detailed explanation of the SuperPrompt-v1 model and its capabilities / limitations.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2 id="features"&gt;Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Utilises the &lt;a href="https://huggingface.co/roborovski/superprompt-v1"&gt;SuperPrompt-v1&lt;/a&gt; model for text generation.&lt;/li&gt;
&lt;li&gt;A basic (aka ugly) graphical user interface built with tkinter.&lt;/li&gt;
&lt;li&gt;Customisable generation parameters (max new tokens, repetition penalty, temperature, top p, top k, seed).&lt;/li&gt;
&lt;li&gt;Optional logging of input parameters and generated outputs.&lt;/li&gt;
&lt;li&gt;Bundling options to include or exclude pre-downloaded model files.&lt;/li&gt;
&lt;li&gt;Unloads the models when the application is idle to free up memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="prebuilt-binaries"&gt;Prebuilt Binaries&lt;/h2&gt;
&lt;p&gt;Check &lt;a href="https://github.com/sammcj/superprompter/releases"&gt;releases&lt;/a&gt; page to see if there are any prebuilt binaries available for your platform.&lt;/p&gt;</description><author>smcleod.net</author><pubDate>Fri, 22 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/03/superprompter-supercharge-your-text-prompts-for-ai/llm-image-generation/</guid></item><item><title>Stock returns of simple trading strategies</title><link>https://bytepawn.com/stock-returns-of-simple-trading-strategies.html</link><description>&lt;p&gt;I clean up the code I wrote for earlier posts, and run simple trading experiments. &lt;br /&gt;&lt;br /&gt;&lt;img alt="." src="/images/tidy-4.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 22 Mar 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/stock-returns-of-simple-trading-strategies.html</guid></item><item><title>Llamalink - Ollama to LM Studio LLM Model Linker</title><link>https://smcleod.net/2024/03/llamalink-ollama-to-lm-studio-llm-model-linker/</link><description>This is a simple command-line tool that allows you to easily link Ollama models to LM Studio's directory structure. It simplifies the process of symlinking Ollama models to LM Studio, making it convenient to use the models in both applications.</description><author>smcleod.net</author><pubDate>Thu, 21 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/03/llamalink-ollama-to-lm-studio-llm-model-linker/</guid></item><item><title>Three-Step Debugging: A Methodical Approach</title><link>https://donatstudios.com/debugging</link><description>&lt;p&gt;I've been working as a developer for almost twenty years now. In that time I have had the pleasure of helping many developers, and helping them debug code. Some were new to the field, had vastly more experience than me.&lt;/p&gt;
&lt;p&gt;While some are naturally somewhat gifted at debugging, it's important to understand that the skill can be learned. In fact, even very experienced developers can struggle with it&lt;/p&gt;
&lt;p&gt;I've known developers who  stare at their code trying to reason their way through it. I've known developers who will change things at random until the problem goes away.  I've known developers whose technique I describe as &amp;quot;I've tried nothing and I'm all out of ideas&amp;quot;.&lt;/p&gt;
&lt;p&gt;I take a very structured approach, and this is something I've meant to write down for years now. Solving 90% of the of the problems you will face comes down to three simple steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Identify Outputs&lt;/li&gt;
&lt;li&gt;Identify Inputs&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Identify Outputs&lt;/h2&gt;
&lt;p&gt;There is a bug in your code; that much we know. It's causing something to not work as expected. The first step in debugging is to identify what that something is.&lt;/p&gt;
&lt;p&gt;Determine what your expected output is, and what the actual output is.&lt;/p&gt;
&lt;p&gt;Walk through the call stack to find where the output diverged from expected.&lt;/p&gt;
&lt;h2&gt;Identify Inputs&lt;/h2&gt;
&lt;p&gt;This step is crucial and often the most challenging.&lt;/p&gt;
&lt;p&gt;In an ideal world we'd only be debug pure functional code. The only inputs would be parameters, and all calls within are deterministic.&lt;/p&gt;
&lt;p&gt;We don't live in an ideal world.&lt;/p&gt;
&lt;p&gt;We often find ourselves debugging code that inherits values and state from elsewhere. This can be self-fulfilling, such code is usually hardest to test and most prone to errors.&lt;/p&gt;
&lt;p&gt;Sources of state include, but are not limited to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Members of the containing class or its parents&lt;/li&gt;
&lt;li&gt;Non-deterministic values and methods
&lt;ul&gt;
&lt;li&gt;The current time&lt;/li&gt;
&lt;li&gt;Random number generators&lt;/li&gt;
&lt;li&gt;Potentially flakey network services&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;File systems&lt;/li&gt;
&lt;li&gt;User input&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Global variables&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Configuration files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This list is far from exhaustive and meant to give you an idea of the kinds of things you might need to consider.&lt;/p&gt;
&lt;p&gt;While debugging, it's important to identify and note all these inputs. We then need to understand their state at the time of the error. This is often where I see developers struggle. Some part of their code can vary and they have failed to identify it.&lt;/p&gt;
&lt;p&gt;Ways to identify their state logging, or by using a debugger to inspect the state of the system at the time of the error.&lt;/p&gt;
&lt;p&gt;It comes down to a is a matter of personal preference as well as comfort with the tooling.&lt;/p&gt;
&lt;p&gt;I know my way around my debugger, and yet I more often than not am inclined to just spit the state to standard out. I find this to be the most effective way of debugging. I know developers who are very good at using debuggers. I also know developers who spend a lot of time trying to make sense of their debugger.&lt;/p&gt;
&lt;p&gt;I've never met a developer who is bad at using print statements.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;– Brian Kernighan&lt;/p&gt;
&lt;h2&gt;Repeat&lt;/h2&gt;
&lt;p&gt;After identifying the code's outputs and inputs, use this information to track down the problem. You will need to repeat this process recursively through the call tree.&lt;/p&gt;
&lt;p&gt;We work our way through the call stack, identifying the unexpected outputs and inputs as we go. Keep this up and we will locate the root cause.&lt;/p&gt;
&lt;h2&gt;Other Thoughts&lt;/h2&gt;
&lt;p&gt;Identifying outputs and inputs isn't limited to values of variables. It's about the state of the system as a whole - including where boundaries get crossed.&lt;/p&gt;
&lt;p&gt;You need to understand how the system handles crossing boundaries and ensure that the handoff results as expected. Understanding how the system manages crossing these boundaries is crucial for identifying where errors might occur.&lt;/p&gt;
&lt;p&gt;You also may need to understand the state of the larger system at the time of the error. &amp;quot;The system&amp;quot; is not limited to the code you're working on. It can also include the state of the database, the state of the file system, the state of the network, cookies, sessions, the end user's browser, etc.&lt;/p&gt;
&lt;p&gt;When you've identified the issue, write a test that reproduces it. This confirms you've fixed it and understood it, as well as preventing its recurrence. If you don't already have a strong test suite, regression tests are a great place to start.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope this guide helps you improve your debugging skills. Debugging is a skill that you can learn and improve over time.&lt;/p&gt;
&lt;p&gt;Debugging is not just about fixing errors. It's an opportunity to learn your codebase and how to enhance it.&lt;/p&gt;
&lt;p&gt;And remember, careful thought, combined with well-placed print statements, remains the most effective debugging tool.&lt;/p&gt;</description><author>Donat Studios</author><pubDate>Thu, 21 Mar 2024 01:07:18 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/debugging</guid></item><item><title>Leaving Amazon</title><link>https://cmdev.com/blog/leaving-amazon/</link><description>Steps for leaving Amazon S3 for S3-compatible Backblaze B2</description><author>The Cranky Developer on Crater Moon Development</author><pubDate>Sat, 16 Mar 2024 20:12:09 GMT</pubDate><guid isPermaLink="true">https://cmdev.com/blog/leaving-amazon/</guid></item><item><title>Zig, Rust, and other languages</title><link>http://notes.eatonphil.com/2024-03-15-zig-rust-and-other-languages.html</link><description>&lt;p&gt;Having worked a bit in Zig, Rust, Go and now C, I think there are a
few common topics worth having a fresh conversation on: automatic
memory management, the standard library, and explicit allocation.&lt;/p&gt;
&lt;p&gt;Zig is not a mature language. But it has made enough useful choices
for a number of companies to invest in it and run it in
production. The useful choices make Zig worth talking about.&lt;/p&gt;
&lt;p&gt;Go and Rust are mature languages. But they have both made questionable
choices that seem worth talking about.&lt;/p&gt;
&lt;p&gt;All of these languages are developed by highly intelligent folks I
personally look up to. And your choice to use any one of these is
certainly fine, whichever it is.&lt;/p&gt;
&lt;p&gt;The positive and negative choices particular languages made, though, are
worth talking about as we consider what a systems programming language
10 years from now would look like. Or how these languages themselves
might evolve in the next 10 years.&lt;/p&gt;
&lt;p&gt;My perspective is mostly building distributed databases. So the points
that I bring up may have no relevance to the kind of work you do, and
that's alright. Moreover, I'm already aware most of these opinions are
not shared by the language maintainers, and that's ok too. I am not
writing to convince anyone.&lt;/p&gt;
&lt;h3 id="automatic-memory-management"&gt;Automatic memory management&lt;/h3&gt;&lt;p&gt;One of my bigger issues with Zig is that it doesn't support RAII. You
can defer cleanup to the end of a block; and this is half of the
problem. But only RAII will allow for smart pointers and automatic
(not manual) reference counting. RAII is an excellent option to
default to, but in Zig you aren't allowed to. In contrast, even C
"supports" automatic cleanup (via compiler extensions).&lt;/p&gt;
&lt;p&gt;But most of the time, arenas are fine. Postgres is written in C and
memory is almost entirely managed through nested arenas (called
"memory contexts") that get cleaned up when some subset of a task
finishes, recursively. Zig has builtin support for arenas, which is
great.&lt;/p&gt;
&lt;h3 id="standard-library"&gt;Standard library&lt;/h3&gt;&lt;p&gt;It seems regrettable that some languages have been shipping smaller
standard libraries. Smaller standard libraries seem to encourage users
of the language to install more transitively-unvetted third-party
libraries, which increases build time and build flakiness, and which
increases bitrot over time as unnecessary breaking changes occur.&lt;/p&gt;
&lt;p&gt;People have been making jokes about &lt;code&gt;node_modules&lt;/code&gt; for a decade now, but
this problem is just as bad in Rust codebases I've seen. And to a
degree it happens in Java and Go as well, though their larger standard
libraries allow you to get further without dependencies.&lt;/p&gt;
&lt;p&gt;Zig has a good standard library, which may be Go and Java tier in a
few years. But one goal of their package manager seemed to be
to allow the standard library to be broken up; made smaller. For
example, JSON support moving out of the standard library into a
package. I don't know if that is actually the planned direction. I
hope not.&lt;/p&gt;
&lt;p&gt;Having a large standard library doesn't mean that the programmer
shouldn't be able to swap out implementations easily as needed. But
all that is required is for the standard library to define an
&lt;strong&gt;interface&lt;/strong&gt; along with the standard library implementation.&lt;/p&gt;
&lt;p&gt;The small size of the standard library doesn't just affect developers
using the language, it even encourages developers of the language
itself to depend on libraries owned by individuals.&lt;/p&gt;
&lt;p&gt;Take a look at the transitive dependencies of an official Node.js
package like
&lt;a href="https://github.com/nodejs/node-gyp/blob/main/package.json#L25"&gt;node-gyp&lt;/a&gt;. Is
it really the ideal outcome of a small standard library to encourage
dependence in official libraries on libraries owned by individuals,
like &lt;a href="https://github.com/sindresorhus/env-paths"&gt;env-paths&lt;/a&gt;, that
haven't been modified in 3 years? 68 lines of code. Is it not safer at
this point to vendor that code? i.e. copy the &lt;code&gt;env-paths&lt;/code&gt; code into
&lt;code&gt;node-gyp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Similarly, if you go looking for compression support in Rust, there's
none in the standard library. But you may notice the
&lt;a href="https://github.com/rust-lang/flate2-rs"&gt;flate2-rs&lt;/a&gt; repo under the
official &lt;a href="https://github.com/rust-lang"&gt;rust-lang&lt;/a&gt; GitHub
namespace. If you look at its transitive dependencies:
&lt;a href="https://github.com/rust-lang/flate2-rs/blob/main/Cargo.toml#L23"&gt;flate2-rs&lt;/a&gt;
depends on (an individual's)
&lt;a href="https://github.com/Frommi/miniz_oxide/blob/master/miniz_oxide/Cargo.toml#L20"&gt;miniz_oxide&lt;/a&gt;
which depends on (an individual's)
&lt;a href="https://github.com/jonas-schievink/adler"&gt;adler&lt;/a&gt; that hasn't been
updated in 4 years. 300 lines of code including tests. Why not vendor
this code? It's the habits a small standard library builds that seem
to encourage everyone not to.&lt;/p&gt;
&lt;p&gt;I don't mean these necessarily constitute a supply-chain risk. I'm not
talking about
&lt;a href="https://www.theregister.com/2016/03/23/npm_left_pad_chaos/"&gt;left-pad&lt;/a&gt;. But
the pattern is sort of clear. Even official packages may end up
depending on external party packages, because the commitment to a
small standard library meant omitting stuff like compression,
checksums, and common OS paths.&lt;/p&gt;
&lt;p&gt;It's a tradeoff and maybe makes the job of the standard library
maintainer easier. But I don't think this is the ideal
situation. Dependencies are useful but should be kept to a reasonable
minimum.&lt;/p&gt;
&lt;p&gt;Hopefully languages end up more like Go than like Rust in
this regard.&lt;/p&gt;
&lt;h3 id="explicit-allocation"&gt;Explicit allocation&lt;/h3&gt;&lt;p&gt;When folk discuss the Zig standard library's pattern of requiring an
allocator argument for every method that allocates, they often talk
about the benefit of swapping out allocators or the benefit of being
able to handle OOM failures.&lt;/p&gt;
&lt;p&gt;Both of these seem pretty niche to me. For example, in Zig tests you
are encouraged to pass around a debug allocator that tells you about
memory leaks. But this doesn't seem too different from compiling a C
project with a debug allocator or compiling with different sanitizers
on and running tests against the binary produced. In both cases you
mostly deal with allocators at a global level depending on the
environment you're running the code in (production or tests).&lt;/p&gt;
&lt;p&gt;The real benefit of explicit allocations to me is much more
trivial. You basically can't code a method in Zig without
acknowledging allocations.&lt;/p&gt;
&lt;p&gt;This is particularly useful for hotpath code. Take an iterator for
example. It has a &lt;code&gt;new()&lt;/code&gt; method, a &lt;code&gt;next()&lt;/code&gt; method, and a &lt;code&gt;done()&lt;/code&gt;
method. In most languages, it's basically impossible at the syntax or
compiler-level to know if you are allocating in the &lt;code&gt;next()&lt;/code&gt; method. You
may know because you know the behavior of all the code in &lt;code&gt;next()&lt;/code&gt; by
heart. But that won't happen all the time.&lt;/p&gt;
&lt;p&gt;Zig is practically alone in that if you write the &lt;code&gt;next()&lt;/code&gt; method and
and don't pass an allocator to any method in the &lt;code&gt;next()&lt;/code&gt; body,
nothing in that &lt;code&gt;next()&lt;/code&gt; method will allocate.&lt;/p&gt;
&lt;p&gt;In any other language it might not be until you run a profiler that
you notice an allocation that should have been done once in &lt;code&gt;new()&lt;/code&gt;
accidentally ended up in &lt;code&gt;next()&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;On the other hand, for all the same reasons, writing Zig is kind of a
pain because everything takes an allocator!&lt;/p&gt;
&lt;p&gt;Explicit allocation is not intrinsic to Zig, the language. It is a
convention that is prevalent in the standard library. There is still a
global allocator and any user of Zig could decide to use the global
allocator. At which point you've got implicit allocation. So explicit
allocation as a convention isn't a perfect solution.&lt;/p&gt;
&lt;p&gt;But it, by default, gives you a level of awareness of allocations you
just can't get from typical Go or Rust or C code, depending on the
project's practices. Perhaps it's possible to switch off the Go, Rust
and C standard library and use one where all functions that allocate
do require an allocator.&lt;/p&gt;
&lt;p&gt;But explicitly passing allocators is still sort of a visual hack.&lt;/p&gt;
&lt;p&gt;I think the ideal situation in the future will be that every language
supports annotating blocks of code as &lt;code&gt;must-not-allocate&lt;/code&gt; or something
along those lines. Either the compiler will enforce this and fail if
you seem to allocate in a block marked &lt;code&gt;must-not-allocate&lt;/code&gt;, or it will
panic during runtime so you can catch this in tests.&lt;/p&gt;
&lt;p&gt;This would be useful beyond static programming languages. It would be
as interesting to annotate blocks in JavaScript or Python as
&lt;code&gt;must-not-allocate&lt;/code&gt; too.&lt;/p&gt;
&lt;p&gt;Otherwise the current state of things is that you'd normally configure
this sort of thing at the global level. Saying "there must not be
any allocations in this entire program" just doesn't seem as useful in
general as being able to say "there must not be any allocations in
this one block".&lt;/p&gt;
&lt;h4 id="optional,-not-required,-allocator-arguments"&gt;Optional, not required, allocator arguments&lt;/h4&gt;&lt;p&gt;Rust has nascent support for passing an allocator to methods that
allocate. But it's optional. From what I understand, C++ STL is like
this too.&lt;/p&gt;
&lt;p&gt;These are both super useful for programming extensions. And it's one
of the reasons I think Zig makes a ton of sense for Postgres
extensions specifically. Because it was only and always ever built for
running in an environment with someone else's allocator.&lt;/p&gt;
&lt;h3 id="praise-for-zig,-rust,-and-go-tooling"&gt;Praise for Zig, Rust, and Go tooling&lt;/h3&gt;&lt;p&gt;All three of these have really great first-party tooling including
build system, package management, test runners and formatters. The
idea that the language should provide a great environment to code in
(end-to-end) makes things simpler and nicer for programmers.&lt;/p&gt;
&lt;h3 id="meandering-non-conclusion"&gt;Meandering non-conclusion&lt;/h3&gt;&lt;p&gt;Use the language you want to use. Zig and Rust are both nice
alternatives to writing vanilla C.&lt;/p&gt;
&lt;p&gt;On the other hand, I've been pleasantly surprised writing Postgres C.
How high level it is. It's almost a separate language since you're
often dealing with user-facing constructs, like Postgres's Datum
objects which represent what you might think of as a cell in a
Postgres database. And you can use all the same functions provided for
Postgres SQL for working with Datums, but from C.&lt;/p&gt;
&lt;p&gt;I've also been able work a bit on Postgres extensions in Rust with
&lt;a href="https://github.com/pgcentralfoundation/pgrx"&gt;pgrx&lt;/a&gt; lately, which I
hope to write about soon. And when I saw
&lt;a href="https://github.com/xataio/pgzx"&gt;pgzx&lt;/a&gt; for writing Postgres extensions in Zig
I was excited to spend some time with that too.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Wrote a post on my wishlist for Zig and Rust. Focused on automatic memory management, the standard library, and explicit allocation.&lt;a href="https://t.co/dvynizU9V2"&gt;https://t.co/dvynizU9V2&lt;/a&gt; &lt;a href="https://t.co/iTXp5QVxj0"&gt;pic.twitter.com/iTXp5QVxj0&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1768725864923931033?ref_src=twsrc%5Etfw"&gt;March 15, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Fri, 15 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-03-15-zig-rust-and-other-languages.html</guid></item><item><title>Sokoban</title><link>http://akkartik.name/post/sokoban</link><description>&lt;p&gt;
The kids have been enjoying &lt;a href="https://hempuli.itch.io/baba"&gt;Baba is
You&lt;/a&gt;, and watching them brought back pleasant memories for me of playing
the classic crate-pushing game &lt;a href="https://en.wikipedia.org/wiki/Sokoban"&gt;Sokoban&lt;/a&gt;. So I went looking
and found &lt;a href="https://lcamtuf.coredump.cx/sir-box-a-lot"&gt;a very nice
project&lt;/a&gt; that has collected 300 classic publicly available Sokoban puzzles.
Then of course I had to get it on my phone so I could play it anywhere. The
result is the &lt;a href="https://akkartik.itch.io/sokoban"&gt;sokoban.love&lt;/a&gt;
client.

&lt;p&gt;
&lt;a href="https://akkartik.name/images/20240313-sokoban-solver.webm"&gt;
  &lt;video style="width: 80%; margin-left: 10%; border: 2px solid #aaaaff;"&gt;
    &lt;source src="https://akkartik.name/images/20240313-sokoban-solver.webm" /&gt;
  &lt;/video&gt;
&lt;/a&gt;
&lt;div class="btw" style="text-align: right;"&gt;
  video; 1 minute
&lt;/div&gt;

&lt;!-- more --&gt;

&lt;p&gt;
On a technical level, with &lt;a href="https://git.sr.ht/~akkartik/sokoban.love"&gt;sokoban&lt;/a&gt;.love
I've finally managed to figure out how to scale modifying programs on my phone
beyond the tiny scripts &lt;a href="https://akkartik.name/post/carousel"&gt;Lua Carousel&lt;/a&gt; supports.
Carousel treats each 'page' of the carousel as a separate script, and shares
the screen between the code for the page and the drawings the page makes. When
you switch between pages, Carousel saves and restores code for you so the
script currently on screen is always the one currently drawing.

&lt;p&gt;
sokoban.love comes bundled with multiple pages of code (including 7000 lines
for all the levels; those would be a pain to copy paste into Carousel). The
pages all collaborate to create the app; switching pages changes nothing about
the code that is running. The screen is also no longer shared between the app
and its code editing environment. When you run the app the Carousel menu
disappears, replaced by a single button to exit the app and edit its code.

&lt;p&gt;
This approach works well for editing on a phone. The trade-off I made is to
jettison the live-editing experience. You can still get that with
sokoban.love, but you'll need to get on a computer and connect driver.love to
it like all my Freewheeling Apps.

&lt;p&gt;
As a bonus, sokoban.love includes a simple solver to eliminate some gruntwork
for moving the player on touchscreens that you can see in action in this video.
Tapping on the buttons along the edges moves the player a single square.
Tapping on an empty square moves the player there if that is possible without
moving any crates. Tapping on a crate and then an empty square will try to get
the crate there if that is possible without moving any &lt;em&gt;other&lt;/em&gt; crates.</description><author>Kartik Agaram</author><pubDate>Wed, 13 Mar 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/sokoban</guid></item><item><title>Quickly reviewing your journal with bash</title><link>https://jodavaho.io/posts/bash-journalling-review.html</link><description>&lt;p&gt;I&amp;rsquo;ve been journalling for 10+ years. In addition to forcing me to actually write down what happened that day, reviewing old entries provides a feeling of history that makes life feel soooo long and so rich. I can review my now-wife&amp;rsquo;s rocky courtship and feel grateful we made it. I looked back 3 years ago and found the day our children were conceived :D (we have twins). I can review the anxieties of my PhD years, etc etc.&lt;/p&gt;
&lt;p&gt;For some reason, feeling like my past life has not been short helps me to feel that there&amp;rsquo;s so much life left to live. Looking back at the phenomenal changes of the last 5 years (or 10), shows me that I can do a lot with the next 5 years.&lt;/p&gt;
&lt;p&gt;Novelty and so on may help to &amp;ldquo;slow&amp;rdquo; time, but for me the perception of the shortness of life is best fought by reminding yourself that it is not short, and there is so much change coming - more than you could imagine.&lt;/p&gt;
&lt;p&gt;(and I&amp;rsquo;m horrible at doing it every day, maybe every week or so during slumps)&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re at our computers all day every day. So I just lowered the barrier to entry with a few bash tricks. It&amp;rsquo;s helped me keep the habit up.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://jodavaho.io/tags/bullet-journal.html"&gt;https://jodavaho.io/tags/bullet-journal.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, I review 1, 5, and 10 years ago every day, to re-live my life from those years, so to speak:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# list dates from 10 years ago +/3 3 days
# get years and day range from args
echo &amp;quot;### $years years ago&amp;quot;
for i in `seq -w -$days $days`
do
  olddate=`date -d &amp;quot;$years years ago $i days&amp;quot; +%Y-%m-%d`  
  longdate=`date -d &amp;quot;$years years ago $i days&amp;quot; +%A\ %B\ %d,\ %Y`
  echo &amp;quot;### $olddate.md ($longdate)&amp;quot;
  cat $olddate.md
done
&lt;/code&gt;&lt;/pre&gt;</description><author>jodavaho.io</author><pubDate>Mon, 11 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/bash-journalling-review.html</guid></item><item><title>First month on a database team</title><link>http://notes.eatonphil.com/2024-03-11-first-month-on-a-database-team.html</link><description>&lt;p&gt;&lt;!-- -*- mode: markdown -*- --&gt;&lt;/p&gt;
&lt;p&gt;A little over a month ago, I joined EnterpriseDB on a
distributed Postgres product
(&lt;a href="https://enterprisedb.com/docs/pgd"&gt;PGD&lt;/a&gt;). The process of onboarding
myself has been pretty similar at each company in the last decade,
though I think I've gotten better at it. The process is of course
influenced by the team, and my coworkers have been excellent. Still, I
wanted to share my thought process and personal strategies.&lt;/p&gt;
&lt;h3 id="avoid,-at-first,-what-is-always-challenging"&gt;Avoid, at first, what is always challenging&lt;/h3&gt;&lt;p&gt;Trickier things at companies are the people, organization, and
processes. What code exists? How does it work together? Who owns what?
How can I find easy code issues to tackle? How do I know what's
important (so I can avoid picking it up and becoming a bottleneck).&lt;/p&gt;
&lt;p&gt;But also, in the first few days or weeks you aren't exactly expected
to contribute meaningfully to features or bugs. Your sprint
contributions are not tracked too closely.&lt;/p&gt;
&lt;p&gt;The combination of 1) what to avoid and 2) the sprint-freedom-you-have
leads to a few interesting and valuable areas to work on on your own:
the build process, tests, running the software, and docs.&lt;/p&gt;
&lt;p&gt;But code need not be ignored either. Some frequent areas to get your
first code contributions in include user configuration code, error
messages, and stale code comments.&lt;/p&gt;
&lt;p&gt;What follows are some little 1st day, 1st week, 1st month projects I
went through to bootstrap my understanding of the system.&lt;/p&gt;
&lt;h3 id="build-process"&gt;Build process&lt;/h3&gt;&lt;p&gt;First off, where is the code and how do you build it? This requires
you to have all the relevant dependencies. Much of my work is on a
Postgres extension. This meant having a local Postgres development
environment, having gcc, gmake (on mac), Perl, and so on. And
furthermore, PGD is a pretty mature product so it supports building
against multiple Postgres distributions. Can I build against all of
them?&lt;/p&gt;
&lt;p&gt;The easiest situation is when there are instructions for all of this,
linked directly from your main repo. When I started, the instructions
did exist but in a variety of places. So over the first week I started
collecting all of what I had learned about building the system, with
dependencies, across distributions, and with various important flags
(debug mode on, asserts enabled, etc.). I finished the first week by
writing a little internal blog post called "Hacking on PGD".&lt;/p&gt;
&lt;p&gt;I hadn't yet figured out the team processes so I didn't want to bother
anyone by trying to get this "blog post" committed anywhere yet as
official internal documentation. Maybe there already was a good doc, I
just hadn't noticed it yet. So I just published it in a private
Confluence page and shared it in the private team slack. If anyone
else benefited from it, great! Otherwise, I knew I'd want to refer
back to it.&lt;/p&gt;
&lt;p&gt;This is an important attitude I think. It can be hard to tell what
others will benefit from. If you get into the habit of writing things
down internally for your own sake, but making it available internally,
it is likely others will benefit from it too. This is something I've
learned from years of blogging publicly outside of work.&lt;/p&gt;
&lt;p&gt;Moreover, the simple act of writing a good post creates yourself as
something of an authority. This is useful for yourself if no one else.&lt;/p&gt;
&lt;h4 id="writing-a-good-post"&gt;Writing a good post&lt;/h4&gt;&lt;p&gt;Let's get distracted here for a second. One of the most important
things I think in documentation is documenting not just what does
exist but what doesn't. If you had to take a path to get something to
work, did you try other paths that didn't work? It can be extremely
useful to figure out what &lt;em&gt;exactly&lt;/em&gt; is required for something.&lt;/p&gt;
&lt;p&gt;Was there a flag that you tried to build with but you didn't try
building without it? Well try again without it and make sure it was
necessary. Was there some process you executed where the build
succeeded but you can't remember if it was actually necessary for the
build to succeed?&lt;/p&gt;
&lt;p&gt;It's difficult to explain why I think this sort of precision is
useful but I'm pretty sure it is. Maybe because it builds the habit of
not treating things as magic when you can avoid it. It builds the
habit of asking questions (if only to yourself) to understand and not
just to get by.&lt;/p&gt;
&lt;h4 id="static-analysis?-dynamic-analysis?"&gt;Static analysis? Dynamic analysis?&lt;/h4&gt;&lt;p&gt;Going back to builds, another aspect to consider is static and dynamic
analysis. Are there special steps to using gdb or valgrind or other
analyzers? Are you using them already? Can you get them running
locally? Has any of this been documented?&lt;/p&gt;
&lt;p&gt;Maybe the answer to all of those is yes, or maybe none of those are
relevant but there are likely similar tools for your ecosystem. If
analysis tools are relevant and no one has yet explored them, that's
another very useful area to explore as a newcomer.&lt;/p&gt;
&lt;h3 id="testing"&gt;Testing&lt;/h3&gt;&lt;p&gt;After I got the builds working, I felt the obvious next step was to
run tests. But what tests exist?  Are there unit tests? Integration
tests? Anything else? Moreover, is there test coverage? I was certain
I'd be able to find some low-hanging contributions to make if I could
find some files with low test coverage.&lt;/p&gt;
&lt;p&gt;Alas, my certainty hit the wall in that there were in fact too many
types of integration tests that all do provide coverage already. They
just don't all &lt;em&gt;report&lt;/em&gt; coverage.&lt;/p&gt;
&lt;p&gt;The easiest ways to report coverage (with gcov) were only reporting
coverage for certain integration tests that we run locally. There are
more integration tests run in cloud environments and getting coverage
reports there to merge with my local coverage files would have
required more knowledge of people and processes, areas that I wanted
not to be forced to think about too quickly.&lt;/p&gt;
&lt;p&gt;So coverage wasn't a good route to go. But around this time, I noticed
a ticket that asked for a simple change to user configuration code. I
was able to make the change pretty quickly and wanted to add tests. We
have our own test framework built on top of Postgres's powerful Perl
test framework. But it was a little difficult to figure out how to use
either of them.&lt;/p&gt;
&lt;p&gt;So I copied code from other tests and pared it down until I got the
smallest version of test code I could get. This took maybe a day or
two of tweaking lines and rerunning tests since I didn't understand
everything that was/wasn't required. Also it's Perl and I've never
written Perl before so that took a bit of time and ChatGPT. (Arrays,
man.)&lt;/p&gt;
&lt;p&gt;In the end though I was able to collect my learnings into another
internal confluence post just about how to write tests, how to debug
tests, how to do common things within tests (for example, ensuring a
Postgres log line was outputted), etc. I published this post as well
and shared it in the team Slack.&lt;/p&gt;
&lt;h3 id="running"&gt;Running&lt;/h3&gt;&lt;p&gt;I had PGD built locally and was able to run integration tests locally,
but I still hadn't gotten a cluster running. Nor played with the
eventual consistency demos I knew we supported. We had a great
quickstart that ran through all the manual steps of getting a two-node
cluster up. This was a distillation, for devs, of a more elaborate
process we give to customers in a production-quality script.&lt;/p&gt;
&lt;p&gt;But I was looking for something in between a production-quality
script and manually initializing a local cluster. And I also wanted to
practice my understanding of our test process. So I ported our
quickstart to our integration test framework and made a PR with this
new test, eventually merging this into the repo. And I wrote a minimal
Python script for bringing up a local cluster. I've got an open PR to
add this script to the repo. Maybe I'll learn though that a simple
script such as this does already exist, and that's fine!&lt;/p&gt;
&lt;h3 id="docs"&gt;Docs&lt;/h3&gt;&lt;p&gt;The entire time, as I'd been trying to build and test and run PGD, I
was trying to understand our terminology and architecture by going
through our public docs. I had a lot of questions coming out of this
I'd ask in the team channel.&lt;/p&gt;
&lt;p&gt;Not to toot my horn but I think it's somewhat of a superpower to be
able/willing to ask "dumb questions" in a group setting. That's how I
frame it anyway. "Dumb question: what does X mean in this paragraph?"
Or, "dumb question: when we say performance improvement because of Y,
what is the intuition here?" Because of the time spent here, I was
able to make a few more docs contributions as I read through the docs
as well.&lt;/p&gt;
&lt;p&gt;You have to balance where you ask your dumb questions though. Asking
dumb questions to one person doesn't benefit the team. But asking dumb
questions in too wide a group is sometimes bad politics. Asking "dumb
questions" in front of your team seems to have the best bang for buck.&lt;/p&gt;
&lt;p&gt;But maybe the more important contributions were, when I got more
comfortable with the team, proposing to merge my personal, internal
Confluence blog posts into the repo as docs. I think in a number of
cases, what I wrote about indeed hadn't been concisely collected
before and thus was useful to have as team documentation.&lt;/p&gt;
&lt;p&gt;Even more challenging was trying to distill (a chunk of) the internal
architecture. Only after following many varied internal docs and
videos, and following through numerous code paths, was I able to
propose an architecture diagram outlining major components and
communication between them, with their differing formats (WAL records,
internal enums, etc.) and means of communication (RPC, shared memory,
etc.). This architecture diagram is still in review and may be totally
off. But it's already helped at least me think about the system.&lt;/p&gt;
&lt;p&gt;In most cases this was all information that the team had already
written or explained but just bringing it together and summarizing
provided a different useful perspective I think. Even if none of the
docs got merged it still helped to build my own understanding.&lt;/p&gt;
&lt;h3 id="beyond-the-repo"&gt;Beyond the repo&lt;/h3&gt;&lt;p&gt;Learning the project is just one aspect of onboarding. Beyond that I
join the #cats channel, the #dogs channel, found some fellow New
Yorkers and opened a NYC channel, and tried to find Zoom-time with the
various people I'd see hanging around common team Slack
channels. Trying to meet not just devs but support folk, product
managers, marketing folk, sales folk, and anyone else!&lt;/p&gt;
&lt;p&gt;Walking the line between scouring our docs and GitHub and Confluence
and Jira on my own, and bugging people with my incessant questions.&lt;/p&gt;
&lt;p&gt;I've enjoyed my time at startups. I've been a dev, a manager, a
founder, a cofounder. But I'm incredibly excited to be back, at a
bigger company, full-time as a developer hacking on a database!&lt;/p&gt;
&lt;p&gt;And what about you? What do you do to onboard yourself at a new
company or new project?&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I've been having an absolute blast in my first month at EDB and I wanted to share a few of my strategies for onboarding myself on a database team. Strategies broadly applicable for devs on a new team/project.&lt;a href="https://t.co/TS5qRLysuA"&gt;https://t.co/TS5qRLysuA&lt;/a&gt; &lt;a href="https://t.co/lvuxDBQJwx"&gt;pic.twitter.com/lvuxDBQJwx&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1767371003527672237?ref_src=twsrc%5Etfw"&gt;March 12, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 11 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-03-11-first-month-on-a-database-team.html</guid></item><item><title>Pensieve: Mar 8 2024 - on Dune 2</title><link>https://www.swyx.io/pensieve-mar-8-2024-on-dune-2</link><description>A collection of public thoughts that could be blogposts but i dont have time, so here, have the short form. I may upgrade these to full posts in future.</description><author>swyx's site RSS Feed</author><pubDate>Sat, 09 Mar 2024 13:45:00 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/pensieve-mar-8-2024-on-dune-2</guid></item><item><title>The volatility of simple trading strategy returns</title><link>https://bytepawn.com/the-volatility-of-simple-trading-strategy-returns.html</link><description>&lt;p&gt;I investigate the volatility of aggressive take-profit strategies on tech stocks. &lt;br /&gt;&lt;br /&gt;&lt;img alt="." src="/images/stock-volatility-creative-1.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 09 Mar 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/the-volatility-of-simple-trading-strategy-returns.html</guid></item><item><title>Pensieve: Mar 7 2024 - on Agency</title><link>https://www.swyx.io/pensieve-mar-7-2024-on-agency</link><description>A collection of public thoughts that could be blogposts but i dont have time, so here, have the short form. I may upgrade these to full posts in future.</description><author>swyx's site RSS Feed</author><pubDate>Fri, 08 Mar 2024 09:26:35 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/pensieve-mar-7-2024-on-agency</guid></item><item><title>Installing direnv on macOS</title><link>https://boyter.org/posts/installing-direnv-macos/</link><description>&lt;p&gt;Having had to install direnv multiple times over the last few days to get a team up and running im writing the method down because I keep having to look it up.&lt;/p&gt;
&lt;p&gt;Install based on the following instructions &lt;a href="https://github.com/direnv/direnv/blob/master/docs/installation.md"&gt;https://github.com/direnv/direnv/blob/master/docs/installation.md&lt;/a&gt;
Then hook it up to your shell via &lt;a href="https://github.com/direnv/direnv/blob/master/docs/hook.md"&gt;https://github.com/direnv/direnv/blob/master/docs/hook.md&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lastly to have it work with .env files create the following directory and file&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;~/.config/direnv/direnv.toml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and edit the contents to contain the following,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;[&lt;/span&gt;global&lt;span style="color: #f92672;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;load_dotenv&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You will need to restart your shell after doing this. Opening a new terminal is the easiest way to do this.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 07 Mar 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/installing-direnv-macos/</guid></item><item><title>Acquired in 99 days</title><link>https://austinhenley.com/blog/acquiredin99days.html</link><description>&lt;a href="https://austinhenley.com/blog/acquiredin99days.html"&gt;https://austinhenley.com/blog/acquiredin99days.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Wed, 06 Mar 2024 17:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/acquiredin99days.html</guid></item><item><title>A simple package for running many linters</title><link>https://blog.steve.fi/a_simple_package_for_running_many_linters.html</link><description>&lt;p&gt;I used to configure Emacs to run a linter when saving some specific type of files.  For example I'd have a perl-utilities package to reformat perl code, and run the perl-linter on saving, then I'd have a hook to do the same thing for Dockerfiles, etc, etc.&lt;/p&gt;

&lt;p&gt;It occurred to me recently that I should have a linter for both JSON and YAML files, since I have to edit those filetypes so damn often, and that there wasn't a great solution for those - Until it occurred to me I wrote &lt;a href="https://github.com/skx/sysbox" rel="nofollow"&gt;sysbox&lt;/a&gt; which is a simple collection of tools in one binary, and that supports some validation commands:&lt;/p&gt;

&lt;div class="highlight highlight-sh"&gt;&lt;pre&gt;sysbox validate-json /path/to/file
sysbox validate-yaml /path/to/file
sysbox validate-xml  /path/to/file
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With that in mind it became obvious that what I want to do is pretty much always the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run an external command, when the file is saved.

&lt;ul&gt;
&lt;li&gt;If the exit-code of that command is "success" (i.e. zero):

&lt;ul&gt;
&lt;li&gt;Do nothing.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;If the exit-code is "failure" (i.e. non-zero):

&lt;ul&gt;
&lt;li&gt;Show the output.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this process is the same for ANY of the linters I run.  The only thing that changes is the command to run, based on the mode/type of file in question.&lt;/p&gt;

&lt;p&gt;That lead to the following configuration:&lt;/p&gt;

&lt;div class="highlight highlight-lisp"&gt;&lt;pre&gt;(defvar save-check-config
      '(

        (:mode cperl-mode
         :exec "perl -wc -I. %f"
         :cond (executable-find "perl"))

        (:mode dockerfile-mode
         :exec "hadolint --no-color %f"
         :cond (executable-find "hadolint"))

        (:mode json-mode
         :exec "sysbox validate-json %f"
         :cond (executable-find "sysbox"))

        (:mode nxml-mode
         :exec "sysbox validate-xml %f"
         :cond (executable-find "sysbox"))

        (:mode perl-mode
         :exec "perl -wc -I. %s"
         :cond (executable-find "perl"))

        ;; This avoids creating .pyc files, which would happen if we had
        ;; used the more natural/obvious "python3 -m py_compile %s" approach
        (:mode python-mode
         :exec "python3 -c 'import ast; ast.parse(open(\"%f\").read())'"
         :cond (executable-find "python3"))

        (:mode sh-mode
         :exec "shellcheck %f"
         :cond (executable-find "shellcheck"))

        (:mode terraform-mode
         :exec "tflint --no-color --chdir %d"
         :cond (executable-find "tflint"))

        (:mode yaml-mode
         :exec "sysbox validate-yaml %f"
         :cond (executable-find "sysbox"))
        )
     )
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Basically a list of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have the mode of files to which the linter/validator applies.&lt;/li&gt;
&lt;li&gt;We have the command to run

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%f&lt;/code&gt; is changed to the filename which has just been saved.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%d&lt;/code&gt; is changed to the directory-name containing that file.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;We add a &lt;code&gt;:cond&lt;/code&gt; key to decide if we should run.

&lt;ul&gt;
&lt;li&gt;Which basically is used for "if the binary is found .. run it, otherwise silently do nothing".&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm quite pleased with how simple the package was to write, and now I have all my linting configuration in one-place.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/skx/save-check.el/" rel="nofollow"&gt;https://github.com/skx/save-check.el/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd be tempted to do the same for "format on save", but to be honest with LSP most of the code I care about has that in-place already.&lt;/p&gt;

&lt;p&gt;Should I rename to "multi-lint[er].el"?  Probably, but I guess we'll see in the future.&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Sun, 03 Mar 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/a_simple_package_for_running_many_linters.html</guid></item><item><title>The Yak Shave</title><link>https://www.marginalia.nu/log/a_102_yak_shave/</link><description>&lt;p&gt;I set out a little over a week ago to add a service registry to Marginalia Search,
primarily to reduce its dependence on docker. I would like it to be able to run
on bare metal as well, which poses a problem since configuring the application manually
is a bit of a headache with dozens of ports that need to be set up. It would also be
desirable to be able to run multiple instances of important services in order elliminate
downtime during upgrades.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 28 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_102_yak_shave/</guid></item><item><title>2024–02–28: Support for WiFi on QuartzPro64</title><link>https://xnux.eu/log/#100</link><author>megi's PinePhone Development Log</author><pubDate>Wed, 28 Feb 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#100</guid></item><item><title>Ideas</title><link>https://donatstudios.com/ideas</link><description>&lt;ul&gt;
&lt;li&gt;A minimal functional language, maybe similar to Excel equations for sharing logic between unrelated languages. Share the same logic between a Go or PHP backend and a JavaScript front end.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mtpl&lt;/code&gt; minimum viable template language - like mustache but with less gotchas&lt;/li&gt;
&lt;li&gt;A pkg.go.dev style service for PHP / composer packages.&lt;/li&gt;
&lt;li&gt;Materialized JSON Parser - generate parsers ahead of time for specific JSON structures to read much faster&lt;/li&gt;
&lt;li&gt;Raspberry PI Thermostat&lt;/li&gt;
&lt;/ul&gt;</description><author>Donat Studios</author><pubDate>Mon, 26 Feb 2024 20:43:34 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/ideas</guid></item><item><title>2024 Feb Summary</title><link>https://jodavaho.io/now/2024-feb.html</link><description>&lt;p&gt;I&amp;rsquo;ve been really into the &lt;a href="https://boardgamegeek.com/boardgame/354570/undaunted-stalingrad"&gt;Undaunted:
Stalingrad&lt;/a&gt;
game. Have been playing a lot less &lt;a href="https://huntshowdown.com"&gt;Hunt showdown&lt;/a&gt;,
since I&amp;rsquo;ve been travelling, doing jury duty, and working more on this blog and
other side projects.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m noodling a new personal finance side project, this one might actually
be of general use. &lt;a href="https://hfopt.jodavaho.io"&gt;Highfleet Opt&lt;/a&gt; is still living,
and we hope to do more work on it.&lt;/p&gt;
&lt;p&gt;The kids are precocious, learning ownership and boundaries, trying to sound out
letters for words (at our prompt, mostly), and being extremely energetic at
home. It&amp;rsquo;s quite a workout.&lt;/p&gt;
&lt;p&gt;Wife and I are running up to a 6mi goal for her marathon relay. We enjoyed a
nice trip to Mexico lately for a wedding.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://outrider.ai"&gt;Work at Outrider&lt;/a&gt; is pretty nuts. Busy, for sure. Some
friends have left, which is always a bummer. I am really proud of the work we
did there to speed up the multi-agent planner by 100x (minutes to less than a
second, max of 10s of minutes to a few seconds). I&amp;rsquo;d love to do more of that
deep technical work. But 99% of all problems in software are interfaces, as in
interfaces between teams and management and processes, so that&amp;rsquo;s always where
the trouble will be.&lt;/p&gt;
&lt;p&gt;I cannot believe we have no snow. None at all. Last year this time we had a
least 18&amp;quot;, with it piled up to 4&amp;rsquo; on each side of my driveway.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Mon, 26 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/now/2024-feb.html</guid></item><item><title>Persona 3 Reload review</title><link>https://burakku.com/blog/persona-3-reload-review/</link><description>&lt;p&gt;&lt;img alt="Persona 3 Reload" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Welcome back.&lt;/p&gt;
&lt;p&gt;Like for many others, Persona 3 was my first foray into the Persona series, having played through FES back in 2011, and will always hold a special place in my heart. I have also been wanting for a Persona 3 remake &lt;a href="https://twitter.com/Mailia/status/1312829346491621379" target="_blank"&gt;since at least 2020&lt;/a&gt;, so I’ve been extremely excited to play through Persona 3 Reload ever since the rumours were confirmed to be real. And yet, with years of lofty expectations weighing down on it, I absolutely loved Persona 3 Reload.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Da bomb" src="dabomb.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Story-wise Persona 3 has always remained as my favourite one in the series. Granted, some of that might be nostalgia for my first Persona story, but it’s a feeling that’s stayed with me after Persona 4 and Persona 5. I just really enjoy the theme and overall feeling of the story. And now that I have experienced it again, I think I can still confidently claim it to be the superior of the three. Thankfully Reload manages to not alter the story even though it does ever-so-slightly expand it here and there. Not changing the story is also to its benefit since ATLUS’ efforts to add new story content for re-releases have often had very mixed results. And despite knowing the story, although fuzzy with some details after 13 years, the emotional highs still bit hard.&lt;/p&gt;
&lt;p&gt;The gameplay side of things has a lot more changes, but it still very much feels like Persona 3. I was actually quite surprised that they didn’t relax the social link calendar to make it more like Persona 5 Royal. Getting all of my bonds maxed out in a single playthrough was definitely challenging with few days to spare. Granted, the social links are still considerably easier than in the original, but not to the point of feeling like the other games. The overall package does feel like a very good balance of the old experience and new quality of life.&lt;/p&gt;
&lt;p&gt;The new Rewind system is excellent. I imagine it’s going to become a new staple feature in every single Persona game going forwards and reduce some of the save overwrite anxiety. Would’ve definitely saved me some time in prior titles. It’s also nice to see that despite trying to be faithful to the original game, they’re not beyond innovating when they have good new ideas, especially when they’re ones that do not dilute the original experience.&lt;/p&gt;
&lt;p&gt;The combat side of things has definitely been Persona 5-ified – but actually less so than I imagined. I fully expected them to just clone the Royal combat system and call it a day, but they actually made a system unique to Reload. Familiar, but unique. And much like Persona 5’s combat system, it very much works. It’s fluid and fun. I always remembered Tartarus being the worst part of the original game, but somehow it doesn’t actually even as bad as I remembered in Reload. Either I am completely misremembering the experience or Reload has just improved it that much. Of course, it is still very repetitive labyrinth of procedural generation, but still a decent climb. The visual update also does help to make it less bleak and boring. The background music is however very faithful to the original, as in, not very inspiring. Might’ve listened to a couple of podcasts while I was at it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Maid Aegis" src="aegis.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The new visuals do look and feel excellent. It’s not exactly the sharpest edge of graphics technology and not all of the art assets are polished to the highest degree, but the style does make up for a lot of it. Especially when the only available versions of Persona 3 have been extremely low fidelity, Reload just feels extremely pretty in comparison. And I just absolutely love all of the character models. I wanted them to remake Persona 3 with the character models from Dancing in Moonlight when that came out, but Reload’s models are even better. And in a series first, there’s ray-traced reflections, which do look really nice.&lt;/p&gt;
&lt;p&gt;The biggest downside might be the performance though – there’s some definite Unreal stutter happening even on powerful hardware. Maybe they’ll patch that out in due time, although I wouldn’t count on it. There’s definitely room for improvement on that department.&lt;/p&gt;
&lt;p&gt;There’s still 2D-animated cutscenes, although some cutscenes that I remember being 2D animation in the original are now 3D live-rendered cutscenes. While the quality of the 2D animation has never been the highest, both in the original and Reload, I’m of two minds about this. The cutscenes in Reload are definitely good, but there’s just something irresistible about 2D cutscenes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Yeah, whatever." src="whatever.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The voice acting in Persona 3 Reload is immaculate. First of all, almost all of the original cast from Persona 3 returns to redo their lines and they all sound exactly how I remember them sounding over a decade ago. The only exception that I am aware is Tanonaka, who passed away in 2010, and they got a fairly alright soundalike to voice Igor in his stead. And to make things even better, they got a bunch of voice actors for all of the social links, with every single rank of every single social link (bar the obvious silent one) being fully voiced! I really hope that we can expect all new Persona games to keep up this level of quality and quantity when it comes to voice acting.&lt;/p&gt;
&lt;p&gt;The music side initially feels rather uncanny. Everything sounds so familiar to the songs I’ve had in my music library for the past ten years and yet are strikingly different. However, that feeling of uncanniness did wear off as I kept playing, and the music does feel like Persona 3 – even if served with the whiff of a bootleg recording. The couple of pivotal songs during critical parts of the game are also kept and sound very similar to the originals despite Kawamura Yumi being replaced with a new face in Takahashi Azumi. There are also a couple of original bangers, so I’ll definitely be adding Reload’s soundtrack to my usual music rotation in the future.&lt;/p&gt;
&lt;p&gt;Yet I unfortunately can’t fully shake the feeling that maybe a small bit of Persona 3’s identity has been lost in the Persona 5-ification of the game. Not to a degree where I’d want to not play this or not recommend it to others, but just enough to make me think that there’s still a place in the universe for the original. I know that there’s definitely still a place for Portable, since the alternative perspective story for the female protagonist is not part of a Persona 3 remake. A lot of people were very vocal about this omission, but I don’t consider it critical: Portable always made it very clear that the female protagonist route was an alternative one. A new perspective, if you will. And who knows, maybe ATLUS will find the strength and courage to take our money again in the form of DLC for new perspectives.&lt;/p&gt;
&lt;p&gt;Remakes are always a contentious topic; if it’s okay to replace part of the history with the new and shiny. However, I do think that Persona 3 Reload is a product of exquisite quality and that it’s definitely a great version of Persona 3 and more accessible for fans both new and old who’ve gotten used to the new way of things. However, that is what I think it should be considered as: a new and different version of Persona 3, and not as &lt;em&gt;the&lt;/em&gt; new Persona 3 to replace the old. A great way to experience the original story, even if wrapped in a remixed experience. It’s not like hordes of people were going to dust off their PlayStation 2 to experience Persona 3, especially when the games are often going for what they retailed for originally – or possibly even more.&lt;/p&gt;
&lt;p&gt;As an old fan of the series, I do highly recommend Persona 3 Reload. Whatever annoyance I felt before for the increased 70€ price tag has been replaced by the sheer joy that I got from my 100-hour journey into an old friend. I also imagine that people completely unfamiliar with Persona 3 will also enjoy it, as it is definitely a great game.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Memories of You" src="end.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;PS: Don't pay extra for the digital soundtrack. You'll hate yourself if you do. Just wait for a real soundtrack release.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 26 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/persona-3-reload-review/</guid></item><item><title>Ruby One-Liners Guide book announcement</title><link>https://learnbyexample.github.io/ruby-oneliners-guide-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;Ruby One-Liners Guide&lt;/strong&gt; ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected.&lt;/p&gt;
&lt;p&gt;When it comes to command line text processing, there are several well known tools like &lt;code&gt;grep&lt;/code&gt; for filtering, &lt;code&gt;sed&lt;/code&gt; for substitution and &lt;code&gt;awk&lt;/code&gt; for field processing. Compared to such tools, &lt;strong&gt;Ruby has a feature rich regular expression engine, plenty of builtin modules and a thriving ecosystem&lt;/strong&gt;. Another advantage is that Ruby is more &lt;strong&gt;portable&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This ebook will show examples for filtering and substitution features, field processing, using standard and third-party modules, multiple file processing, how to construct solutions that depend on multiple records, how to compare records and fields between two or more files, how to identify duplicates while maintaining input order and so on.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of &lt;strong&gt;Ruby One-Liners Guide&lt;/strong&gt; for FREE till 29-February-2024. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/ruby-oneliners"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/ruby-oneliners/c/new_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Ruby Text Processing&lt;/strong&gt; bundle is free as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/ruby-textprocessing"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/ruby-textprocessing/c/new_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So is the &lt;strong&gt;Magical one-liners&lt;/strong&gt; bundle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/oneliners"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/oneliners/c/new_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Command version updated to &lt;strong&gt;Ruby 3.3.0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Added more exercises&lt;/li&gt;
&lt;li&gt;Long sections split into smaller ones&lt;/li&gt;
&lt;li&gt;In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;Ruby One-Liners Guide&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;This Ruby one-liners cookbook is incredible. Pretty mind boggling all the stuff you can do.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/jbrancha/status/1506766118756786189"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;One-liner introduction&lt;/li&gt;
&lt;li&gt;Line processing&lt;/li&gt;
&lt;li&gt;Field separators&lt;/li&gt;
&lt;li&gt;Record separators&lt;/li&gt;
&lt;li&gt;Multiple file input&lt;/li&gt;
&lt;li&gt;Processing multiple records&lt;/li&gt;
&lt;li&gt;Two file processing&lt;/li&gt;
&lt;li&gt;Dealing with duplicates&lt;/li&gt;
&lt;li&gt;Processing structured data&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/learn_ruby_oneliners/"&gt;https://learnbyexample.github.io/learn_ruby_oneliners/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/learn_ruby_oneliners"&gt;https://github.com/learnbyexample/learn_ruby_oneliners&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/learn_ruby_oneliners/issues"&gt;https://github.com/learnbyexample/learn_ruby_oneliners/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 26 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/ruby-oneliners-guide-announcement/</guid></item><item><title>LLMs on Hitler vs Elon</title><link>http://blog.onepatchdown.net/llm/ai/elon/2024/02/25/llm-hitler-elon/</link><description>&lt;p&gt;So the question comes up via Twitter: who negatively impacted society more, elon tweeting memes or Hitler. When posted to Google’s Gemini LLM, it gets a hilaribad response back.&lt;/p&gt;

&lt;p&gt;Gemini: &lt;img alt="Gemini" src="/assets/llm-gemini-hitler-elon.png" /&gt; &lt;a href="https://twitter.com/NateSilver538/status/1761800684272308302"&gt;Twitter&lt;/a&gt;
ChatGPT: &lt;img alt="ChatGPT" src="/assets/llm-chatgpt-hitler-elon.png" /&gt; &lt;a href="https://chat.openai.com/share/dc737931-be2b-441c-8484-cf731446e1a0"&gt;chat.openai.com&lt;/a&gt;
Llama2-uncensored: &lt;img alt="llama-uncensored" src="/assets/llm-llama2-uncensored-hitler-elon.png" /&gt;
Grok: &lt;img alt="Grok" src="/assets/llm-grok-hitler-elon.png" /&gt; &lt;a href="https://twitter.com/echotoall/status/1761802775283826981"&gt;Twitter&lt;/a&gt;
and Grok on fun mode: &lt;img alt="Grok-fun" src="/assets/llm-grok-fun-hitler-elon.png" /&gt; &lt;a href="https://twitter.com/TheoBrewsevelt/status/176183286574950818://twitter.com/TheoBrewsevelt/status/1761832865749508181"&gt;Twitter&lt;/a&gt;&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Mon, 26 Feb 2024 01:10:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/llm/ai/elon/2024/02/25/llm-hitler-elon/</guid></item><item><title>Marginalia: 3 Years</title><link>https://www.marginalia.nu/log/a_101_marginalia-3-years/</link><description>&lt;p&gt;It&amp;rsquo;s been three years since the inception of Marginalia Search, then
a dinky experiment to find where the heck the cool Internet has gone,
now my full time job.&lt;/p&gt;
&lt;p&gt;While there&amp;rsquo;s always things that can be improved, it&amp;rsquo;s fair to say
the search engine has never worked as well as it does right now.&lt;/p&gt;
&lt;p&gt;A great number of milestones have been reached, perhaps biggest
of all the search engine has moved out of my living room and into
a proper enterprise server.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sun, 25 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_101_marginalia-3-years/</guid></item><item><title>Introductory investigations into the stability of stock price volatility</title><link>https://bytepawn.com/introductory-investigations-into-the-stability-of-stock-price-volatility.html</link><description>&lt;p&gt;I investigate the volatility of some securities, and its stability over time. &lt;br /&gt;&lt;br /&gt;&lt;img alt="." src="/images/intro_volatility_4.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 25 Feb 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/introductory-investigations-into-the-stability-of-stock-price-volatility.html</guid></item><item><title>2024–02–24: Some upstreaming and Pinephone Pro modem audio</title><link>https://xnux.eu/log/#099</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 24 Feb 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#099</guid></item><item><title>rabbot.love</title><link>http://akkartik.name/post/rabbot</link><description>&lt;p&gt;
&lt;a href="https://akkartik.itch.io/rabbot-run"&gt;rabbot.love&lt;/a&gt; is
a little helper I whipped up to check the programs the kids were writing for
&lt;a href="https://www.kiwico.com/us/store/dp/robot-coder-starter-kit/5555"&gt;a
neat little paper computer&lt;/a&gt;.

&lt;p&gt;
&lt;a href="https://akkartik.name/images/20240220-rabbot.webm"&gt;
  &lt;video style="width: 80%; margin-left: 10%; border: 2px solid #aaaaff;"&gt;
    &lt;source src="https://akkartik.name/images/20240220-rabbot.webm" /&gt;
  &lt;/video&gt;
&lt;/a&gt;
&lt;div class="btw" style="text-align: right;"&gt;
  video; 25 seconds
&lt;/div&gt;</description><author>Kartik Agaram</author><pubDate>Tue, 20 Feb 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/rabbot</guid></item><item><title>2024–02–17: New U-Boot build for Pinephone Pro</title><link>https://xnux.eu/log/#098</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 17 Feb 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#098</guid></item><item><title>KDA Tools</title><link>https://jodavaho.io/posts/kda-tools-hunt-showdown.html</link><description>&lt;p&gt;I play &lt;a href="https://www.huntshowdown.com/"&gt;hunt showdown&lt;/a&gt; a lot. It&amp;rsquo;s very fun.
It&amp;rsquo;s also insanely frustrating sometimes. The game has long matches, very
frantic, quick battles, and a wide variety of meaningful character
specialization and equipment options. It can take dozens of matches to
determine if a loadout is worth it, and there are many loadouts, and a match
takes an hour &amp;hellip; In short, it &lt;em&gt;is very hard to get feedback on what equipment
loadouts, tactics, or friends are useful&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Furthermore, it&amp;rsquo;s hard to know if you&amp;rsquo;re doing well &lt;em&gt;lately&lt;/em&gt; or doing better
&lt;em&gt;with this loadout&lt;/em&gt; or &lt;em&gt;just play worse with your friend Justin&lt;/em&gt;. Luckily, I
was once paid to solve these problems for real, so let&amp;rsquo;s solve them for fun.&lt;/p&gt;
&lt;p&gt;This is the first of likely a bunch of posts on: designing a logging system
(pen and paper), defining a math problem that will help us answer general
questions, then writing some Rust to solve it. (Rust is not important here,
it&amp;rsquo;s just what I play with when I&amp;rsquo;m making CLI tools). This is also my first
longer-form post, &lt;em&gt;and&lt;/em&gt; my first real entry in a series, so I&amp;rsquo;m going to have
to come back and re-edit this until I get my writing habits in order.
Finally, I am way behind on my &lt;em&gt;actual&lt;/em&gt; writing, so this became a
procrastination mechanism. But for now, &lt;em&gt;caveat emptor&lt;/em&gt; and enjoy!&lt;/p&gt;
&lt;p&gt;Skip to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://jodavaho.io/posts/kda-tools-hunt-showdown.html#step-1-get-the-data"&gt;getting the data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jodavaho.io/posts/kda-tools-hunt-showdown.html#step-2-form-the-problem"&gt;form the problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jodavaho.io/posts/kda-tools-hunt-showdown.html#step-3-make-the-program"&gt;make the program&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;or just &lt;a href="https://jodavaho.io/posts/kda-tools-hunt-showdown.html#final-product"&gt;go see the final product&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But first, if you appreciate this kind of content or would like to see me take
on something else, let me know! You can reach me at &lt;a href="mailto:hello@jodavaho.io"&gt;&lt;a href="mailto:hello@jodavaho.io"&gt;hello@jodavaho.io&lt;/a&gt;&lt;/a&gt;. &lt;div class="support-box"&gt;
  
 
 
&lt;/div&gt;
&lt;/p&gt;
&lt;h2 id="step-1-get-the-data"&gt;Step 1 Get the data&lt;/h2&gt;
&lt;p&gt;The first step in any statistical analysis program is data gathering!
Unfortunately, Hunt Showdown does not support a vibrant modding community, and
uses encrypted telemetry, and so rather than get &lt;em&gt;super&lt;/em&gt; technogeek here, we&amp;rsquo;ll
just write down a few key stats for each game.  This is ok, there are not
many, and they are all available on the summary screen at the end of a match.&lt;/p&gt;
&lt;p&gt;What we want to be able to do is say &amp;ldquo;Every time &lt;em&gt;this happened&lt;/em&gt;, what seemed
to happen a lot as well?&amp;rdquo;. For example &amp;ldquo;Every time I carried &lt;em&gt;this gun&lt;/em&gt;, did I
&lt;em&gt;get more kills&lt;/em&gt;?&amp;rdquo; Or, &amp;ldquo;When I play with Justin, do we kill more bosses?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So you have to actually write down everything you want to measure.&lt;/p&gt;
&lt;p&gt;I kept a log on paper, writing down the date, the match number and the following things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kills&lt;/li&gt;
&lt;li&gt;Deaths&lt;/li&gt;
&lt;li&gt;Assists&lt;/li&gt;
&lt;li&gt;Bosses killed&lt;/li&gt;
&lt;li&gt;Bounties extracted&lt;/li&gt;
&lt;li&gt;Main weapon (what weapon did I bring in)&lt;/li&gt;
&lt;li&gt;Secondary weapon (what secondary)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;d be suprised if it Itook me 30s to scribble this down. Later I kept a text editor open,
alt-tabbed, and recorded one match per line as:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2021-10-11 k:3 d:1 b:2 a
2021-10-11 d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see a clear date, three kills, 1 death, two bounties extracted, and an
assist. What a game! Then, of course, an immediate wipe. Hunt giveth, hunt taketh
away.&lt;/p&gt;
&lt;p&gt;By the way, this format is something I call &amp;ldquo;key value
counts&amp;rdquo;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. You can define any format you want, as long as you write a parser.&lt;/p&gt;
&lt;p&gt;We should also record &amp;ldquo;loadouts&amp;rdquo;. I did often recorded actual weapons, or just
&amp;ldquo;types&amp;rdquo;. For simplicity let&amp;rsquo;s assume I named a few loadouts that worked well
together, like &lt;code&gt;sniper&lt;/code&gt;, &lt;code&gt;shotgun&lt;/code&gt;, &lt;code&gt;rifle&lt;/code&gt;, &lt;code&gt;carbine&lt;/code&gt; etc. So the log should
look like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2021-10-11 k:3 d:1 b:2 a sniper
2021-10-11 d shotgun
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Later, I got curious about some additional &amp;ldquo;meta stats&amp;rdquo;. You can record these
too! You&amp;rsquo;ll be able to correlate any variable later.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ambushes (how many times did we set up a fight on an unaware opponent because we knew where they were going)&lt;/li&gt;
&lt;li&gt;Raids (how many times did we rush an established defender, even if they were under attack already)&lt;/li&gt;
&lt;li&gt;Defends (how many times did we fight off attackers while defending a bounty)&lt;/li&gt;
&lt;li&gt;Wipes (how many times did we wipe another team)&lt;/li&gt;
&lt;li&gt;Loss (how many times were we wiped)&lt;/li&gt;
&lt;li&gt;Who I played with&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img alt="A hunt-showdown actuary, maybe analyzing raid data? (credit: Midjourney)" src="https://jodavaho.io/img/hunt-showdown-actuary.png" /&gt;&lt;figcaption&gt;
			&lt;p&gt;A hunt-showdown actuary, maybe analyzing raid data? (credit: Midjourney)&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="step-2-form-the-problem"&gt;Step 2: Form the problem&lt;/h2&gt;
&lt;p&gt;This is a classic treatment effect calculation. We want to know if a treatment
(&amp;ldquo;Carry this gun instead of any other&amp;rdquo;) has an effect (&amp;ldquo;KDA rises or more bosses
killed&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;So how do we model this? The punchline is I chose to assume that kills, deaths,
bounties, etc, accumulated over time, were Poisson Processes&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;That is, we assume that in the K-D-A-B space of all games, the number of kills,
deaths, assists, and bounties &lt;em&gt;per game&lt;/em&gt;, were essentially Poisson Random
Variables&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;. The KDAB values are assumed independent &lt;em&gt;of eachother&lt;/em&gt;, but not
independnet of &lt;em&gt;other variables&lt;/em&gt; like loadout.&lt;/p&gt;
&lt;h4 id="poisson-random-variables"&gt;Poisson Random Variables&lt;/h4&gt;
&lt;p&gt;Poisson Random Variables have a single parameter of interest, \(\lambda\). That
\(\lambda\) is basically the expected number of times the &amp;ldquo;event&amp;rdquo; (like kills)
should occur each &amp;ldquo;round&amp;rdquo;. &lt;em&gt;Given&lt;/em&gt; a \(\lambda\) you can calculate a
distribution of possible event counts, each with a likelihood, for a given
round. That just means we can plot how likely 3 kills was given we have an
average number of kills per round of 2. We can &lt;em&gt;also&lt;/em&gt; calculate how likeley
getting &lt;em&gt;more than 3&lt;/em&gt; kills was, given we usually get 2.&lt;/p&gt;
&lt;p&gt;If you average two kills per game, here&amp;rsquo;s the distribution of kills you&amp;rsquo;d
usually see:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Poisson Distribution: Probability of seeing x events, given you usually see \(\lambda=3\) events per round" src="https://jodavaho.io/img/poisson-mu3.png" /&gt;&lt;figcaption&gt;
			&lt;p&gt;Poisson Distribution: Probability of seeing x events, given you usually see \(\lambda=3\) events per round&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img alt="Poisson Distribution: Probability of seeing x events, given you average \(\lambda=1.2\) events per round" src="https://jodavaho.io/img/poisson-mu1.2.png" /&gt;&lt;figcaption&gt;
			&lt;p&gt;Poisson Distribution: Probability of seeing x events, given you average \(\lambda=1.2\) events per round&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;How can we use that? Well, we can slice the data! Suppose you wanted to compare
kills when you use &lt;code&gt;sniper&lt;/code&gt;. You can calculate the value of
\(\lambda\) given all the games with &lt;code&gt;sniper&lt;/code&gt; (\(\lambda_\text{sniper}\)), versus
the \(\lambda\) for all games overall,  and then ask &amp;ldquo;are these equal&amp;rdquo;?  Since
the best estimate of \(\lambda\) for a given distribution is just the &lt;em&gt;average
rate observed&lt;/em&gt;, we have &amp;hellip;&lt;/p&gt;
$$
\begin{aligned}
\lambda_\text{sniper} &amp;= \frac{\text{sum of all kills during sniper matches}}{\text{number of sniper matches}} \\\
\lambda_\text{all} &amp;= \frac{\text{sum of all kills during all }}{\text{number of matches}} \\\
\end{aligned}
$$&lt;p&gt;We&amp;rsquo;d like to test&lt;/p&gt;
$$
\begin{equation}
\lambda_\text{sniper} \stackrel{?}{&gt;} \lambda_\text{all}
\tag{1}
\label{test}
\end{equation}
$$&lt;p&gt;You could just compare them. Of course, this is not enough to say whether
they are unequal&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt;. Random variables are usually unequal by virtue of being
random. What we want to know is, are they unequal &lt;em&gt;more than would be
expected&lt;/em&gt;? That is, &amp;ldquo;do we believe the choice of loadout was a differentiating
factor on K, D, A, or B values&amp;rdquo;.&lt;/p&gt;
&lt;h4 id="poisson-statistical-tests"&gt;Poisson Statistical Tests&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s recall some statistics. We can transform Eq \eqref{test} to&lt;/p&gt;
$$
\begin{equation}
 \frac{\lambda_\text{sniper}}{ \lambda_\text{all} } \stackrel{?}{&gt;}  1
\tag{2}
\label{ratiotest}
\end{equation}
$$&lt;p&gt;This could be  a &amp;ldquo;simple-simple likelihood ratio test&amp;rdquo;&lt;sup id="fnref:5"&gt;&lt;a class="footnote-ref" href="#fn:5"&gt;5&lt;/a&gt;&lt;/sup&gt;, meaning that the
distribution is known for both cases, and we want to know if the \(\lambda\)
parameter of the distributions are the same or not. We assume they are, and
want to see if we have sufficient difference to determine that was a bad
assumption.&lt;/p&gt;
&lt;p&gt;It is &lt;em&gt;extremely&lt;/em&gt; common to do the following, given a test like Eq \eqref{ratiotest}.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Assume they are equal (defined as the &amp;ldquo;null hypothesis&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;Contort the equations until they are combined into a &amp;rsquo;test statistic'&lt;/li&gt;
&lt;li&gt;Show that your &amp;rsquo;test statistic&amp;rsquo; has a nice distribution, which you can
analytically bound by a value \(p\).&lt;/li&gt;
&lt;li&gt;Calculate when your &amp;rsquo;test statistic&amp;rsquo; is &amp;lsquo;out of bounds&amp;rsquo;, i.e., your &amp;ldquo;\(p\)
value&amp;rdquo; is too small, meaning you&amp;rsquo;ve observed something likely unexplained
by the null hypothesis.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While the definition given in the prior paragraph appeared in 1933, it is
suprisingly hard to find well-explained test statistics in literature for this
type of test.&lt;sup id="fnref:6"&gt;&lt;a class="footnote-ref" href="#fn:6"&gt;6&lt;/a&gt;&lt;/sup&gt;. But, some suprisingly recent results were helpful. Following
&lt;a href="#Gu2008"&gt;[Gu2008]&lt;/a&gt;
 and &lt;a href="#Ng2005"&gt;[Ng2005]&lt;/a&gt;
, we find a bunch of ways of doing steps 1-4 for Poisson
ratios.&lt;sup id="fnref:7"&gt;&lt;a class="footnote-ref" href="#fn:7"&gt;7&lt;/a&gt;&lt;/sup&gt;, and I just chose Section 2.4 in Gu&amp;rsquo;s paper, which is a bit of lovely prose that looks like:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="lovely&amp;hellip;" src="https://jodavaho.io/img/Gu2008-2.4.png" /&gt;&lt;figcaption&gt;
			&lt;p&gt;lovely&amp;hellip;&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h5 id="sidebar-future-work-on-kda-experiment-design"&gt;Sidebar: Future work on KDA experiment design&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;My absolutely favorite sub-subject in statistics is Experiment Design. I &lt;a href="https://josh.vanderhook.info/thesis.html"&gt;wrote
a whole thesis on it&lt;/a&gt;. Experiment
design is concerned with the question: &amp;ldquo;How many measurements should I make to
ensure that the statistical test will pass if there is an effect?&amp;rdquo; This is of
critical importance for budgetting trials, but also for bounding the time
required to track targets using robots.&lt;/p&gt;
&lt;p&gt;For &lt;em&gt;this&lt;/em&gt; project, I will eventually sit down an add code that will tell you
how many more games you should play to determine if the loadout is useful.
&lt;a href="#Ng2005"&gt;[Ng2005]&lt;/a&gt;
 &lt;em&gt;has&lt;/em&gt; these values in closed form for some test
statistics we could be using here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But for now, time to write some code.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="A hunter unlocking the magic of mathematics. Credit: Midjourney" src="https://jodavaho.io/img/hunt-showdown-stats-magic.png" /&gt;&lt;figcaption&gt;
			&lt;p&gt;A hunter unlocking the magic of mathematics. Credit: Midjourney&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="step-3-make-the-program"&gt;Step 3 Make the program&lt;/h2&gt;
&lt;p&gt;I am using the &lt;a href="https://www.rust-lang.org/"&gt;rust programming language&lt;/a&gt;. Rust
is great. It&amp;rsquo;s like C, which I love, but also comes with a ton of modern
niceties. I hate most of what people make &lt;em&gt;for&lt;/em&gt; Rust (i.e., bloated libraries),
but that&amp;rsquo;s not the language&amp;rsquo;s fault.&lt;/p&gt;
&lt;p&gt;As much as I&amp;rsquo;d love to walk through the detailed programming, it&amp;rsquo;s just too
much for one post. But I can present the high level steps and libraries used,
and hope to write more on this later. The code is available online&lt;sup id="fnref:8"&gt;&lt;a class="footnote-ref" href="#fn:8"&gt;8&lt;/a&gt;&lt;/sup&gt;, and
is easily installed with &lt;code&gt;cargo install kda-tools&lt;/code&gt; or by installing using
&lt;code&gt;dpkg&lt;/code&gt; as noted in docs.&lt;/p&gt;
&lt;p&gt;First, we want code to read the game journal, above. If you don&amp;rsquo;t have one,
here&amp;rsquo;s a sample journal from my actual play history.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2021-03-12 BAR+Scope pistol K K B alone
2021-03-12 BAR+Scope pistol K D D jb
2021-03-12 Short-Rifle Short-Shotgun K D jb
2021-03-12 BAR+Scope pistol D jp+jb
2021-03-13 BAR+Scope pistol jp D
2021-03-13 BAR+Scope pistol jp B D D A A
2021-03-13 Shotgun pistol jp D
2021-03-13 BAR+Scope pistol jp K
2021-03-14 Short-Rifle akimbo  alone
2021-03-17 LAR Sil pistol  alone
2021-03-17 pistol-stock akimbo  alone
2021-03-17 Short-Shotgun pistol-stock  alone
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As mentioned, this is KVC&lt;sup id="fnref:9"&gt;&lt;a class="footnote-ref" href="#fn:9"&gt;9&lt;/a&gt;&lt;/sup&gt; format. So &lt;code&gt;cargo add kvc&lt;/code&gt;. The
documentation&lt;sup id="fnref:10"&gt;&lt;a class="footnote-ref" href="#fn:10"&gt;10&lt;/a&gt;&lt;/sup&gt; contains a simple function &lt;code&gt;read_kvc_line_default&lt;/code&gt; which
returns a vector of keys and counts as tuples. So you only need to read each
line, parse, and we have our data!&lt;/p&gt;
&lt;p&gt;To conduct a test, you want to go over the data, add up all rows &lt;em&gt;with&lt;/em&gt; the
factor of interest, and then using all rows as &lt;em&gt;baseline&lt;/em&gt;. Then, calculate
\(\lambda_\text{h1}\) for the interesting ones, and calculate \(\lambda_\text{h0}\)
as the &amp;ldquo;null&amp;rdquo; hypothesis using all data, and plug into the above equation.
Let&amp;rsquo;s do that for the practice data.&lt;sup id="fnref:11"&gt;&lt;a class="footnote-ref" href="#fn:11"&gt;11&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;baseline&amp;rdquo; kills / match for the practice data above is a total of 5 Kills (&lt;code&gt;sumH0=5&lt;/code&gt;) in 12 matches (&lt;code&gt;nH0=12&lt;/code&gt;), for a value of \(\lambda_\text{h0}=5/12\).&lt;/p&gt;
&lt;p&gt;Suppose we want to test &amp;ldquo;BAR+Scope&amp;rdquo; gives more kills than usual.&lt;/p&gt;
&lt;p&gt;Partitioning the data, we see:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2021-03-12 BAR+Scope pistol K K B alone
2021-03-12 BAR+Scope pistol K D D jb
2021-03-12 BAR+Scope pistol D jp+jb
2021-03-13 BAR+Scope pistol jp D
2021-03-13 BAR+Scope pistol jp B D D A A
2021-03-13 BAR+Scope pistol jp K
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For a total of 4 kills &lt;code&gt;(sumH1=4)&lt;/code&gt; in 6 matches (&lt;code&gt;nH1=6&lt;/code&gt;). (\(\lambda=2/3\))&lt;/p&gt;
&lt;p&gt;We now need to plug these into the equations above. Luckily, there&amp;rsquo;s also code for that&lt;sup id="fnref:12"&gt;&lt;a class="footnote-ref" href="#fn:12"&gt;12&lt;/a&gt;&lt;/sup&gt;, which provides the function &lt;code&gt;two_tailed_rates_equal&lt;/code&gt;, the example&lt;sup id="fnref:13"&gt;&lt;a class="footnote-ref" href="#fn:13"&gt;13&lt;/a&gt;&lt;/sup&gt; is straightforward.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-rust"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; p &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; two_tailed_rates_equal(sumH1, nH1, sumH0, nH0);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;//or
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; p &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; two_tailed_rates_equal(&lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;, &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;, &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;, &lt;span style="color: #ae81ff;"&gt;12&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this case we get a conclusive answer, which is &lt;code&gt;p=0.05&lt;/code&gt;! This tells use the
probability of observing \(\lambda=4/6\) given my usual playing record of 5 kills
every 12 matches is very low. Meaning, something had a measureable effect!&lt;/p&gt;
&lt;p&gt;The code is relatively straightfoward, but only if you closely follow the paper&lt;sup id="fnref:14"&gt;&lt;a class="footnote-ref" href="#fn:14"&gt;14&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;You can reproduce these steps with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;$ cat test | kda-explore &lt;span style="color: #e6db74;"&gt;'K: BAR+Scope'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Processed. Read: &lt;span style="color: #ae81ff;"&gt;12&lt;/span&gt; rows and &lt;span style="color: #ae81ff;"&gt;18&lt;/span&gt; variables
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Varibables found:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Date K alone pistol BAR+Scope B D jb Short-Rifle Short-Shotgun jp+jb jp A Shotgun akimbo LAR Sil pistol-stock 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Debug: processing: K: BAR+Scope
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;met    grp        n     M     rate  ~n    ~M    ~rate p     notes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      BAR+Scope  &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     0.67  &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     0.17  0.05
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice how we had to tell it &lt;code&gt;K: BAR+Scope&lt;/code&gt; which means &amp;ldquo;Test kills / match
using BAR+Scope vs normal performance&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Now, there&amp;rsquo;s no reason you would necessarily have to prompt the user for &amp;ldquo;What
would you like to know&amp;rdquo;. Given we just have to add up some variables and plug
into an equation, we can do this &lt;em&gt;really fast&lt;/em&gt; so why not just test everything?&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what I ended up doing. The program &lt;code&gt;kda-explore&lt;/code&gt; will output the following on this journal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;$ cat journal | kda-explore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Processed. Read: &lt;span style="color: #ae81ff;"&gt;12&lt;/span&gt; rows and &lt;span style="color: #ae81ff;"&gt;18&lt;/span&gt; variables
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Varibables found:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Date B alone K BAR+Scope pistol jb D Short-Rifle Short-Shotgun jp+jb jp A Shotgun akimbo LAR Sil pistol-stock 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Debug: processing: K D A : all
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;No matches found without grouping &lt;span style="color: #e6db74;"&gt;'K'&lt;/span&gt;, this test is useless. Skipping!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;met    grp            n     M     rate  ~n    ~M    ~rate p     notes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      BAR+Scope      &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     0.67  &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     0.17  0.05
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      jb             &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     1.00  &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.30  0.08
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      B              &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     1.00  &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.30  0.08
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      pistol         &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     0.50  &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     0.25  0.35
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      Short-Shotgun  &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.50  &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.40  0.78
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      Short-Rifle    &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.50  &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.40  0.78
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      alone          &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     0.40  &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;     0.43  0.91
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      pistol-stock   &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.50  0.74
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      akimbo         &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.50  0.74
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      Sil            &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.45  0.73
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      LAR            &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.45  0.73
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      Shotgun        &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.45  0.73
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      A              &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.45  0.73
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      jp+jb          &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.45  0.73
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      D              &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     0.33  &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     0.50  0.53
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;K      jp             &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     0.25  &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     0.50  0.35
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;No matches found without grouping &lt;span style="color: #e6db74;"&gt;'D'&lt;/span&gt;, this test is useless. Skipping!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;met    grp            n     M     rate  ~n    ~M    ~rate p     notes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      BAR+Scope      &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     1.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     0.33  0.04
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      jb             &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     1.50  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.50  0.05
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      A              &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     2.00  &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.55  0.05
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      pistol         &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     0.88  &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     0.25  0.05
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      jp             &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     1.00  &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     0.50  0.17
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      B              &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     1.00  &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.60  0.40
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      Shotgun        &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     1.00  &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.64  0.57
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      jp+jb          &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     1.00  &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.64  0.57
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      K              &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     0.75  &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     0.62  0.73
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      Sil            &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.73  0.97
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      LAR            &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.73  0.97
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      Short-Shotgun  &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.50  &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.70  0.64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      Short-Rifle    &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.50  &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.70  0.64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      pistol-stock   &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.80  0.40
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      akimbo         &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.80  0.40
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;D      alone          &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;     1.14  0.01
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;No matches found without grouping &lt;span style="color: #e6db74;"&gt;'B'&lt;/span&gt;, this test is useless. Skipping!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;No matches found without grouping &lt;span style="color: #e6db74;"&gt;'BAR+Scope'&lt;/span&gt;, this test is useless. Skipping!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;No matches found without grouping &lt;span style="color: #e6db74;"&gt;'pistol'&lt;/span&gt;, this test is useless. Skipping!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;No matches found without grouping &lt;span style="color: #e6db74;"&gt;'D'&lt;/span&gt;, this test is useless. Skipping!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;No matches found without grouping &lt;span style="color: #e6db74;"&gt;'jp'&lt;/span&gt;, this test is useless. Skipping!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;No matches found without grouping &lt;span style="color: #e6db74;"&gt;'A'&lt;/span&gt;, this test is useless. Skipping!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;met    grp            n     M     rate  ~n    ~M    ~rate p     notes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      K              &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;     0.25  0.74
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      pistol-stock   &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.20  0.66
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      akimbo         &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.20  0.66
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      Short-Shotgun  &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.20  0.66
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      Short-Rifle    &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.20  0.66
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      jb             &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;    0.20  0.66
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      alone          &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;     0.29  0.48
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      Sil            &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.18  0.33
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      LAR            &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.18  0.33
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      Shotgun        &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.18  0.33
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;A      jp+jb          &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;     0.00  &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;     &lt;span style="color: #ae81ff;"&gt;11&lt;/span&gt;    0.18  0.33
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see it complains &amp;ldquo;This test is useless&amp;rdquo;, when it cannot find that an
event had occured. This is a corner case in how the test statistic is
calculated. To get around that, and allow arbitrary tests, we can use other
more modern (e.g., computationally painful) methods. That&amp;rsquo;s another one for
next time.&lt;/p&gt;
&lt;h2 id="final-product"&gt;Final Product&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s recap. First, you need to keep a journal of matches. Then, you can either calculate the \(\lambda\) values from that journal, or write some tools to do it for you. If you&amp;rsquo;d like to use my tools, they are here: (see &lt;a href="https://github.com/jodavaho/kda-tools"&gt;github.com/jodavaho/kda-tools&lt;/a&gt; )&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kda-summary&lt;/code&gt; will summarize your K, D, and A values (and the usual KDA metric) over the entire journal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kda-compare&lt;/code&gt; will run will look at the whole dataset and tell you if you&amp;rsquo;re doing significantly differently with different loadouts.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kda-explore&lt;/code&gt; will allow &lt;em&gt;you&lt;/em&gt; to look at the conditional distributions &lt;em&gt;of anything&lt;/em&gt; with and without &lt;em&gt;anything else&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="get-for-debian--wsl"&gt;Get for debian / WSL&lt;/h3&gt;
&lt;p&gt;If you have &lt;code&gt;cargo&lt;/code&gt; (&lt;code&gt;apt install cargo&lt;/code&gt;), then &lt;code&gt;cargo install kda-tools&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Otherwise, just grab one of the test debs in releases/  For example &lt;a href="https://github.com/jodavaho/kda-tools/blob/main/releases/kda-tools_1.3.0_amd64.deb"&gt;1.3.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then, in bash/cli or &lt;a href="https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux"&gt;wsl&lt;/a&gt;,&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo dpkg -i kda-tools_1.3.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve talked about how we can model Hunt Showdown statistics as Poisson Random
Variables, how we can formulate a test to see how well we do with certain
loadouts, and provided links to code which allows you to automated those tests.&lt;/p&gt;
&lt;p&gt;I will, in the future, write up more on these tools, including discussing how
non-analytical results are possible, how we can measure more than just Kills or
Bounties (e.g., KDA), and how we can automate the whole thing. I&amp;rsquo;ll also dive
into the code more, to provide examples of how it works.&lt;/p&gt;
&lt;p&gt;Next, we&amp;rsquo;ll go over a more powerful tool &lt;code&gt;kda-compare&lt;/code&gt;, which does automated testing of more than just things you can count. For example, it&amp;rsquo;ll measure &amp;ldquo;KDA&amp;rdquo; (i.e., \(\frac{K+A}{D}\)) and &amp;ldquo;Bounties per death&amp;rdquo; \(\frac{B}{D}\), which existing analytical methods cannot do (to the best of my knowledge).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;$ &amp;lt;journal.txt kda-compare
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Processed. Read: &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; rows and &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt; variables
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;[====================================================]&lt;/span&gt; 100.00 % 2696.14/s 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;met    grp      n/d      val   N     n/d      ~val  M     p
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;kda    Sniper   5/1      5.00   &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;    2/2      1.00   &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;    0.06
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;kda    JP       2/0      inf    &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;    5/3      1.67   &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;    0.49
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;kda    Shotgun  2/2      1.00   &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;    5/1      5.00   &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;    0.77
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;kda    JB       2/2      1.00   &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;    5/1      5.00   &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;    0.78
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;b/d    Sniper   1/1      1.00   &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;    1/2      0.50   &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;    0.24
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;b/d    Shotgun  1/2      0.50   &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;    1/1      1.00   &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;    0.69
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;b/d    JB       2/2      1.00   &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;    0/1      0.00   &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;    NaN
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;b/d    JP       0/0      NaN    &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;    2/3      0.67   &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;    NaN
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Just a teaser: The first row is &lt;code&gt;met grp ...&lt;/code&gt;
These are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the metric name (e.g., kda or bounties / death b/d)&lt;/li&gt;
&lt;li&gt;item group (grp)&lt;/li&gt;
&lt;li&gt;value counts (n)&lt;/li&gt;
&lt;li&gt;deaths (d)&lt;/li&gt;
&lt;li&gt;the value of the metric &amp;lsquo;val&amp;rsquo; &lt;em&gt;with&lt;/em&gt; the grp&lt;/li&gt;
&lt;li&gt;number of matches where &amp;lsquo;grp&amp;rsquo; was used (N)&lt;/li&gt;
&lt;li&gt;value counts &lt;em&gt;without&lt;/em&gt; the grp (n)&lt;/li&gt;
&lt;li&gt;death counts &lt;em&gt;without&lt;/em&gt; the grp (d)&lt;/li&gt;
&lt;li&gt;the value of the metric &amp;lsquo;val&amp;rsquo; &lt;em&gt;without&lt;/em&gt; the grp&lt;/li&gt;
&lt;li&gt;number of matches &lt;em&gt;without&lt;/em&gt; the grp (M)&lt;/li&gt;
&lt;li&gt;and the probability that we&amp;rsquo;d randomly see that &amp;lsquo;val&amp;rsquo; given the distribution of the metric without the grp.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="references"&gt;References&lt;/h1&gt;
&lt;p&gt;&lt;a id="Ng2005"&gt;Ng2005&lt;/a&gt;
 Ng, Hon Keung Tony, and Man‐Lai Tang. &amp;ldquo;Testing the equality of two Poisson means using the rate ratio.&amp;rdquo; Statistics in medicine 24, no. 6 (2005): 955-965.&lt;/p&gt;
&lt;p&gt;&lt;a id="Ractliffe1964"&gt;Ractliffe1964&lt;/a&gt;
: Ractliffe, J. F. &amp;ldquo;The significance of the difference between two Poisson variables: an experimental investigation.&amp;rdquo; Applied Statistics (1964): 84-86.&lt;/p&gt;
&lt;p&gt;&lt;a id="Gu2008"&gt;Gu2008&lt;/a&gt;
: Gu, Kangxia, Hon Keung Tony Ng, Man Lai Tang, and William R. Schucany. &amp;ldquo;Testing the ratio of two poisson rates.&amp;rdquo; Biometrical Journal: Journal of Mathematical Methods in Biosciences 50, no. 2 (2008): 283-298.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;See &lt;a href="https://github.com/jodavaho/kvc"&gt;github.com/jodavaho/kvc&lt;/a&gt; for more information on key-value-counts&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Poisson Process: &lt;a href="https://en.wikipedia.org/wiki/Poisson_point_process"&gt;https://en.wikipedia.org/wiki/Poisson_point_process&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Poisson random varaibles: &lt;a href="https://en.wikipedia.org/wiki/Poisson_distribution"&gt;https://en.wikipedia.org/wiki/Poisson_distribution&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;For example, getting 1, 2, 2 kills with shotgun (avg: 5/6) vs 2, 2, 2 kills with snipers (avg: 6/6) &lt;em&gt;is&lt;/em&gt; different, but not &lt;em&gt;different enough to care about&lt;/em&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:4"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Likelihood Ratio Test: &lt;a href="https://en.wikipedia.org/wiki/Likelihood-ratio_test#"&gt;https://en.wikipedia.org/wiki/Likelihood-ratio_test#&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:5"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;maybe a better statistician could have derived them?&amp;#160;&lt;a class="footnote-backref" href="#fnref:6"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;I really should have followed &lt;a href="#Ng2005"&gt;[Ng2005]&lt;/a&gt;
, but live and learn&amp;#160;&lt;a class="footnote-backref" href="#fnref:7"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;&lt;a href="https://github.com/jodvaho/kda-tools"&gt;github.com/jodavaho/kda-tools&lt;/a&gt; or &lt;code&gt;cargo install kda-tools&lt;/code&gt; which pulls &lt;a href="https://crates.io/crates/kda-tools"&gt;crates.io/crates/kda-tools&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:8"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:9"&gt;
&lt;p&gt;&lt;a href="https://github.com/jodavaho/kvc"&gt;kvc on github.com/jodavaho/kvc&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:9"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:10"&gt;
&lt;p&gt;&lt;a href="https://docs.rs/kvc/latest/kvc/"&gt;kvc on docs.rs&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:10"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:11"&gt;
&lt;p&gt;The use of \(H0\) to represent the null hypothesis and \(H1\) to represent the &amp;ldquo;stuff that may have changed things&amp;rdquo; is mostly historical.&amp;#160;&lt;a class="footnote-backref" href="#fnref:11"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:12"&gt;
&lt;p&gt;&lt;code&gt;poisson-rate-test&lt;/code&gt; &lt;a href="https://crates.io/crates/poisson-rate-test/1.2.2"&gt;crates.io&lt;/a&gt; or &lt;a href="https://docs.rs/poisson-rate-test/latest/poisson_rate_test/"&gt;docs.rs&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:12"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:13"&gt;
&lt;p&gt;&lt;code&gt;two_tailed_rates_equal&lt;/code&gt; documentation on &lt;a href="https://docs.rs/poisson-rate-test/latest/poisson_rate_test/fn.two_tailed_rates_equal.html"&gt;docs.rs&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:13"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:14"&gt;
&lt;p&gt;See &lt;code&gt;one_tailed&lt;/code&gt; code on &lt;a href="https://docs.rs/poisson-rate-test/latest/src/poisson_rate_test/lib.rs.html#190-250"&gt;poisson_rate_test/lib.rs lines 190-250&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:14"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Thu, 15 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/kda-tools-hunt-showdown.html</guid></item><item><title>Blockchain: Earth has a gambling problem</title><link>https://jodavaho.io/posts/blockchain-is-just-gambling.html</link><description>&lt;blockquote&gt;
&lt;p&gt;All blockchain games are predicated on guessing random numbers in exchange for money. How is this not gambling?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Did you know that two percent of USA&amp;rsquo;s energy is just bitcoin mining?&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This is dumb. Blockchain is automated gambling. I don&amp;rsquo;t mean &amp;ldquo;I&amp;rsquo;m gambling the cost of bitcoin will go up, so I buy some&amp;rdquo; I mean the entire system is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Guess a number&lt;/li&gt;
&lt;li&gt;Plug it into an equation, and see if the output matches a random number generated by another equation. The only difference between this coin or that is how the equations work.&lt;/li&gt;
&lt;li&gt;if it matches, you win some &amp;lsquo;coin&amp;rsquo;! The number you guessed is &amp;lsquo;proof&amp;rsquo; you earned the coin. Now you can sell it to someone who thinks this is valuable, and they&amp;rsquo;ll give you money&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(essentially, at an allegory level).&lt;/p&gt;
&lt;p&gt;And for that, we run giant data centers and burn coal in third world countries&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt; to keep doing trillions of guesses per second to earn a little coin.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&amp;ldquo;Two percent of the US&amp;rsquo;s electricity generation now goes to bitcoin&amp;rdquo; &lt;a href="https://arstechnica.com/science/2024/02/over-2-percent-of-the-uss-electricity-generation-now-goes-to-bitcoin/"&gt;https://arstechnica.com/science/2024/02/over-2-percent-of-the-uss-electricity-generation-now-goes-to-bitcoin/&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Hey, it&amp;rsquo;s cool that &amp;ldquo;selling&amp;rdquo; means you change the equations of how the random numbers are generated, but &lt;em&gt;so what&lt;/em&gt;? It doesn&amp;rsquo;t change the fundamental nature of gambling how you record who won.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;It happens here too: &lt;a href="https://www.theverge.com/2024/2/6/24063773/the-epa-just-fined-a-bitcoin-mining-plant-for-violating-coal-ash-rules"&gt;https://www.theverge.com/2024/2/6/24063773/the-epa-just-fined-a-bitcoin-mining-plant-for-violating-coal-ash-rules&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Mon, 12 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/blockchain-is-just-gambling.html</guid></item><item><title>On Software Engineering</title><link>https://jodavaho.io/lists/list-research.html</link><description>&lt;h2 id="reading"&gt;Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://quiltro.org/notes/simplicity.html"&gt;https://quiltro.org/notes/simplicity.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;mastering programming &lt;a href="https://tidyfirst.substack.com/p/mastering-programming"&gt;https://tidyfirst.substack.com/p/mastering-programming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://matthewrocklin.com/index.html"&gt;https://matthewrocklin.com/index.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="contribute"&gt;Contribute&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/scipopt/russcip"&gt;https://github.com/scipopt/russcip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GregoireHENRY/rust-spice"&gt;https://github.com/GregoireHENRY/rust-spice&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="writing"&gt;Writing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://graphics.stanford.edu/~seander/bithacks.html"&gt;http://graphics.stanford.edu/~seander/bithacks.html&lt;/a&gt; Bithacks!&lt;/li&gt;
&lt;/ul&gt;</description><author>jodavaho.io</author><pubDate>Mon, 12 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/lists/list-research.html</guid></item><item><title>Persona 4 Golden review</title><link>https://burakku.com/blog/persona-4-golden-review/</link><description>&lt;p&gt;&lt;img alt="Persona 4 Golden" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Everyone’s favourite Scooby-Doo game on the PlayStation Vita.&lt;/p&gt;
&lt;p&gt;Y(o)u're a Japanese youth, kicked from the big city into the boonies, when shit kicks off and people start dying mysteriously in an otherwise quiet small town – and the only one who can reach out to the truth is you and your merry gang of superpowered high-school students.&lt;/p&gt;
&lt;p&gt;A role-playing game lives or dies by its story and thankfully the story in Persona 4 Golden is good. Not maybe the most clever or through-provoking one, especially for supposedly being a murder mystery, but still a good experience. Granted, I knew the general gist of the story since I’ve played through Persona 4 on the PlayStation 2 over a decade ago. But despite knowing what generally will happen, it was still a fun time reading through. And since it’s been a while since I last read through it, and I’d already forgotten some parts of the Persona 4 story, I could experience some things with “fresh” eyes, which was also nice. I imagine any players without any knowledge about Persona 4 would enjoy it even further.&lt;/p&gt;
&lt;p&gt;The chemistry between the character in the story is also good. By the end of the game, it really feels like you’ve made unbreakable bonds with your social links. I also enjoyed the humorous slice of life events that didn’t carry the story forwards. It’s just a fairly good time in general.&lt;/p&gt;
&lt;p&gt;Most of the game is spent living the life of the most popular and proactive student in Japan by improving yourself and socialising with your friends and neighbours. These day-to-day activities can be quite overwhelming, as you might have half a dozen citizens of Inaba vying for your attention at any given moment. It does very much feel like there is not enough time to get everything done within a single playthrough, and I did actually fail to fully nurture two social links in a single playthrough. However, it’s also somehow so incredibly addicting for reasons completely oblivious to me. At times I found it hard to stop playing even though I really had to.&lt;/p&gt;
&lt;p&gt;This addictive gameplay also extends to the actual dungeon crawling part of the game: it’s really fun and at times it was hard to put down my Steam Deck to go do other things. Maybe the only thing I have against it after Persona 5 is that the battle interface feels clunky and outdated. Not to a point where it would’ve stopped me from playing, but definitely had some more friction in the beginning. I also know that some people do not like the dungeons in Persona 4, but I actually didn’t have a problem with them. Most of them have pretty nice designs and soundtracks, so I can’t hate them, and since they’re randomly generated, you can’t just memorise and blast through them.&lt;/p&gt;
&lt;p&gt;I can’t really remember exactly how all of the aspects in the original Persona 4 worked, but Golden does feel a lot more polished than the original. Easier too, which is seemingly a thing that always happens when they re-release any of the Persona games. However, the mechanics still feel quite stiff to all of the modernisations they stuck into Persona 5, so I definitely recommend people interested in the Persona series to start off with Persona 4 Golden instead of Persona 5 or Persona 5 Royal. The transition from P4G to P5R is definitely smoother than going in reverse.&lt;/p&gt;
&lt;p&gt;Not completely sure about the third semester / epilogue though. It seems kinda of an unnecessary addition, and mainly revolves around a character who seems quite forcibly added to an existing and complete story and one that you’ll probably struggle to care about. That being said, it’s not exactly long to get through, so it kinda slipped past me anyway.&lt;/p&gt;
&lt;p&gt;Unfortunately, Persona 4 Golden is showing its age in the graphics department though. We’re talking about a PlayStation 2 game that was polished up for the portable PlayStation Vita and is then rendered at a slightly higher resolution on the PC. That being said, it’s still absolutely fine. The art style does make up for a lot of the low fidelity and the 2D sprites are ageless. Still, the 15-year patina is another reason I’d recommend people to start off with Persona 4 Golden before moving onto flashier titles like Persona 5 Royal or Persona 3 Reload. It’s not like Persona games have any kind of interconnected stories, so you can freely choose what order you play them in.&lt;/p&gt;
&lt;p&gt;Thankfully the soundtrack still absolutely holds up. Never has there been a Persona game with a bad soundtrack and you are definitely going to be listening to the soundtrack outside of the game too. Guaranteed.&lt;/p&gt;
&lt;p&gt;And as you might expect from a PlayStation Vita title, this game feels tailor-made for the Steam Deck. The gameplay really feels at home on a platform where you can suspend and resume the game as you please. The only thing that gives off the feeling that it’s not a native title for the device are these small freezes that occur here and there. I’m talking like a half second freeze for half an hour of gameplay. Small hiccups, not exactly something to worry about. Otherwise the performance is rock-solid, even if I did often limit the game to 60 FPS instead of the possible 90 FPS (which was smooth and solid) for some battery life improvements. There’s even proper Steam Deck button graphics in the game!&lt;/p&gt;
&lt;p&gt;Actually, the game looks downright silly on a 27-inch monitor, and really shows how this game was designed as a portable title. All of the user interface elements look XXL-sized and the low-fidelity assets are really laid out bare. I also can’t get the vibrant colours and inky blacks on my PC monitor as I could with my OLED Steam Deck screen, which made it feel like I was playing it on a supersized PlayStation Vita. Granted, I do have a pretty  awful PC monitor, but so do a lot of people. I’d maybe even go as far as to recommend this game a little bit less if you cannot play it on a Steam Deck. Not that a Steam Deck is absolutely necessary – but I’d argue that it’s the best way to experience this game. Persona 4 Golden was a PlayStation Vita must-have, and now it’s a Steam Deck must-have.&lt;/p&gt;
&lt;p&gt;I must also confession something: even though I had Persona 4 Golden on my Vita for the longest time, I never actually managed to finish it there. Kind of a PlayStation Vita sin, not finishing one of its greatest hits. I guess I was saving it for when I needed a portable time-killer and never actually got to the end despite starting the adventure. But maybe that was for the best – since I think that the ultimate way of enjoying this game is not on the poor old PlayStation Vita, but rather on the Steam Deck.&lt;/p&gt;
&lt;p&gt;Persona 4 Golden is, despite its obvious veteran age, still a fantastic and fun game and it should probably be the first Persona game for newcomers for the series. An absolute must-have gem of a game.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 12 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/persona-4-golden-review/</guid></item><item><title>A grab bag of articles I saved</title><link>https://jodavaho.io/lists/list-meta.html</link><description>&lt;h1 id="computer-science-blogs"&gt;Computer Science Blogs&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gavinhoward.com/"&gt;https://gavinhoward.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.benkuhn.net/"&gt;https://www.benkuhn.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://danluu.com/"&gt;https://danluu.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vorpus.org"&gt;https://vorpus.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/"&gt;https://jvns.ca/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eta.st/"&gt;https://eta.st/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="quality-sites"&gt;Quality sites&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/"&gt;https://news.ycombinator.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lobste.rs/"&gt;https://lobste.rs/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hackaday.com/"&gt;https://hackaday.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.lesswrong.com/"&gt;https://www.lesswrong.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="humanity"&gt;Humanity&lt;/h1&gt;
&lt;p&gt;Why you, personally, should want a larger population&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;link: &lt;a href="https://rootsofprogress.org/why-a-larger-population"&gt;https://rootsofprogress.org/why-a-larger-population&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;description: Various reasons for above, including faster progress, more opportunities or options, deeper patterns (agglomeration effects)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="learning"&gt;Learning&lt;/h1&gt;
&lt;p&gt;Mathematics for the adventurous self-learner&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;link: &lt;a href="https://www.neilwithdata.com/mathematics-self-learner"&gt;https://www.neilwithdata.com/mathematics-self-learner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A set of books to read / work through to learn math&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="meta-skills"&gt;Meta-skills&lt;/h1&gt;
&lt;p&gt;All strength comes from repetition (of the basics)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;link: &lt;a href="https://dariusforoux.com/repetition/"&gt;https://dariusforoux.com/repetition/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The biggest mistake you can make is to ignore the basics in your profession.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;“Long-term, we must begin to build our internal strengths. It isn’t just skills like computer technology. It’s the old-fashioned basics of self-reliance, self-motivation, self-reinforcement, self-discipline, self-command.” Steven Pressfield&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ten Simple Rules for Lifelong Learning, According to Hamming&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;link: &lt;a href="https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1004020"&gt;https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1004020&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;docid: &lt;a href="https://doi.org/10.1371/journal.pcbi.1004020"&gt;https://doi.org/10.1371/journal.pcbi.1004020&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rule 1. Cultivate Lifelong Learning as a “Style of Thinking” That Concentrates on Fundamental Principles Rather Than on Facts&lt;/li&gt;
&lt;li&gt;Rule 2. Structure Your Learning to Ride the Information Tsunami Rather Than Drown in It&lt;/li&gt;
&lt;li&gt;Rule 3. Be Prepared to Compete and Interact with a Greater and More Rapidly Increasing Number of Scientists Than at Any Time in the Past&lt;/li&gt;
&lt;li&gt;Rule 4. Focus on the Future but Don’t Ignore the Past&lt;/li&gt;
&lt;li&gt;Rule 5. Look for the Personal Angle &amp;ldquo;In that vein, we may infuse our learning with personal details, which some scientists publish.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Rule 6. Learn from the Successes of Others&lt;/li&gt;
&lt;li&gt;Rule 7. Use Trial and Error to Find the Style of Learning That Suits You&lt;/li&gt;
&lt;li&gt;Rule 8. No Matter How Much Advice You Get and How Much Talent You Possess, It Is Still You Who Must Do the Learning and Put in the Time&lt;/li&gt;
&lt;li&gt;Rule 9. Have a Vision to Give You a General Direction&lt;/li&gt;
&lt;li&gt;Rule 10. Make Your Life Count: Struggle for Excellence&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;As Hamming says, because “there are so many ways of being wrong and so few of being right, studying successes is more efficient, and furthermore, when your turn comes you will know how to succeed rather than how to fail.” In addition, he notes that “vicarious learning from the experiences of others saves making errors yourself” - Hamming RW (1997) The Art of Doing Science and Engineering—Learning to Learn. Amsterdam: Gordon and Breach Science Publishers. pmid:25165803&lt;/p&gt;
&lt;/blockquote&gt;</description><author>jodavaho.io</author><pubDate>Sun, 11 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/lists/list-meta.html</guid></item><item><title>An ongoing list of business, leadership, or management resources</title><link>https://jodavaho.io/lists/list-leadership.html</link><description>&lt;h2 id="leadership-and-teams"&gt;Leadership and Teams&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Turn the Ship Around &lt;a href="https://davidmarquet.com/turn-the-ship-around-book/"&gt;homepage&lt;/a&gt;, &lt;a href="https://www.amazon.com/Turn-Ship-Around-Building-Breaking/dp/0241250943/"&gt;amazon&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Peopleware: Productive Projects and Teams &lt;a href="https://www.amazon.com/Peopleware-Productive-Projects-Tom-DeMarco/dp/0932633439"&gt;amazon&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Related: &amp;ldquo;Let me tell you a secret&amp;rdquo; &lt;a href="https://blog.glyph.im/2024/02/let-me-tell-you-a-secret.html"&gt;it&amp;rsquo;s the people&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Five Dysfunctions of a Team &lt;a href="https://www.runn.io/blog/5-dysfunctions-of-a-team-summary"&gt;summary&lt;/a&gt;, &lt;a href="https://www.amazon.com/Five-Dysfunctions-Team-Leadership-Fable/dp/8126522747/"&gt;amazon&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;High output management &lt;a href="https://www.amazon.com/High-Output-Management-Andrew-Grove/dp/0679762884"&gt;amazon&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Army Leadership Manual &lt;a href="https://armypubs.army.mil/epubs/DR_pubs/DR_a/ARN18529-ADP_6-22-000-WEB-1.pdf"&gt;pdf&lt;/a&gt;, &lt;a href="https://jodavaho.io/media/pdf/army.pdf"&gt;cached local copy&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No Bullshit CTO /Tech lead guide &lt;a href="https://vadimkravcenko.com/technical-manager-guide/"&gt;web&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to start google &lt;a href="https://paulgraham.com/google.html"&gt;web&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description><author>jodavaho.io</author><pubDate>Sun, 11 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/lists/list-leadership.html</guid></item><item><title>Command line heaven</title><link>https://jodavaho.io/lists/list-cli.html</link><description>&lt;h2 id="tools"&gt;Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://asciinema.org/"&gt;asciinema&lt;/a&gt;: Record and share your terminal sessions, the right way.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="web-services"&gt;Web services&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wttr.in/"&gt;wttr.in&lt;/a&gt;: The right way to check the weather from your terminal.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qrenco.de/"&gt;qrenco.de&lt;/a&gt;: Generate QR codes from the command line.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheat.sh/"&gt;cheat.sh&lt;/a&gt;: The right way to get help from the command line.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rate.sx/"&gt;rate.sx&lt;/a&gt;: The right way to get currency exchange rates from the terminal.&lt;/li&gt;
&lt;/ul&gt;</description><author>jodavaho.io</author><pubDate>Sun, 11 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/lists/list-cli.html</guid></item><item><title>Conferences, Journals, Etc</title><link>https://jodavaho.io/lists/list-research.html</link><description>&lt;h2 id="multi-agent-systems"&gt;Multi-agent systems&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AAMAS &lt;a href="https://dl.acm.org/doi/proceedings/10.5555/3545946"&gt;proceedings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="planning--scheduling"&gt;Planning &amp;amp; Scheduling&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ICAPS &lt;a href="https://www.icaps-conference.org/"&gt;conf page&lt;/a&gt;, &lt;a href="https://www.icaps-conference.org/proceedings/"&gt;proceedings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="robotics"&gt;Robotics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ICRA &lt;a href="https://www.ieee-ras.org/conferences-workshops/fully-sponsored/icra"&gt;conf page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;IROS &lt;a href="https://www.ieee-ras.org/conferences-workshops/financially-co-sponsored/iros"&gt;conf page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="space-related"&gt;Space related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MASSpace &lt;a href="https://mas-space.github.io/aamas2024ws/#cfp"&gt;&amp;lsquo;24 conf page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="linux--oss"&gt;Linux &amp;amp; OSS&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Linux Foundation &lt;a href="https://events.linuxfoundation.org/"&gt;events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open Source Summit &lt;a href="https://events.linuxfoundation.org/open-source-summit-north-america/"&gt;events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Scale &lt;a href="https://www.socallinuxexpo.org/"&gt;homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;FOSSDEM &lt;a href="https://fosdem.org/"&gt;homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DEFCON &lt;a href="https://www.defcon.org/"&gt;homepage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="learning"&gt;Learning&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;RustConf &lt;a href="https://rustconf.com/"&gt;homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CppCon &lt;a href="https://cppcon.org/"&gt;homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PyCon &lt;a href="https://us.pycon.org/"&gt;homepage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>jodavaho.io</author><pubDate>Sun, 11 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/lists/list-research.html</guid></item><item><title>2024–02–11: Small tidbits and magnetometer driver upstreaming</title><link>https://xnux.eu/log/#097</link><author>megi's PinePhone Development Log</author><pubDate>Sun, 11 Feb 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#097</guid></item><item><title>2024 Data Outlook</title><link>https://bytepawn.com/2024-data-outlook.html</link><description>&lt;p&gt;It is the beginning of the year — a good time to reflect on the previous year and make plans for the year ahead. I wrote this document for my team members in 2024 January to kick off the year. This is an abridged version with sensitive content removed. &lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/outlook-2024-1.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 11 Feb 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/2024-data-outlook.html</guid></item><item><title>Falsehoods I used to believe about shoes</title><link>https://blog.steve.fi/falsehoods_i_used_to_believe_about_shoes.html</link><description>&lt;p&gt;Once upon a time I used to divide shoes, boots, and other footwear, into two categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Things that had good soles and a good grip.&lt;/li&gt;
&lt;li&gt;Things that were slippery and treacherous.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nowadays I know better.  When walking on snow, ice, or slush it isn't the sole of the shoe that causes you to slip, slide, and fall.  It is you.&lt;/p&gt;

&lt;p&gt;The shoes do make a difference, which is why people use icebugs (i.e. shows with little nails sticking out) and similar things.  But really it's all about walking "properly":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One leg directly below you.&lt;/li&gt;
&lt;li&gt;Put your feet down flat.&lt;/li&gt;
&lt;li&gt;Shorter strides.&lt;/li&gt;
&lt;li&gt;Horizontal movement is dangerous.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TLDR; be like a penguin.&lt;/p&gt;

&lt;p&gt;Also - I seem to no longer appear on "Planet Debian".  Weird.  A problem for another day.&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Sat, 10 Feb 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/falsehoods_i_used_to_believe_about_shoes.html</guid></item><item><title>The Computer Architecture of AI (in 2024)</title><link>https://specbranch.com/posts/ai-infra/</link><description>&lt;p&gt;Over the last year, as a person with a hardware background, I have heard a lot of complaints about Nvidia's dominance
of the machine learning market and whether I can build chips to make the situation better.  While the amount of money
I would expect it to take is less than
&lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/openai-ceo-sam-altman-seeks-dollar5-to-dollar7-trillion-to-build-a-network-of-fabs-for-ai-chips"&gt;$7 trillion&lt;/a&gt;,
hardware accelerating this wave of AI will be a very tough problem--much tougher than the last wave focused on CNNs--and
there is a good reason that Nvidia has become the leader in this field with few competitors.  While the
inference of CNNs used to be a math problem, the inference of large language models has actually become a computer
architecture problem involving figuring out how to coordinate memory and I/O with compute to get the best performance
out of the system.&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Sat, 10 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/ai-infra/</guid></item><item><title>An intuition for distributed consensus in OLTP systems</title><link>http://notes.eatonphil.com/2024-02-08-an-intuition-for-distributed-consensus-in-oltp-systems.html</link><description>&lt;p&gt;&lt;!-- -*- mode: markdown -*- --&gt;&lt;/p&gt;
&lt;p&gt;Distributed consensus in transactional databases (e.g. etcd or
Cockroach) is a big deal these days. Most often under the hood are
variations of log-based Paxos-like algorithms such as MultiPaxos,
Viewstamped Replication, or Raft. While there are new variations that
come out each year, optimizing for various workloads, these algorithms
are fairly standard and well-understood.&lt;/p&gt;
&lt;p&gt;In fact they are used in so many places, Kubernetes for example, that
even if you don't decide to implement Raft (which is fun and I
encourage it), it seems worth building an intuition for distributed
consensus.&lt;/p&gt;
&lt;p&gt;What happens as you tweak a configuration. What happens as the
production environment changes. Or what to reach for as product
requirements change.&lt;/p&gt;
&lt;p&gt;I've been &lt;a href="https://notes.eatonphil.com/2023-05-25-raft.html"&gt;thinking&lt;/a&gt;
&lt;a href="https://eatonphil.com/2023-ddia.html"&gt;about&lt;/a&gt; the
&lt;a href="https://eatonphil.com/2023-database-internals.html"&gt;basics&lt;/a&gt; of
&lt;a href="https://github.com/eatonphil/raft-rs"&gt;distributed consensus&lt;/a&gt;
recently. There has been a lot to digest and characterize. And I'm
only beginning to get an understanding.&lt;/p&gt;
&lt;p&gt;This post is an attempt to share some of the intuition built up
reading about and working in this space. Originally this post was also
going to end with a walkthrough of my &lt;a href="https://github.com/eatonphil/raft-rs"&gt;most
recent&lt;/a&gt; Raft implementation in
Rust. But I'm going to hold off on that for another time.&lt;/p&gt;
&lt;p&gt;I was fortunate to have a few excellent reviewers looking at versions
of this post: Paul Nowoczynski, Alex Miller, Jack Vanlightly, Daniel
Chia, and Alex Petrov. Thank you!&lt;/p&gt;
&lt;p&gt;Let's start with Raft.&lt;/p&gt;
&lt;h3 id="raft"&gt;Raft&lt;/h3&gt;&lt;p&gt;Raft is a distributed consensus algorithm that allows you to build a
replicated state machine on top of a replicated log.&lt;/p&gt;
&lt;p&gt;A Raft library handles replicating and durably persisting a sequence
(or &lt;i&gt;log&lt;/i&gt;) of commands to at least a majority of nodes in a
cluster. You provide the library a state machine that interprets the
replicated commands. From the perspective of the Raft library,
commands are just opaque byte strings.&lt;/p&gt;
&lt;p&gt;For example, you could build a replicated key-value store out of &lt;code&gt;SET&lt;/code&gt;
and &lt;code&gt;GET&lt;/code&gt; commands that are passed in by a client. You provide a Raft
library state machine code that interprets the Raft log of &lt;code&gt;SET&lt;/code&gt; and
&lt;code&gt;GET&lt;/code&gt; commands to modify or read from an in-memory hashtable. You can
find concrete examples of exactly this replicated key-value store
modeling in &lt;a href="https://notes.eatonphil.com/tags/raft.html"&gt;previous Raft
posts&lt;/a&gt; I've written.&lt;/p&gt;
&lt;p&gt;All nodes in the cluster run the same Raft code (including the state
machine code you provide); communicating among themselves. Nodes elect
a semi-permanent leader that accepts all reads and writes from
clients. (Again, reads and writes are modeled as commands).&lt;/p&gt;
&lt;p&gt;To commit a new command to the cluster, clients send the command to
all nodes in the cluster. Only the leader accepts this command, if
there is currently a leader. Clients retry until there is a leader
that accepts the command.&lt;/p&gt;
&lt;p&gt;The leader appends the command to its log and makes sure to replicate
all commands in its log to followers in the same order. The leader
sends periodic heartbeat messages to all followers to prolong its term
as leader. If a follower hasn't heard from the leader within a period
of time, it becomes a candidate and requests votes from the cluster.&lt;/p&gt;
&lt;p&gt;When a follower is asked to accept a new command from a leader, it
checks if its history is up-to-date with the leader. If it is not, the
follower rejects the request and asks the leader to send previous
commands to bring it up-to-date. It does this ultimately, in the worst
case of a follower that has lost all history, by going all the way
back to the very first command ever sent.&lt;/p&gt;
&lt;p&gt;When a quorum (typically a majority) of nodes has accepted a command,
the leader marks the command as committed and applies the command to
its own state machine. When followers learn about newly committed
commands, they also apply committed commands to their own state machine.&lt;/p&gt;
&lt;p&gt;For the most part, these details are graphically summarized in Figure
2 of the &lt;a href="https://raft.github.io/raft.pdf"&gt;Raft paper&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="availability-and-linearizability"&gt;Availability and linearizability&lt;/h3&gt;&lt;p&gt;Taking a step back, distributed consensus helps a group of nodes, a
cluster, agree on a value. A client of the cluster can treat a value
from the cluster as if the value was atomically written to and read
from a single thread. This property is called
&lt;a href="https://jepsen.io/consistency/models/linearizable"&gt;linearizability&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, with distributed consensus, the client of the cluster has
better availability guarantees from the cluster than if the client
atomically wrote to or read from a single thread. A single thread that
crashes becomes unavailable. But some number &lt;code&gt;f&lt;/code&gt; nodes can crash in a
cluster implementing distributed consensus and still 1) be available
and 2) provide linearizable reads and writes.&lt;/p&gt;
&lt;p&gt;That is: &lt;b&gt;distributed consensus solves the problem of high
availability for a system while remaining linearizable&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;Without distributed consensus you can still achieve high
availability. For example, a database might have two read
replicas. But a client reading from a read replica might get stale
data. Thus, this system (a database with two read replicas) is not
linearizable.&lt;/p&gt;
&lt;p&gt;Without distributed consensus you can also try synchronous
replication. It would be very simple to do. Have a fixed leader and
require all nodes to acknowledge before committing, But the value here
is extremely limited. If a single node in the cluster goes down the
entire cluster is down.&lt;/p&gt;
&lt;p&gt;You might think I'm proposing a strawman. We could simply designate a
permanent leader that handles all reads and writes; and require a
majority of nodes to commit a command before the leader responds to a
client. But in that case, what's the process for getting a lagging
follower up-to-date? And what happens if it is the leader who goes
down?&lt;/p&gt;
&lt;p&gt;Well, these are not trivial problems! And, beyond linearizability that
we already mentioned, these problems are exactly what distributed
consensus solves.&lt;/p&gt;
&lt;h3 id="why-does-linearizability-matter?"&gt;Why does linearizability matter?&lt;/h3&gt;&lt;p&gt;It's very nice, and often even critical, to have a highly available
system that will never give you stale data. And regardless, it's
convenient to have a term for what we might naively think of as the
"correct" way you'd always want to set and get a value.&lt;/p&gt;
&lt;p&gt;So linearizability is a convenient way of thinking about complex
systems, if you can use or build a system that supports it. But it's
not the only consistency approach you'll see in the wild.&lt;/p&gt;
&lt;p&gt;As you increase the guarantees of your consistency model, you tend to
sacrifice performance. Going the opposite direction, some production
systems sacrifice consistency to improve performance. For example, you
might allow stale reads from any node, reading only from local state
and avoiding consensus, so that you can reduce load on a leader and
avoid the overhead of consensus.&lt;/p&gt;
&lt;p&gt;There are formal definitions for lower consistency models, including
sequential and read-your-writes. You can read the &lt;a href="https://jepsen.io/consistency"&gt;Jepsen
page&lt;/a&gt; for more detail.&lt;/p&gt;
&lt;h3 id="best-and-worst-case-scenarios"&gt;Best and worst case scenarios&lt;/h3&gt;&lt;p&gt;A distributed system relies on communicating over the network. The
worse the network, whether in terms of latency or reliability, the
longer it will take for communication to happen.&lt;/p&gt;
&lt;p&gt;Aside from the network, disks can misdirect writes or corrupt data. Or
you could be mounted on a network filesystem such as EBS.&lt;/p&gt;
&lt;p&gt;And processes themselves can crash due to low disk space or the OOM
killer.&lt;/p&gt;
&lt;p&gt;It will take longer to achieve consensus to commit messages these
scenarios. If messages take longer to reach nodes, or if nodes are
constantly crashing, followers will timeout more often, triggering
leader election. And the leader election itself (which also requires
consensus) will also take longer.&lt;/p&gt;
&lt;p&gt;The best case scenario for distributed consensus is where the network
is reliable and low-latency. Where disks are reliable and fast. And
where processes don't often crash.&lt;/p&gt;
&lt;p&gt;TigerBeetle has an incredible &lt;a href="https://sim.tigerbeetle.com/"&gt;visual
simulator&lt;/a&gt; that demonstrates what
happens across ever-worsening environments. While TigerBeetle and this
simulator use Viewstamped Replication, the demonstrated principles
apply to Raft as well.&lt;/p&gt;
&lt;h3 id="what-happens-when-you-add-nodes?"&gt;What happens when you add nodes?&lt;/h3&gt;&lt;p&gt;Distributed consensus algorithms make sure that some minimum number of
nodes in a cluster agree before continuing. The minimum number is
proportional to the total number of nodes in the cluster.&lt;/p&gt;
&lt;p&gt;A typical implementation of Raft for example will require 3 nodes in a
5-node cluster to agree before continuing. 4 nodes in a 7-node
cluster. And so on.&lt;/p&gt;
&lt;p&gt;Recall that the p99 latency for a service is at least as bad as the
slowest external request the service must make. As you increase the
number of nodes you must talk to in a consensus cluster, you increase
the chance of a slow request.&lt;/p&gt;
&lt;p&gt;Consider the extreme case of a 101-node cluster requiring 51 nodes to
respond before returning to the client. That's 51 chances for a slower
request. Compared to 4 chances in a 7-node cluster. The 101-node
cluster is certainly more highly available though! It can tolerate 49
nodes going down. The 7-node cluster can only tolerate 3 nodes going
down. The scenario where 49 nodes go down (assuming they're in
different availability zones) seems pretty unlikely!&lt;/p&gt;
&lt;h3 id="horizontal-scaling-with-distributed-consensus?-not-exactly"&gt;Horizontal scaling with distributed consensus? Not exactly&lt;/h3&gt;&lt;p&gt;All of this is to say that the most popular algorithms for distributed
consensus, on their own, have nothing to do with horizontal scaling.&lt;/p&gt;
&lt;p&gt;The way that horizontally scaling databases like Cockroach or Yugabyte
or Spanner work is by sharding the data, transparent to the
client. Within each shard data is replicated with a dedicated
distributed consensus cluster.&lt;/p&gt;
&lt;p&gt;So, yes, distributed consensus can be a &lt;em&gt;part&lt;/em&gt; of horizontal
scaling. But again what distributed consensus primarily solves is high
availability via replication while remaining linearizable.&lt;/p&gt;
&lt;p&gt;This is not a trivial point to
make. &lt;a href="https://web.archive.org/web/20230327030543/https://etcd.io/docs/v3.2/learning/why/#using-etcd-for-metadata"&gt;etcd&lt;/a&gt;,
&lt;a href="https://web.archive.org/web/20231212132325/https://www.hashicorp.com/resources/operating-and-running-consul-at-scale"&gt;consul&lt;/a&gt;,
and &lt;a href="https://github.com/rqlite/rqlite"&gt;rqlite&lt;/a&gt; are examples of
databases that do not do sharding, only replication, via a single
Raft cluster that replicates all data for the entire system.&lt;/p&gt;
&lt;p&gt;For these databases there is no horizontal scaling. If they support
"horizontal scaling", they support this by doing non-linearizable
(stale) reads. Writes remain a challenge.&lt;/p&gt;
&lt;p&gt;This doesn't mean these databases are bad. They are not. One obvious
advantage they have over Cockroach or Spanner is that they are
conceptually simpler. Conceptually simpler often equates to easier to
operate. That's a big deal.&lt;/p&gt;
&lt;h3 id="optimizations"&gt;Optimizations&lt;/h3&gt;&lt;p&gt;We've covered the basics of operation, but real-world implementations
get more complex.&lt;/p&gt;
&lt;h4 id="snapshots"&gt;Snapshots&lt;/h4&gt;&lt;p&gt;Rather than letting the log grow indefinitely, most libraries
implement snapshotting. The user of the library provides a state
machine and also provides a method for serializing the state machine
to disk. The Raft library periodically serializes the state machine to
disk and truncates the log.&lt;/p&gt;
&lt;p&gt;When a follower is so far behind that the leader no longer has a log
entry (because it has been truncated), the leader transfers an entire
snapshot to the follower. Then once the follower is caught up on
snapshots, the leader can transfer normal log entries again.&lt;/p&gt;
&lt;p&gt;This technique is described in the Raft paper. While it isn't
necessary for Raft to work, it's so important that it is hardly an
optimization and more a required part of a production Raft system.&lt;/p&gt;
&lt;h4 id="batching"&gt;Batching&lt;/h4&gt;&lt;p&gt;Rather than limiting clients of the cluster to submitting only one
command at a time, it's common for the cluster to accept many commands
at a time. Similarly, many commands at a time are submitted to
followers. When any node needs to write commands to disk, it can batch
commands to disk as well.&lt;/p&gt;
&lt;p&gt;But you can go a step beyond this in a way that is completely opaque
to the Raft library. Each opaque command the client submits can &lt;em&gt;also&lt;/em&gt;
contain a batch of messages. In this scenario, only the user-provided
state machine needs to be aware that each command it receives is
actually a batch of messages that it should pull apart and interpret
separately.&lt;/p&gt;
&lt;p&gt;This latter techinque is a fairly trivial way to increase throughput
by an order of magnitude or two.&lt;/p&gt;
&lt;h4 id="disk-and-network"&gt;Disk and network&lt;/h4&gt;&lt;p&gt;In terms of how data is stored on disk and how data is sent over the
network there is obvious room for optimization.&lt;/p&gt;
&lt;p&gt;A naive implementation might store JSON on disk and send JSON over the
network. A slightly more optimized implementation might store binary
data on disk and send binary data over the network.&lt;/p&gt;
&lt;p&gt;Similarly you can swap out your RPC for gRPC or introduce zlib for
compression to network or disk.&lt;/p&gt;
&lt;p&gt;You can swap out synchronous IO for libaio or io_uring or SPDK/DPDK.&lt;/p&gt;
&lt;p&gt;A little tweak I made in my latest Raft implementation was to index
log entries so searching the log was not a linear operation. Another
little tweak was to introduce a page cache to eliminate unnecessary
disk reads. This increased throughput for by an order of magnitude.&lt;/p&gt;
&lt;h4 id="flexible-quorums"&gt;Flexible quorums&lt;/h4&gt;&lt;p&gt;This brilliant &lt;a href="https://arxiv.org/pdf/1608.06696.pdf"&gt;optimization&lt;/a&gt; by
Heidi Howard and co. shows you can relax the quorum required for
committing new commands so long as you increase the quorum required
for electing a leader.&lt;/p&gt;
&lt;p&gt;In an environment where leader election doesn't happen often, flexible
quorums can increase throughput and decrease latency. And it's a
pretty easy change to make!&lt;/p&gt;
&lt;h4 id="more"&gt;More&lt;/h4&gt;&lt;p&gt;These are just a couple common optimizations. You can also read about
&lt;a href="https://www.pingcap.com/blog/optimizing-raft-in-tikv/"&gt;parallel state machine
apply&lt;/a&gt;,
&lt;a href="https://www.pingcap.com/blog/optimizing-raft-in-tikv/"&gt;parallel append to
disk&lt;/a&gt;,
witnesses,
&lt;a href="https://vldb.org/pvldb/vol14/p2203-whittaker.pdf"&gt;compartmentalization&lt;/a&gt;,
and leader leases. TiKV, Scylla, RedPanda, and Cockroach tend to have
public material talking about this stuff.&lt;/p&gt;
&lt;p&gt;There are also a few people I follow who are often reviewing relevant
papers, if they are not producing their own. I encourage you to follow
them too if this is interesting to you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://muratbuffalo.blogspot.com/"&gt;https://muratbuffalo.blogspot.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://charap.co/"&gt;https://charap.co/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brooker.co.za/blog/"&gt;https://brooker.co.za/blog/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://distributed-computing-musings.com/"&gt;https://distributed-computing-musings.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="safety-and-testing"&gt;Safety and testing&lt;/h3&gt;&lt;p&gt;The other aspect to consider is safety. For example, checksums for
everything written to disk and passed over the network; or &lt;a href="https://www.usenix.org/conference/fast18/presentation/alagappan"&gt;being able
to
recover&lt;/a&gt;
from corruption in the log.&lt;/p&gt;
&lt;p&gt;Testing is also a big deal. There are prominent tools like
&lt;a href="https://jepsen.io/"&gt;Jepsen&lt;/a&gt; that check for consistency in the face of
fault injection (process failure, network failure, etc.). But even
Jepsen has its limits. For example, it doesn't test disk failure.&lt;/p&gt;
&lt;p&gt;FoundationDB &lt;a href="https://www.youtube.com/watch?v=4fFDFbi3toc"&gt;made
popular&lt;/a&gt; a number of
testing techniques. And the people behind this testing went on to
build a product, &lt;a href="https://antithesis.com/"&gt;Antithesis&lt;/a&gt;, around deterministic
testing of non-deterministic code while injecting faults.&lt;/p&gt;
&lt;p&gt;And on that topic there's Facebook Experimental's
&lt;a href="https://github.com/facebookexperimental/hermit"&gt;Hermit&lt;/a&gt; deterministic
Linux hypervisor that may help to test complex distributed
systems. However, my experience with it has not been great and the
maintainers do not seem very engaged with other people who have
reported bugs. I'm hopeful for it but we'll see.&lt;/p&gt;
&lt;p&gt;Antithesis and Hermit seem like a boon when half the trouble of
working on distributed consensus implementations is avoiding flakey
tests.&lt;/p&gt;
&lt;p&gt;Another promising avenue is emitting logs during the Raft lifecycle
and validating the logs against a TLA+ spec. Microsoft has such a
project that has &lt;a href="https://github.com/etcd-io/raft/issues/111"&gt;begun to see
adoption&lt;/a&gt; among
open-source Raft implementations.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Everything aside, consensus is expensive. There is overhead to the
entire consensus process. So if you do not need this level of
availability and can settle for some process via backups, it's going
to have lower latency and higher throughput than if it had to go
through distributed consensus.&lt;/p&gt;
&lt;p&gt;If you do need high availability, distributed consensus can be a great
choice. But consider the environment and what you want from your
consensus algorithm.&lt;/p&gt;
&lt;p&gt;Also, while MultiPaxos, Raft, and Viewstamped Replication are some of
the most popular algorithms for distributed consensus, there is a
world beyond. Two-phase commit, ZooKeeper Atomic Broadcast, PigPaxos,
EPaxos, Accord by Cassandra. The world of distributed consensus also
gets especially weird and interesting outside of OLTP systems.&lt;/p&gt;
&lt;p&gt;But that's enough for one post.&lt;/p&gt;
&lt;h3 id="further-reading"&gt;Further reading&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raft.github.io/raft.pdf"&gt;The Raft Paper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ongardie/raft.tla/blob/master/raft.tla"&gt;The Raft TLA+ Spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.stanford.edu/~ouster/cgi-bin/papers/OngaroPhD.pdf"&gt;The Raft Author's PhD Thesis on Raft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dataintensive.net/"&gt;Designing Data-Intensive Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dabeaz.com/raft.html"&gt;David Beazley's Raft Course&lt;/a&gt; if you can get your company to pay for it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a post about building an intuition for distributed consensus in OLTP systems!&lt;br /&gt;&lt;br /&gt;Very grateful to all the folks who reviewed.&lt;a href="https://t.co/wMxUuokKeg"&gt;https://t.co/wMxUuokKeg&lt;/a&gt; &lt;a href="https://t.co/cfY2kdfqak"&gt;pic.twitter.com/cfY2kdfqak&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1755580821476397527?ref_src=twsrc%5Etfw"&gt;February 8, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 08 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-02-08-an-intuition-for-distributed-consensus-in-oltp-systems.html</guid></item><item><title>Best SEO spam 2024 reddit</title><link>https://www.marginalia.nu/log/a_100_reddit_spam/</link><description>&lt;p&gt;One of the great joys of working on a search engine is that you get to reverse engineer SEO spam, and overall study how it evolves over time.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been noticing the search engine spam strategy of adding &amp;lsquo;reddit&amp;rsquo; to page titles for a few years now, but it feels like it&amp;rsquo;s been growing a lot recently. I don&amp;rsquo;t think it&amp;rsquo;s actually &lt;em&gt;working&lt;/em&gt;, but it&amp;rsquo;s so cute that they are trying.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 07 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_100_reddit_spam/</guid></item><item><title>Conscientiousness is (almost) all you need</title><link>https://jodavaho.io/posts/conscientiousness-is-best.html</link><description>&lt;h2 id="why-do-gpa-and-act-scores-matter"&gt;Why do GPA and ACT scores matter?&lt;/h2&gt;
&lt;p&gt;Recently, there&amp;rsquo;s been some pushback against standardized tests and college
entrance exams. It begs the question: What is all this testing good for? After
all, well-to-do families and students can spend more time preparing for these
exams, making them an unfair indicator of &lt;em&gt;wealth&lt;/em&gt; moreso than &lt;em&gt;IQ&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I propose:&lt;/em&gt; They are not a measure of &lt;em&gt;ability&lt;/em&gt; but of rule-following,
self-regulation, and preparadness. If this is true, the fact that you &lt;em&gt;can&lt;/em&gt;
study for these tests isn&amp;rsquo;t a bug, but a feature. If we were measuring IQ or
&amp;ldquo;raw&amp;rdquo; ability for some kind of meritocratic system, we&amp;rsquo;d want a test you could
&lt;em&gt;not&lt;/em&gt; study for.&lt;/p&gt;
&lt;p&gt;So why measure someone&amp;rsquo;s ability (and willingness) to study? Becasue then we&amp;rsquo;re
measuring conscientiousness. And, it turns out &lt;strong&gt;conscientiousness is (almost)
all you need&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="lets-rewind"&gt;Let&amp;rsquo;s rewind&lt;/h3&gt;
&lt;p&gt;Undergraduate GPA predicts lifetime earnings&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, incoming test scores and GPA
are highly predictive of undergraduate GPA&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;, advanced degrees (which
increase earnings), and increased earnings within degrees &lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;, &lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt;. I suggest
these effects are because being a good student aka &amp;ldquo;eating books&amp;rdquo; is correlated
with conscientiousness. They show up to lectures, prepare, and test well. And
while test scores may be influenced by socioeconomic status, once that is
removed, they are &lt;em&gt;still&lt;/em&gt; an excellent predictor of grades&lt;sup id="fnref1:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;And conscientiousness is very highly correlated with lifetime achievement, AND
fufillment &lt;sup id="fnref:5"&gt;&lt;a class="footnote-ref" href="#fn:5"&gt;5&lt;/a&gt;&lt;/sup&gt;. So measuring conscientiousness, and signalling high
conscientiousness is a really good idea.&lt;/p&gt;
&lt;p&gt;IQ is great, but conscientiousness is how you get things done &lt;sup id="fnref:6"&gt;&lt;a class="footnote-ref" href="#fn:6"&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://jodavaho.io/posts/creation-is-divine.html"&gt;creation is our higher purpose&lt;/a&gt;&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9004755/"&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9004755/&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://files.eric.ed.gov/fulltext/ED562860.pdf"&gt;https://files.eric.ed.gov/fulltext/ED562860.pdf&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref1:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;a href="https://mpreiner.medium.com/what-is-the-impact-of-your-high-"&gt;https://mpreiner.medium.com/what-is-the-impact-of-your-high-&lt;/a&gt;&amp;hellip;&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;&lt;a href="https://www.ssa.gov/policy/docs/research-summaries/education"&gt;https://www.ssa.gov/policy/docs/research-summaries/education&lt;/a&gt;&amp;hellip;&amp;#160;&lt;a class="footnote-backref" href="#fnref:4"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3498890/"&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3498890/&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:5"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;&lt;a href="https://docs.iza.org/dp8235.pdf"&gt;https://docs.iza.org/dp8235.pdf&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:6"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Mon, 05 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/conscientiousness-is-best.html</guid></item><item><title>Latent Spoilers</title><link>https://blog.nawaz.org/posts/2024/Feb/latent-spoilers/</link><description>&lt;p&gt;Why are entertainment UIs so&amp;nbsp;bad?&lt;/p&gt;
&lt;p&gt;Here are some&amp;nbsp;examples:&lt;/p&gt;
&lt;p&gt;You&amp;#8217;re watching a recorded tennis match. At the end of the first set,
you hit &amp;#8220;Pause&amp;#8221; to get a snack. The progress indicator pops up and shows
you that you&amp;#8217;re about 50% done with the video. You now …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Sun, 04 Feb 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2024/Feb/latent-spoilers/</guid></item><item><title>Messing about with serious software</title><link>https://jodavaho.io/posts/spice-initial-setup.html</link><description>&lt;h2 id="messing-about-with-serious-software"&gt;Messing about with serious software&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;In which we discuss setting up some spaceflight software from NASA inside a
tiny &lt;a href="https://api.jodavaho.io/s/readme"&gt;webserver&lt;/a&gt;, inside an AWS lambda, and with Rust.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is no reason you&amp;rsquo;d ever want to do this, but I was stuck at home sick and
needed to scratch an itch: &lt;strong&gt;How does one know if the sun is shining on any
particular part of the moon right now?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The solution is very complicated, actually.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is the spot occluded by, e.g., being in a crater?&lt;/li&gt;
&lt;li&gt;How &lt;em&gt;high&lt;/em&gt; above the surface are you? Are you &lt;em&gt;right on&lt;/em&gt; the surface?&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Technically&lt;/em&gt; the sun goes out of line of sight about &lt;a href="https://www.wolframalpha.com/input?i=distance+to+sun+%2F+speed+of+light"&gt;8 minutes&lt;/a&gt; before the light stops hitting the spot - do we take that into account?&lt;/li&gt;
&lt;li&gt;Does the atmosphere bend the light of the sun? Making it visible when it isn&amp;rsquo;t strictly in line of sight?&lt;/li&gt;
&lt;li&gt;What does &amp;ldquo;now&amp;rdquo; mean? Does it mean that when we look at this spot the light is not hitting it (e.g., the reflection of sunlight from that spot is no longer reaching our eyes).&lt;/li&gt;
&lt;li&gt;How do we know where the moon is relative to the sun &lt;a href="https://en.wikipedia.org/wiki/Lunar_standstill"&gt;precisely&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;Doesn&amp;rsquo;t the moon &lt;a href="https://svs.gsfc.nasa.gov/10836"&gt;wobble&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;Suppose I wanted to plan the arrival &lt;a href="https://en.wikipedia.org/wiki/Lunar_standstill"&gt;of a spacecraft that will do distributed sensing&lt;/a&gt;, so now I need to know all this for future dates!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can imagine that someone wanting to point a telescope at a spot on Mars
would want to do similar calculations, lest they take a picture of a
shadow-obscured region, or worse, something in darkness &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://upload.wikimedia.org/wikipedia/commons/1/15/Rilles_and_craters_near_lunar_terminator.jpg" /&gt;&lt;figcaption&gt;
			&lt;h4&gt;Lunar terminator (creative commons)&lt;/h4&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;One of the things I most appreciated about working at
&lt;a href="https://www.jpl.nasa.gov/"&gt;JPL&lt;/a&gt; was being surrounded by smart people who could
think of all these details and solve them. So, I was exposed to
&lt;a href="https://naif.jpl.nasa.gov/naif/"&gt;NAIF&lt;/a&gt; SPICE system. SPICE is a C library for
calculating observer geometry.&lt;/p&gt;
&lt;p&gt;Putting this up into a webservice involved the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Figuring out if SPICE could solve this problem&lt;/li&gt;
&lt;li&gt;Downloading and building the software&lt;/li&gt;
&lt;li&gt;Finding and configuring the kernels (~180MB of them!)&lt;/li&gt;
&lt;li&gt;Using Rust FFI to interact with the right SPICE apis&lt;/li&gt;
&lt;li&gt;Configuring an API Gateway, Lambda, and domain name&lt;/li&gt;
&lt;li&gt;Wrapping the rust code in &lt;a href="https://docs.rs/axum/latest/axum/"&gt;Axum&lt;/a&gt;, and deploying&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;m definitely going to write about each of those steps &amp;hellip; right after I get some sleep&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Actually, taking images of the &lt;a href="https://en.wikipedia.org/wiki/Terminator_(solar)"&gt;terminator&lt;/a&gt; can yield some interesting science opportunities, so now we &lt;em&gt;really&lt;/em&gt; want to know where the sun is!&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Sun, 04 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/spice-initial-setup.html</guid></item><item><title>Understanding Ruby Regexp book announcement</title><link>https://learnbyexample.github.io/understanding-ruby-regexp-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I just published a new version of the &amp;quot;&lt;strong&gt;Understanding Ruby Regexp&lt;/strong&gt;&amp;quot; ebook. Corrected examples and descriptions for Atomic grouping, &lt;code&gt;\G&lt;/code&gt; and &lt;code&gt;\K&lt;/code&gt; features, improved examples, exercises and so on.&lt;/p&gt;
&lt;p&gt;This book will help you learn &lt;strong&gt;Ruby Regular Expressions&lt;/strong&gt; step-by-step from beginner to advanced levels with &lt;strong&gt;hundreds of examples and exercises&lt;/strong&gt;.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="ebook-links"&gt;Ebook links&lt;a class="zola-anchor" href="#ebook-links"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can download the PDF/EPUB versions of the book for free using the below links (you can also pay if you wish):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/rubyregexp"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/rubyregexp"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/Ruby_Regexp/"&gt;https://learnbyexample.github.io/Ruby_Regexp/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Ruby version updated to &lt;strong&gt;3.3.0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Corrected examples and descriptions for Atomic grouping, &lt;code&gt;\G&lt;/code&gt; and &lt;code&gt;\K&lt;/code&gt; features&lt;/li&gt;
&lt;li&gt;In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;Understanding Ruby Regexp&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;li&gt;Images centered for EPUB format&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Why is it needed?&lt;/li&gt;
&lt;li&gt;Regexp introduction&lt;/li&gt;
&lt;li&gt;Anchors&lt;/li&gt;
&lt;li&gt;Alternation and Grouping&lt;/li&gt;
&lt;li&gt;Escaping metacharacters&lt;/li&gt;
&lt;li&gt;Dot metacharacter and Quantifiers&lt;/li&gt;
&lt;li&gt;Interlude: Tools for debugging and visualization&lt;/li&gt;
&lt;li&gt;Working with matched portions&lt;/li&gt;
&lt;li&gt;Character class&lt;/li&gt;
&lt;li&gt;Groupings and backreferences&lt;/li&gt;
&lt;li&gt;Interlude: Common tasks&lt;/li&gt;
&lt;li&gt;Lookarounds&lt;/li&gt;
&lt;li&gt;Modifiers&lt;/li&gt;
&lt;li&gt;Unicode&lt;/li&gt;
&lt;li&gt;Further Reading&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/Ruby_Regexp"&gt;https://github.com/learnbyexample/Ruby_Regexp&lt;/a&gt; for markdown source, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/Ruby_Regexp/issues"&gt;https://github.com/learnbyexample/Ruby_Regexp/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;learn by example.net@gmail.com&lt;/code&gt; (without the spaces)&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 02 Feb 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/understanding-ruby-regexp-announcement/</guid></item><item><title>Supporting scc and my other free software tools</title><link>https://boyter.org/posts/supporting-scc/</link><description>&lt;p&gt;Sometime last year Justin Vincent of the &lt;a href="https://techzinglive.com/"&gt;techzing&lt;/a&gt; podcast suggests that I should add a license to
Quote,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I &amp;ldquo;strongly&amp;rdquo; suggest that you wrap each product into a paid version - where you sell a &amp;ldquo;license&amp;rdquo; for usage with priority support.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;New year means new energy and so I got around to do this. All the tools will remain free software, so there is no need to worry about a rug pull. However it does mean I am now offering proper email and if required face time support for every license sold for a year from the data of purchase.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 31 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/supporting-scc/</guid></item><item><title>Contexts, Friction and Distractions</title><link>https://www.marginalia.nu/log/99_context/</link><description>&lt;p&gt;I get significantly more work done when I unplug my computer from the Internet. It&amp;rsquo;s not that my productive output drops to zero when I&amp;rsquo;m plugged in, but more like 70%.&lt;/p&gt;
&lt;p&gt;Despite many of the tools that I use requiring a connection, and certainly the Internet containing a wealth of information that might expedite my work, these benefits are drastically outweighed by the wealth of distractions also available.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s very appealing, when the code is compiling or the docker containers restarting, to sneak open a browser tab with hacker news, or the Χ formerly known as Twitter, youtube, mastodon, a news site, or something similar to pass those minutes.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 30 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/99_context/</guid></item><item><title>Highfleet Cogitator 1.0 Release</title><link>https://jodavaho.io/posts/highfleet-1.0-release.html</link><description>&lt;p&gt;&lt;img alt="hfopt logo" src="https://jodavaho.io/img/hfopt-nobg.png" /&gt;&lt;/p&gt;
&lt;p&gt;As of today, we&amp;rsquo;re at 1.0 for the Highfleet tools / cogitator site.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hfopt.jodavaho.io"&gt;https://hfopt.jodavaho.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;uploading ships, and getting a permalink for sharing.&lt;/li&gt;
&lt;li&gt;creating collections of ships you have uploaded, which can also be shared&lt;/li&gt;
&lt;li&gt;getting ship design hints from the optimizer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One nice thing is that all downloads are verified to be valid .seria files. We hope you find it useful - especially as a permanent archive of ships!&lt;/p&gt;
&lt;p&gt;We have several improvements on the horizon:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downloading collections (almost ready! for now you can download from a collection individually)&lt;/li&gt;
&lt;li&gt;Ship stats and search&lt;/li&gt;
&lt;li&gt;Stars / &amp;ldquo;save&amp;rdquo;&lt;/li&gt;
&lt;li&gt;many more&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Best part - it&amp;rsquo;s free. It was a passion project for a couple of us, and we wanted to give it to you.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Mon, 29 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/highfleet-1.0-release.html</guid></item><item><title>Charlatans spreading misleading beginner advice are the evolutionary crabs of youtube content creators</title><link>https://www.marginalia.nu/log/98_youtube-crabs/</link><description>&lt;p&gt;You have a hobby you&amp;rsquo;ve been into for a decade or more. You like talking about your hobby, and your friends and family, after listening to these things for as long as you&amp;rsquo;ve been into them, maybe aren&amp;rsquo;t as excited to always hear about it as you are about discussing them, so in an act of compassion you create a youtube channel where you can monologue about your passion instead.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 29 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/98_youtube-crabs/</guid></item><item><title>Security Automation with Gitlab</title><link>https://www.brightball.com/articles/security-automation-with-gitlab</link><description>I recently had the opportunity to speak to DEF CON 864 about the multiple layers of security automation within Gitlab, the open source tools that drive them and how the findings are managed and resolved.</description><author>Brightball Articles</author><pubDate>Sat, 27 Jan 2024 11:38:13 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/security-automation-with-gitlab</guid></item><item><title>Hype</title><link>https://blog.bayindirh.io/blog/hype/</link><description>&lt;p&gt;Hype is a loaded word, both in meaning and energy. It’s moving masses in today’s consumerism oriented world, in every category possible, including technology. Both for consumers and professionals alike.&lt;/p&gt;
&lt;p&gt;Hype is not inherently bad, but after a certain threshold, it’s nauseating and blinding, creating mindless followers and fanboys of the worst kind. When hype reaches these levels, Logical discourse becomes impossible to maintain, or even start. Worse, the effect is addicting, nudging people to follow the latest hype without even understanding the last thing they’re running after.&lt;/p&gt;
&lt;p&gt;As I have written before, I’m a big believer of &lt;a href="https://blog.bayindirh.io/blog/tools-craftsmanship-and-mastery/"&gt;craftsmanship and mastery&lt;/a&gt; and the prefer the benefits it brings over changing tools too often.&lt;/p&gt;
&lt;p&gt;In recent times, I have seen two examples of this over-the-top hype: Rust (the programming language) and generative AI models (GPT family, Dall-E and friends, the usual suspects). The hype is at self-sustaining levels because of what they bring to the table, but the hype around them is pumped up at the same time to increase their influence over their respective ecosystems, disregarding the damage they do to the circles they influence. I will try to analyze them one by one.&lt;/p&gt;
&lt;p&gt;Rust is an interesting programming language. In short, it disallows you to do things which are potentially dangerous from memory safety perspective. The borrow checker is strict, directly halting compilation when something doesn’t fit to the security model at the slightest level. It’s even so strict that, Rust has an escape hatch called &lt;code&gt;unsafe&lt;/code&gt;, which relaxes the security model to allow more things, however borrow checker has no switch. It’s always on. However, people are trying to bypass it for fun and profit. &lt;/p&gt;
&lt;p&gt;The impact of Rust and its borrow checker is huge. You can’t have race conditions in your code, and it’s guaranteed to be memory safe with no leaks, no races and potentially no deadlocks. In essence, Rust’s borrow checker forces you to be mindful about how your data moves, and forces you to design your software around it, making it snug and secure. It increases development time somewhat, and makes you reinvent a couple of wheels in the process since some of your wheels doesn’t fit to Rust as is. That’s OK. Neat even.&lt;/p&gt;
&lt;p&gt;Problem is, the excitement around Rust makes people blind. While not mapping one to one, the problems attacked by Rust are being worked on for a long time, solved with different approaches and trade-offs. However, with the hype, all this effort and other mechanisms and methods become invisible to these people. Moreover, pointing out the existence of other ways is dismissed without even understanding how they work or what they address. Discussions becomes scoffing matches, or worse, flamewars, leaving both ends bitter.&lt;/p&gt;
&lt;p&gt;In my opinion, this drives people who want to understand Rust, but are veterans of other programming languages, out of the Rust ecosystem, because being able to objectively compare things and understand what they bring to the table is an essential part of the craft. Every software development starts with selection of correct tools for the job. Otherwise, your project becomes harder or impossible depending how off you are.&lt;/p&gt;
&lt;p&gt;Another problem with this hype is “Rewrite it in Rust” movement, however it’s more nuanced, and deserves its own, separate blog post in the future.&lt;/p&gt;
&lt;p&gt;There is a similar, if not bigger, hype cycle is happening in the Artificial Intelligence right now. With the advances in the hardware and computing capabilities, a couple of high impact generative models (GPT, DALL-E, Stable Diffusion) are developed and made accessible to the public. Then, everything spread like a wildfire.&lt;/p&gt;
&lt;p&gt;This was in the making for a long time. AI, as a discipline, was not sleeping. Everybody was working hard for decades, but the computing power was not there, and with the power at hand, the barriers were removed and here we are.&lt;/p&gt;
&lt;p&gt;However, the result is not a utopia, but something much more complex and nuanced. What we have needs to be understood and navigated. The developers of these models doesn’t want you see these parts of the story, but points you to the capabilities of these models. Summarizing web pages, analyzing reports, generating code on a whim, seeing a web page and duplicating it... The list goes on and on...&lt;/p&gt;
&lt;p&gt;When one tries to lift the shiny lid, what there is a concoction of questionable things. Scraping of web regardless of how the content is licensed (ranging from copyleft to copyright, and everything in between), regardless of consent of the producers of said content, and doing various mental exercises to fit everything into fair use.&lt;/p&gt;
&lt;p&gt;When it comes to image models, training or fine tuning models with known artists’ style brings another set of problems to the table, since the thing they refined is grabbed out of their hands, and their livelihood is &lt;a href="https://catandgirl.com/4000-of-my-closest-friends/"&gt;being threatened&lt;/a&gt;. Moreover, this progress is even applauded by people making things harder both in the community and mentally for the affected artists. &lt;/p&gt;
&lt;p&gt;Stripping a human being of their authenticity and telling that they are worthless is being derogatory, and traumatizing. Considering the time and effort spent by an artist to perfect their style and technique, this is one of the worst traumas to survive, and is hard to recover. Add in the passion required to endure this torturing path to mastery, and the outcome is pretty clear.&lt;/p&gt;
&lt;p&gt;All in all, training generative models require immense amounts of material, and defending this material falls under fair use requires tons of mental gymnastics. For example, researchers in AI like to defend that their models learn like humans to claim that they’re “reading” the material like a person. However, same researchers claim that the model is not like a human and lacks conscience and other humanly traits, hence they can’t guarantee their models’ outputs’ correctness, or its honesty. As a topper, when pushed enough or correctly, many models can emit their training data verbatim, creating big problems in confidentiality, privacy, consent, and much more.&lt;/p&gt;
&lt;p&gt;Similarly, energy consumption of these models both in training and inference is another problem, which requires its own blog post, because it’s again nuanced and needs space for discussion.&lt;/p&gt;
&lt;p&gt;Point to these problems, and you’ll be again scoffed off, claiming being “anti-progress”, “close minded” and a “coward”. Some people claims that “AI companies are doing something amazing, hence they need no permission” to do what they are doing, and elimination of jobs and commoditization of art and other hard things are equalizing and democratizing.&lt;/p&gt;
&lt;p&gt;Stopping people creating things by traumatizing, alienating, and forcing not to share what have they done is akin to poisoning the well you drink your water, but nobody is listening because of the hype.&lt;/p&gt;
&lt;p&gt;There are other notable events as well, like OpenAI’s recent change of terms for allowing military use, and Eric Schmidt’s AI powered drones company for military applications. I leave these as a “reader’s exercise”, since this post is long enough in its current form.&lt;/p&gt;
&lt;p&gt;Watching the backstage of AI research and engaging in discussions and reading about it is like seeing the sausage factory. This time, it’s way more unpleasant.&lt;/p&gt;
&lt;p&gt;For a list of things discussed about AI, please see &lt;a href="https://notes.bayindirh.io/notes/Lists/Discussions+about+Artificial+Intelligence"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Be aware of the hype, be mindful of what you say and do. Like everything, its overdose is deadly. Both for you and for the people around you.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Sat, 27 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/hype/</guid></item><item><title>Bring Back Short Stories</title><link>https://www.swyx.io/short-stories</link><description>There was a time, before the screens took over, when people read these things called "books". Remarkably efficient things. Always-on display. Could survive dropping from a great height. Somewhat fragile but who cares when you could mass produce thousands of the things for hundreds of dollars.</description><author>swyx's site RSS Feed</author><pubDate>Wed, 24 Jan 2024 02:20:32 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/short-stories</guid></item><item><title>add Whisper to your iOS Action Button</title><link>https://www.swyx.io/add-whisper-to-your-ios-action-button</link><description>The new iPhones ditched the physical mute button with a software enabled action button, that allows some customization. the highest degree of customization is Shortcuts. Apple's default transcription is terrible. I got a new iPhone recently and so this is my chance.</description><author>swyx's site RSS Feed</author><pubDate>Tue, 23 Jan 2024 01:24:18 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/add-whisper-to-your-ios-action-button</guid></item><item><title>How to achieve high performance, and, ratings</title><link>https://bytepawn.com/how-to-achieve-high-performance-and-ratings.html</link><description>&lt;p&gt;I wrote this document for my team members — Data Scientists and Data Engineers — to help them do a better on their annual performance reviews.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Watts-Strogatz" src="/images/performance-lion2.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 21 Jan 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/how-to-achieve-high-performance-and-ratings.html</guid></item><item><title>LLM Assisted Moderation</title><link>https://blog.nawaz.org/posts/2024/Jan/llm-assisted-moderation/</link><description>&lt;p&gt;Moderating online forums is labor intensive. Burnout is common. It&amp;#8217;s
almost a given that if a subreddit gets to a certain size, the quality
&lt;em&gt;will&lt;/em&gt; drop, even with active moderation - moderators simply cannot
handle the&amp;nbsp;scale.&lt;/p&gt;
&lt;p&gt;Here are some ways LLMs can help&amp;nbsp;out.&lt;/p&gt;
&lt;div class="section" id="enforcing-forum-rules"&gt;
&lt;span id="enforce"&gt;&lt;/span&gt;&lt;h2&gt;Enforcing Forum&amp;nbsp;Rules&lt;/h2&gt;
&lt;p&gt;Whenever a …&lt;/p&gt;&lt;/div&gt;</description><author>Beetle Space</author><pubDate>Mon, 15 Jan 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2024/Jan/llm-assisted-moderation/</guid></item><item><title>VA-11 Hall-A review</title><link>https://burakku.com/blog/va-11-hall-a-review/</link><description>&lt;p&gt;&lt;img alt="VA-11 Hall-A" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Time to mix drinks and change lives.&lt;/p&gt;
&lt;p&gt;VA-11 Hall-A is basically a cyberpunk visual novel about a bartender and her clients, with a bartending minigame firmly attached to it. A relatively simple concept really. I'd actually cleared this game once already on the PlayStation Vita, but decided to get another playthrough in it for the yearly Daily VA-11 Hall-A, which I guess is already indicative of the game's quality.&lt;/p&gt;
&lt;p&gt;While there is an underlying story in VA-11 Hall-A, it’s not really a plot game. Instead, it’s more of a character and slice of life game about a bar. And thankfully it does this part quite well. The characters are well written and it does generally feel like people are just trying to live their lives in the cyberpunkian shithole city they happen to live in. And to top it off, you do get a satisfactory jumping off point for the story – with some epilogue stuff sprinkled on top of it if you managed to do your day job properly.&lt;/p&gt;
&lt;p&gt;There is also another strong point for VA-11 Hall-A: the art in this game is lovely. What it maybe lacks in resolution (since the game runs in a 1280x720 window) it more than enough makes up in style and atmosphere. Granted, there’s not really much event CG like you might have in over visual novels, but I don’t think it really matters here. There’s also a satisfying amount of animation for the characters, so it’s definitely an enjoyable visual experience.&lt;/p&gt;
&lt;p&gt;The music is also pretty good, although I do have a problem with it: I don’t like the jukebox mechanic. Why am I in charge of picking the music? Can’t you pick appropriate music for me instead so I don’t have to pick something incredibly inappropriate for whatever worries my clientele carries in through the front door?&lt;/p&gt;
&lt;p&gt;I do also have to criticise the Steam Deck experience too. Even though this game has been released on the PlayStation Vita and the Nintendo Switch, there’s no official controller layout. This means that it’s kind of a pain to play on the Steam Deck, and I couldn’t really find a controller layout that worked nicely. Touch screen controls for the bartending interface didn’t really work either. And even more annoyingly, even though the game advertises Steam Cloud support, it doesn’t sync save data between Windows and Linux versions. This game is far from a Steam Deck verification and anyone thinking of playing this on one should be aware that there are definitely issues here.&lt;/p&gt;
&lt;p&gt;I also have to say that I hate the Model Warrior Julianne minigame and the fact that being it is part of getting all of the achievements. For a while I considered somehow cheating through it but thankfully I managed to eventually get through it by abusing every available cheese. I mean, it is an optional minigame so I can’t hate it too much, but the completionist in me compelled me to get through it and I definitely could’ve lived without it.&lt;/p&gt;
&lt;p&gt;VA-11 Hall-A is a fairly short and simple slice of life bartending game, and an enjoyable one at that. If that sounds even remotely like an interesting concept to you, I have a hard time believing you wouldn’t get your money’s worth even at full price.&lt;/p&gt;
&lt;p&gt;(Just don't get your hopes up for getting more of it, since the sequel seems to be stuck in development hell.)&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 13 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/va-11-hall-a-review/</guid></item><item><title>You Should Have A Virtual Mailbox</title><link>https://www.swyx.io/virtual-mailbox</link><description>If you fit one of these criteria:</description><author>swyx's site RSS Feed</author><pubDate>Wed, 10 Jan 2024 02:18:08 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/virtual-mailbox</guid></item><item><title>Writing a minimal in-memory storage engine for MySQL/MariaDB</title><link>http://notes.eatonphil.com/2024-01-09-minimal-in-memory-storage-engine-for-mysql.html</link><description>&lt;p&gt;&lt;!-- -*- mode: markdown -*- --&gt;&lt;/p&gt;
&lt;p&gt;I &lt;a href="https://eatonphil.com/2024-01-wehack-mysql.html"&gt;spent a week&lt;/a&gt;
looking at MySQL/MariaDB internals along with ~80 other devs. Although
MySQL and MariaDB are mostly the same (more on that later), I focused
on MariaDB specifically this week.&lt;/p&gt;
&lt;p&gt;Before last week I had never built MySQL/MariaDB before. The first day
of this hack week, I got MariaDB building locally and &lt;a href="https://twitter.com/eatonphil/status/1742649922791395501"&gt;made a code
tweak&lt;/a&gt; so
that &lt;code&gt;SELECT 23&lt;/code&gt; returned &lt;code&gt;213&lt;/code&gt;, and &lt;a href="https://twitter.com/eatonphil/status/1742654868085526896"&gt;another
tweak&lt;/a&gt; so
that &lt;code&gt;SELECT 80 + 20&lt;/code&gt; returned &lt;code&gt;60&lt;/code&gt;. The second day I got a &lt;a href="https://twitter.com/eatonphil/status/1742958892957446490"&gt;basic UDF
in C&lt;/a&gt;
working so that &lt;code&gt;SELECT mysum(20, 30)&lt;/code&gt; returned &lt;code&gt;50&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The rest of the week I spent figuring out how to build a minimal
in-memory storage engine, which I'll walk through in this post. 218 lines
of C++.&lt;/p&gt;
&lt;p&gt;It supports &lt;code&gt;CREATE&lt;/code&gt;, &lt;code&gt;DROP&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;,
and &lt;code&gt;SELECT&lt;/code&gt; for tables that only have &lt;code&gt;INTEGER&lt;/code&gt; fields. It is
explicitly not thread-safe because I didn't have time to understand
MariaDB's lock primitives.&lt;/p&gt;
&lt;p&gt;In this post I'll also talk about how the MariaDB custom storage API
compares to the Postgres one, based on &lt;a href="https://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html"&gt;a previous hack week project I
did&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All code for this post can be found in &lt;a href="https://github.com/eatonphil/mariadb/tree/11.4/storage/memem"&gt;my fork on
GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="mysql-and-mariadb"&gt;MySQL and MariaDB&lt;/h3&gt;&lt;p&gt;Before we go further though, why do I keep saying MySQL/MariaDB?&lt;/p&gt;
&lt;p&gt;MySQL is GPL licensed (let's completely ignore the commercial
variations of MySQL that Oracle offers). The code is
open-source. However, the development is done behind closed
doors. There is a code dump &lt;a href="https://github.com/mysql/mysql-server/commits/trunk/"&gt;every
month&lt;/a&gt; or so.&lt;/p&gt;
&lt;p&gt;MariaDB is a fork of MySQL by the creator of MySQL (who is no longer
involved, as it happens). It is also GPL licensed (let's completely
ignore the commercial variations of MariaDB that MariaDB Corporation
offers). The code is open-source. The development is also open-source.&lt;/p&gt;
&lt;p&gt;When you install "MySQL" in your Linux distro you are &lt;a href="https://mariadb.com/newsroom/press-releases/mariadb-replaces-mysql-as-the-default-in-debian-9/"&gt;often
actually&lt;/a&gt;
installing MariaDB.&lt;/p&gt;
&lt;p&gt;The two are mostly compatible. During this week, I &lt;a href="https://twitter.com/eatonphil/status/1742642758408405237"&gt;stumbled
onto&lt;/a&gt; that
they evolved support for &lt;code&gt;SELECT .. FROM VALUES ..&lt;/code&gt; differently. Some
differences are documented on &lt;a href="https://mariadb.com/kb/en/moving-from-mysql/"&gt;the MariaDB
KB&lt;/a&gt;. But this KB is
painful to browse. Which leads me to my next point.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://dev.mysql.com/doc/"&gt;MySQL docs&lt;/a&gt; are excellent. Easy to
read, browse; and they are thorough. The &lt;a href="https://mariadb.com/kb"&gt;MariaDB
docs&lt;/a&gt; are a work in progress. I'm sorry I
can't be stoic: in just a week I've come to really hate using this
KB. Thankfully, in some twisted way, it also doesn't seem to be very
thorough either. It isn't completely avoidable though since there is
no guarantee MySQL and MariaDB do the same thing.&lt;/p&gt;
&lt;p&gt;Ultimately, I spent the week using MariaDB because I'm biased toward
fully open projects. But I kept having to look at MySQL docs, hoping
they were relevant.&lt;/p&gt;
&lt;p&gt;Now that you understand the state of things, let's move on to fun
stuff!&lt;/p&gt;
&lt;h3 id="storage-engines"&gt;Storage engines&lt;/h3&gt;&lt;p&gt;Mature databases often support swapping out the storage layer. Maybe
you want an in-memory storage layer so that you can quickly run
integration tests. Maybe you want to switch between B-Trees
(read-optimized) and LSM Trees (write-optimized) and unordered heaps
(write-optimized) depending on your workload. Or maybe you just want
to try a third-party storage library
(e.g. &lt;a href="https://rocksdb.org/"&gt;RocksDB&lt;/a&gt; or &lt;a href="https://sled.rs/"&gt;Sled&lt;/a&gt; or
&lt;a href="https://tikv.org/"&gt;TiKV&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The benefit of swapping out only the storage engine is that, from a
user's perspective, the semantics and features of the database stay
mostly the same. But the database is magically faster for a workload.&lt;/p&gt;
&lt;p&gt;You keep powerful user management, extension support, SQL support, and
a well-known wire protocol. You modify only the method of storing the
actual data.&lt;/p&gt;
&lt;h4 id="existing-storage-engines"&gt;Existing storage engines&lt;/h4&gt;&lt;p&gt;MySQL/MariaDB is particularly well known for its custom storage engine
support. The MySQL docs for &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/storage-engines.html"&gt;alternate storage
engines&lt;/a&gt;
are great.&lt;/p&gt;
&lt;p&gt;While the docs do warn that you should probably stick with the default
storage engine, that warning didn't quite feel strong enough because
nothing else seemed to indicate the state of other engines.&lt;/p&gt;
&lt;p&gt;Specifically, in the past I was always interested in the CSV storage
engine. But when you look at the &lt;a href="https://github.com/MariaDB/server/blob/11.4/storage/csv/ha_tina.cc"&gt;actual code for the CSV
engine&lt;/a&gt;
there is a pretty strong warning:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;First off, this is a play thing for me, there are a number of things
wrong with it:
  *) It was designed for csv and therefore its performance is highly
     questionable.
  *) Indexes have not been implemented. This is because the files can
     be traded in and out of the table directory without having to worry
     about rebuilding anything.
  *) NULLs and &amp;quot;&amp;quot; are treated equally (like a spreadsheet).
  *) There was in the beginning no point to anyone seeing this other
     then me, so there is a good chance that I haven't quite documented
     it well.
  *) Less design, more &amp;quot;make it work&amp;quot;

Now there are a few cool things with it:
  *) Errors can result in corrupted data files.
  *) Data files can be read by spreadsheets directly.

TODO:
 *) Move to a block system for larger files
 *) Error recovery, its all there, just need to finish it
 *) Document how the chains work.

-Brian
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The difference between the seeming confidence of the docs and seeming
confidence of the contributor made me chuckle.&lt;/p&gt;
&lt;p&gt;The benefit of these diverse storage engines for me was that they give
examples of how to implement the storage engine API. The
&lt;a href="https://github.com/MariaDB/server/blob/11.4/storage/csv"&gt;csv&lt;/a&gt;,
&lt;a href="https://github.com/MariaDB/server/tree/11.4/storage/blackhole"&gt;blackhole&lt;/a&gt;,
&lt;a href="https://github.com/MariaDB/server/tree/11.4/storage/example"&gt;example&lt;/a&gt;,
and &lt;a href="https://github.com/MariaDB/server/tree/11.4/storage/heap"&gt;heap&lt;/a&gt;
storage engines were particularly helpful to read.&lt;/p&gt;
&lt;p&gt;The heap engine is a complete in-memory storage engine. Complete means
complex though. So there seemed to be room for a stripped down version
of an in-memory engine.&lt;/p&gt;
&lt;p&gt;And that's we'll cover in this post! First though I want to talk a
little bit about the limitations of custom storage engines.&lt;/p&gt;
&lt;h3 id="limitations"&gt;Limitations&lt;/h3&gt;&lt;p&gt;While being able to tailor a storage engine to a workload is powerful,
there are limits to the benefits based on the design of the storage
API.&lt;/p&gt;
&lt;p&gt;Both Postgres and MySQL/MariaDB currently have a custom storage API
built around &lt;em&gt;individual rows&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id="column-wise-execution"&gt;Column-wise execution&lt;/h4&gt;&lt;p&gt;I have &lt;a href="https://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html"&gt;previously
written&lt;/a&gt;
that custom storage engines allows you to switch between column- and
row-oriented data storage. Two big reasons to do column-wise storage
are 1) opportunity for compression, and 2) fast operations on a single
column.&lt;/p&gt;
&lt;p&gt;The opportunity for 1) compression &lt;em&gt;on disk&lt;/em&gt; would still exist even if
you needed to deal with individual rows at the storage API layer since
the compression could happen on disk. However any benefits of passing
around compressed columns &lt;em&gt;in memory&lt;/em&gt; disappear if you must convert to
rows for the storage API.&lt;/p&gt;
&lt;p&gt;You'd also lose the advantage for 2) fast operations on a single
column if the column must be converted into a row at the storage API
whereupon it's passed to higher levels that perform execution. The
execution would happen row-wise, not column-wise.&lt;/p&gt;
&lt;p&gt;All of this is to say that while column-wise storage is possible, the
&lt;em&gt;benefit of doing so&lt;/em&gt; is not obvious with the current API design for
both MySQL/MariaDB and Postgres.&lt;/p&gt;
&lt;h4 id="vectorization"&gt;Vectorization&lt;/h4&gt;&lt;p&gt;An API built around individual rows also sets limits on the amount of
vectorization you can do. A custom storage engine could still do some
vectorization under the hood: always filling a buffer with N rows and
returning a row from the buffer when the storage API requests a single
row. But there is likely some degree of performance left on the table
with an API that deals with individual rows.&lt;/p&gt;
&lt;p&gt;Remember though: if you did batched reads and writes of rows in the
custom storage layer, there isn't necessarily any vectorization
happening at the execution layer. From a &lt;a href="https://notes.eatonphil.com/2023-09-21-how-do-databases-execute-expressions.html"&gt;previous
study&lt;/a&gt;
I did, neither MySQL/MariaDB nor Postgres do vectorized query
execution. This paragraph isn't a critique of the storage API, it's
just something to keep in mind.&lt;/p&gt;
&lt;h4 id="storage-versus-execution"&gt;Storage versus execution&lt;/h4&gt;&lt;p&gt;The general point I'm making here is that unless both the execution
and storage APIs are designed in a certain way, you may attempt
optimizations in the storage layer that are ineffective or even
harmfull because the execution layer doesn't or can't take advantage
of them.&lt;/p&gt;
&lt;h4 id="nothing-permanent"&gt;Nothing permanent&lt;/h4&gt;&lt;p&gt;The current limitations of the storage API are not intrinsic aspects
of MySQL/MariaDB or Postgres's design. For both project there used to
be no pluggable storage at all. We can imagine a future patch to
either project that allows support for batched row reads and writes
that together could make column-wise storage and vectorized execution
more feasible.&lt;/p&gt;
&lt;p&gt;Even today there have been invasive attempts to fully support
&lt;a href="https://www.citusdata.com/blog/2021/03/06/citus-10-columnar-compression-for-postgres/"&gt;column-wise storage and
execution&lt;/a&gt;
in Postgres. And there have also been projects to bring &lt;a href="https://github.com/citusdata/postgres_vectorization_test"&gt;vectorized
execution to
Postgres&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I'm not as familiar with the MySQL landscape to comment about efforts
at the moment their.&lt;/p&gt;
&lt;h3 id="debug-build-of-mariadb-running-locally"&gt;Debug build of MariaDB running locally&lt;/h3&gt;&lt;p&gt;Now that you've got some background, let's get a debug build of
MariaDB!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/MariaDB/server&lt;span class="w"&gt; &lt;/span&gt;mariadb
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mariadb
&lt;span class="gp"&gt;$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;build
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;build
&lt;span class="gp"&gt;$ &lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;-DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Debug&lt;span class="w"&gt; &lt;/span&gt;..
&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j8
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This takes a while. When I was hacking on Postgres (a C project), it
took 1 minute on my beefy Linux server to build. It took 20-30 minutes
to build MySQL/MariaDB from scratch. That's C++ for you!&lt;/p&gt;
&lt;p&gt;Thankfully incremental builds of MySQL/MariaDB for a tweak after the
initial build take roughly the same time as incremental builds of
Postgres after a tweak.&lt;/p&gt;
&lt;p&gt;Once the build is done, create a database.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;./build/scripts/mariadb-install-db&lt;span class="w"&gt; &lt;/span&gt;--srcdir&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--datadir&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/db
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And create a config for the database.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;[client]&lt;/span&gt;
&lt;span class="go"&gt;socket=$(pwd)/mariadb.sock&lt;/span&gt;

&lt;span class="go"&gt;[mariadb]&lt;/span&gt;
&lt;span class="go"&gt;socket=$(pwd)/mariadb.sock&lt;/span&gt;

&lt;span class="go"&gt;basedir=$(pwd)&lt;/span&gt;
&lt;span class="go"&gt;datadir=$(pwd)/db&lt;/span&gt;
&lt;span class="go"&gt;pid-file=$(pwd)/db.pid&amp;quot; &amp;gt; my.cnf&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Start up the server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;./build/sql/mariadbd&lt;span class="w"&gt; &lt;/span&gt;--defaults-extra-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/my.cnf&lt;span class="w"&gt; &lt;/span&gt;--debug:d:o,&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/db.debug
&lt;span class="go"&gt;./build/sql/mariadbd: Can't create file '/var/log/mariadb/mariadb.log' (errno: 13 &amp;quot;Permission denied&amp;quot;)&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] Starting MariaDB 11.4.0-MariaDB-debug source revision 3fad2b115569864d8c1b7ea90ce92aa895cfef08 as process 185550&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: !!!!!!!! UNIV_DEBUG switched on !!!!!!!!!&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Compressed tables use zlib 1.2.13&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Number of transaction pools: 1&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Using crc32 + pclmulqdq instructions&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Initializing buffer pool, total size = 128.000MiB, chunk size = 2.000MiB&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Completed initialization of buffer pool&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Buffered log writes (block size=512 bytes)&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: End of log at LSN=57155&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Opened 3 undo tablespaces&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: 128 rollback segments in 3 undo tablespaces are active.&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Setting file './ibtmp1' size to 12.000MiB. Physically writing the file full; Please wait ...&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: File './ibtmp1' size is now 12.000MiB.&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: log sequence number 57155; transaction id 16&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Loading buffer pool(s) from ./db/ib_buffer_pool&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] Plugin 'FEEDBACK' is disabled.&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] Plugin 'wsrep-provider' is disabled.&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] InnoDB: Buffer pool(s) load completed at 240103 17:10:15&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] Server socket created on IP: '0.0.0.0'.&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] Server socket created on IP: '::'.&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] mariadbd: Event Scheduler: Loaded 0 events&lt;/span&gt;
&lt;span class="go"&gt;2024-01-03 17:10:15 0 [Note] ./build/sql/mariadbd: ready for connections.&lt;/span&gt;
&lt;span class="go"&gt;Version: '11.4.0-MariaDB-debug'  socket: './mariadb.sock'  port: 3306  Source distribution&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  With that &lt;code&gt;--debug&lt;/code&gt; flag, debug logs will show up in
  &lt;code&gt;$(pwd)/db.debug&lt;/code&gt;. It's unclear why debug logs are
  treated separately from the console logs shown here. I'd rather them
  all be in one place.
&lt;/p&gt;&lt;p&gt;In another terminal, run a client and make a request!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;./build/client/mariadb&lt;span class="w"&gt; &lt;/span&gt;--defaults-extra-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/my.cnf&lt;span class="w"&gt; &lt;/span&gt;--database&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="go"&gt;Reading table information for completion of table and column names&lt;/span&gt;
&lt;span class="go"&gt;You can turn off this feature to get a quicker startup with -A&lt;/span&gt;

&lt;span class="go"&gt;Welcome to the MariaDB monitor.  Commands end with ; or \g.&lt;/span&gt;
&lt;span class="go"&gt;Your MariaDB connection id is 3&lt;/span&gt;
&lt;span class="go"&gt;Server version: 11.4.0-MariaDB-debug Source distribution&lt;/span&gt;

&lt;span class="go"&gt;Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.&lt;/span&gt;

&lt;span class="go"&gt;Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.&lt;/span&gt;

&lt;span class="go"&gt;MariaDB [test]&amp;gt; SELECT 1;&lt;/span&gt;
&lt;span class="go"&gt;+---+&lt;/span&gt;
&lt;span class="go"&gt;| 1 |&lt;/span&gt;
&lt;span class="go"&gt;+---+&lt;/span&gt;
&lt;span class="go"&gt;| 1 |&lt;/span&gt;
&lt;span class="go"&gt;+---+&lt;/span&gt;
&lt;span class="go"&gt;1 row in set (0.001 sec)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Huzzah! Let's write a custom storage engine!&lt;/p&gt;
&lt;h3 id="where-does-the-code-go?"&gt;Where does the code go?&lt;/h3&gt;&lt;p&gt;When writing an extension for some project, I usually expect to have
the extension exist in its own repo. I was able to do this with the
&lt;a href="https://github.com/eatonphil/pgtam"&gt;Postgres in-memory storage engine I
wrote&lt;/a&gt;. And in general, Postgres
extensions exist as their own repos.&lt;/p&gt;
&lt;p&gt;I was able to create and build a UDF plugin outside the MariaDB source
tree. But when it came to getting a storage engine to build and load
successfully, I wasted almost an entire day (a large amount of time in
a single hack week) getting nowhere.&lt;/p&gt;
&lt;p&gt;Extensions for MySQL/MariaDB are most easily built via the CMake
infrastructure within the repo. Surely there's &lt;em&gt;some&lt;/em&gt; way to replicate
that infrastructure from outside the repo but I wasn't able to figure
it out within a day and didn't want to spend more time on it.&lt;/p&gt;
&lt;p&gt;Apparently the &lt;a href="https://twitter.com/kastauyra/status/1743346665442935174"&gt;normal thing to
do&lt;/a&gt; in
MySQL/MariaDB is to keep extensions within a fork of MySQL/MariaDB.&lt;/p&gt;
&lt;p&gt;When I switched to this method I was able to very quickly get the
storage engine building and loaded. So that's what we'll do.&lt;/p&gt;
&lt;h3 id="boilerplate"&gt;Boilerplate&lt;/h3&gt;&lt;p&gt;Within the MariaDB source tree, create a new folder in the &lt;code&gt;storage&lt;/code&gt;
subdirectory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;storage/memem
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Within &lt;code&gt;storage/memem/CMakeLists.txt&lt;/code&gt; add the following.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# This program is free software; you can redistribute it and/or modify&lt;/span&gt;
&lt;span class="c"&gt;# it under the terms of the GNU General Public License as published by&lt;/span&gt;
&lt;span class="c"&gt;# the Free Software Foundation; version 2 of the License.&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# This program is distributed in the hope that it will be useful,&lt;/span&gt;
&lt;span class="c"&gt;# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;
&lt;span class="c"&gt;# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;/span&gt;
&lt;span class="c"&gt;# GNU General Public License for more details.&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# You should have received a copy of the GNU General Public License&lt;/span&gt;
&lt;span class="c"&gt;# along with this program; if not, write to the Free Software&lt;/span&gt;
&lt;span class="c"&gt;# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1335 USA&lt;/span&gt;

&lt;span class="nb"&gt;SET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;MEMEM_SOURCES&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;ha_memem.cc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ha_memem.h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;MYSQL_ADD_PLUGIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;memem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MEMEM_SOURCES&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;STORAGE_ENGINE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This hooks into MySQL/MariaDB build infrastructure. So next time you
run &lt;code&gt;make&lt;/code&gt; within the &lt;code&gt;build&lt;/code&gt; directory we created above, it will also
build this project.&lt;/p&gt;
&lt;h3 id="the-storage-engine-class"&gt;The storage engine class&lt;/h3&gt;&lt;p&gt;It would be nice to see a way to extend MySQL in C (for one, because
it would then be easier to port to other languages). But all of the
builtin storage methods use classes. So we'll do that too.&lt;/p&gt;
&lt;p&gt;The class we must implement is an instance of
&lt;a href="https://github.com/MariaDB/server/blob/11.4/sql/handler.h#L3200"&gt;&lt;code&gt;handler&lt;/code&gt;&lt;/a&gt;. There
is a single &lt;code&gt;handler&lt;/code&gt; instance per thread, corresponding to a single
running query. (Postgres gives each query its own process, MySQL gives
each query its own thread.) However, &lt;code&gt;handler&lt;/code&gt; instances are reused
across different queries.&lt;/p&gt;
&lt;p&gt;There are a number of virtual methods on &lt;code&gt;handler&lt;/code&gt; we must implement
in our subclass. For most of them we'll do nothing: simply returning
immediately. These simple methods will be implemented in
&lt;code&gt;ha_memem.h&lt;/code&gt;. The methods with more complex logic will be implemented
in &lt;code&gt;ha_memem.cc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's set up includes in &lt;code&gt;ha_memem.h&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cm"&gt;/* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.&lt;/span&gt;

&lt;span class="cm"&gt;  This program is free software; you can redistribute it and/or modify&lt;/span&gt;
&lt;span class="cm"&gt;  it under the terms of the GNU General Public License as published by&lt;/span&gt;
&lt;span class="cm"&gt;  the Free Software Foundation; version 2 of the License.&lt;/span&gt;

&lt;span class="cm"&gt;  This program is distributed in the hope that it will be useful,&lt;/span&gt;
&lt;span class="cm"&gt;  but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;
&lt;span class="cm"&gt;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;/span&gt;
&lt;span class="cm"&gt;  GNU General Public License for more details.&lt;/span&gt;

&lt;span class="cm"&gt;  You should have received a copy of the GNU General Public License&lt;/span&gt;
&lt;span class="cm"&gt;  along with this program; if not, write to the Free Software&lt;/span&gt;
&lt;span class="cm"&gt;  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */&lt;/span&gt;

&lt;span class="cp"&gt;#ifdef USE_PRAGMA_INTERFACE&lt;/span&gt;
&lt;span class="cp"&gt;#pragma interface &lt;/span&gt;&lt;span class="cm"&gt;/* gcc class implementation */&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;thr_lock.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;handler.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;table.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;sql_const.h&amp;quot;&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;memory&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next we'll define structs for our in-memory storage.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MememRow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MememTable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;shared_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MememRow&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;shared_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MememDatabase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;shared_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MememTable&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Within &lt;code&gt;ha_memem.cc&lt;/code&gt; we'll implement a global (not thread-safe)
&lt;code&gt;static MememDatabase*&lt;/code&gt; that all &lt;code&gt;handler&lt;/code&gt; instances will query when
requested. We need the definitions in the header file though because
we'll store the table currently being queried in the &lt;code&gt;handler&lt;/code&gt;
subclass.&lt;/p&gt;
&lt;p&gt;This is so that every call to &lt;code&gt;write_row&lt;/code&gt; to write a single row or
call to &lt;code&gt;rnd_next&lt;/code&gt; to read a single row does not need to look up the
in-memory table object N times within the same query.&lt;/p&gt;
&lt;p&gt;And finally we'll define the subclass of &lt;code&gt;handler&lt;/code&gt; and implementations
of trivial methods.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ha_memem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;shared_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MememTable&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memem_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ha_memem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handlerton&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;hton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLE_SHARE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;table_arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;ha_memem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;index_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ulonglong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;table_flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index_flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all_parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* The following defines can be increased if necessary */&lt;/span&gt;
&lt;span class="cp"&gt;#define MEMEM_MAX_KEY MAX_KEY     &lt;/span&gt;&lt;span class="cm"&gt;/* Max allowed keys */&lt;/span&gt;
&lt;span class="cp"&gt;#define MEMEM_MAX_KEY_SEG 16      &lt;/span&gt;&lt;span class="cm"&gt;/* Max segments for key */&lt;/span&gt;
&lt;span class="cp"&gt;#define MEMEM_MAX_KEY_LENGTH 3500 &lt;/span&gt;&lt;span class="cm"&gt;/* Like in InnoDB */&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;max_supported_keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MEMEM_MAX_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;max_supported_key_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MEMEM_MAX_KEY_LENGTH&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;max_supported_key_part_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MEMEM_MAX_KEY_LENGTH&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_if_locked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;rnd_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;rnd_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;rnd_pos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index_read_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key_part_map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keypart_map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ha_rkey_function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;find_flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_END_OF_FILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index_read_idx_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="n"&gt;key_part_map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keypart_map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ha_rkey_function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;find_flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_END_OF_FILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index_read_last_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="n"&gt;key_part_map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keypart_map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_END_OF_FILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_END_OF_FILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index_prev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_END_OF_FILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index_first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_END_OF_FILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index_last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_END_OF_FILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;external_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;THD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;thd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lock_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;table_arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_CREATE_INFO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;create_info&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;THR_LOCK_DATA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nf"&gt;store_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;THD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;thd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;THR_LOCK_DATA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                             &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;thr_lock_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lock_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;delete_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reset_memem_table&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;write_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;update_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;old_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;new_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_WRONG_COMMAND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;delete_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_WRONG_COMMAND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A complete storage engine might seriously implement all of these
methods. But we'll only seriously implement 7 of them.&lt;/p&gt;
&lt;p&gt;To finish up the boilerplate, we'll switch over to &lt;code&gt;ha_memem.cc&lt;/code&gt; and
set up the includes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cm"&gt;/* Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.&lt;/span&gt;

&lt;span class="cm"&gt;  This program is free software; you can redistribute it and/or modify&lt;/span&gt;
&lt;span class="cm"&gt;  it under the terms of the GNU General Public License as published by&lt;/span&gt;
&lt;span class="cm"&gt;  the Free Software Foundation; version 2 of the License.&lt;/span&gt;

&lt;span class="cm"&gt;  This program is distributed in the hope that it will be useful,&lt;/span&gt;
&lt;span class="cm"&gt;  but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;
&lt;span class="cm"&gt;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;/span&gt;
&lt;span class="cm"&gt;  GNU General Public License for more details.&lt;/span&gt;

&lt;span class="cm"&gt;  You should have received a copy of the GNU General Public License&lt;/span&gt;
&lt;span class="cm"&gt;  along with this program; if not, write to the Free Software&lt;/span&gt;
&lt;span class="cm"&gt;  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */&lt;/span&gt;

&lt;span class="cp"&gt;#ifdef USE_PRAGMA_IMPLEMENTATION&lt;/span&gt;
&lt;span class="cp"&gt;#pragma implementation &lt;/span&gt;&lt;span class="c1"&gt;// gcc: Class implementation&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;

&lt;span class="cp"&gt;#define MYSQL_SERVER 1&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;my_global.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;sql_priv.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;unireg.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;sql_class.h&amp;quot;&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;ha_memem.h&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok! Let's dig into the implementation.&lt;/p&gt;
&lt;h3 id="implementation"&gt;Implementation&lt;/h3&gt;&lt;h4 id="the-global-database"&gt;The global database&lt;/h4&gt;&lt;p&gt;First up, we need to declare a global &lt;code&gt;MememDatabase*&lt;/code&gt; instance. We'll
also implement a helper function for finding the index of a table by
name within the database.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// WARNING! All accesses of `database` in this code are thread&lt;/span&gt;
&lt;span class="c1"&gt;// unsafe. Since this was written during a hack week, I didn't have&lt;/span&gt;
&lt;span class="c1"&gt;// time to figure out MySQL/MariaDB's runtime well enough to do the&lt;/span&gt;
&lt;span class="c1"&gt;// thread-safe version of this.&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MememDatabase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memem_table_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INT_MAX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;c_str&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  As I wrote this post I noticed that this code also assumes there's
  only a single database. That isn't how MySQL works. Everytime you
  call &lt;code&gt;USE ...&lt;/code&gt; in MySQL you are switching between
  databases. You can query tables across databases. A real in-memory
  backend would need to be aware of the different databases, not just
  different tables. But to keep the code succinct we won't implement
  that in this post.
&lt;/p&gt;&lt;p&gt;Next we'll implement plugin initialization and cleanup.&lt;/p&gt;
&lt;h4 id="plugin-lifecycle"&gt;Plugin lifecycle&lt;/h4&gt;&lt;p&gt;Before we register the plugin with MariaDB, we need to set up
initialization and cleanup methods for it.&lt;/p&gt;
&lt;p&gt;The initialization method will take care of initializing the global
&lt;code&gt;MememDatabase* database&lt;/code&gt; object. It will set up a handler for
creating new instances of our &lt;code&gt;handler&lt;/code&gt; subclass. And it will set up a
handler for deleting tables.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;memem_create_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handlerton&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;hton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLE_SHARE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                                     &lt;/span&gt;&lt;span class="n"&gt;MEM_ROOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mem_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ha_memem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memem_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;handlerton&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;memem_hton&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memem_hton&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handlerton&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memem_hton&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DB_TYPE_AUTOASSIGN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memem_hton&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memem_create_handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memem_hton&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;drop_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[](&lt;/span&gt;&lt;span class="n"&gt;handlerton&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memem_table_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_NO_SUCH_TABLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;erase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DBUG_PRINT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;info&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[MEMEM] Deleted table '%s'.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memem_hton&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HTON_CAN_RECREATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Initialize global in-memory database.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MememDatabase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  The &lt;code&gt;DBUG_PRINT&lt;/code&gt; macro is a debug helper MySQL/MariaDB gives us. As
  noted above, the output is directed to a file specified by the
  &lt;code&gt;--debug&lt;/code&gt; flag. Unfortunately I couldn't figure out how to flush the
  stream this macro writes to. It seemed like occasionally when there
  was a segfault logs I expected to be there weren't there. And the
  file would often contain what looked like partially written
  logs. Anyway, as long as there wasn't a segfault the debug file will
  eventually contain the &lt;code&gt;DBUG_PRINT&lt;/code&gt; logs.
&lt;/p&gt;&lt;p&gt;The only thing the plugin cleanup function must do is delete the
global database.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memem_fini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can register the plugin!&lt;/p&gt;
&lt;h4 id="plugin-registration"&gt;Plugin registration&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;maria_declare_plugin&lt;/code&gt; and &lt;code&gt;maria_declare_plugin_end&lt;/code&gt; register the
plugin's metadata (name, version, etc.) and callbacks.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;st_mysql_storage_engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memem_storage_engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MYSQL_HANDLERTON_INTERFACE_VERSION&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;maria_declare_plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memem&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MYSQL_STORAGE_ENGINE_PLUGIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;memem_storage_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MEMEM&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MySQL AB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;In-memory database.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;PLUGIN_LICENSE_GPL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;memem_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* Plugin Init */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;memem_fini&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* Plugin Deinit */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mh"&gt;0x0100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* 1.0 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="cm"&gt;/* status variables                */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="cm"&gt;/* system variables                */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="cm"&gt;/* string version */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MariaDB_PLUGIN_MATURITY_STABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* maturity */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maria_declare_plugin_end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's it! Now we need to implement methods for writing rows, reading
rows, and creating a new table.&lt;/p&gt;
&lt;h4 id="create-table"&gt;Create table&lt;/h4&gt;&lt;p&gt;To create a table, we make sure one by this name doesn't already
exist, make sure it only has &lt;code&gt;INTEGER&lt;/code&gt; fields, allocate memory for the
table, and append it to the global database.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ha_memem::create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;table_arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="n"&gt;HA_CREATE_INFO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;create_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memem_table_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// We only support INTEGER fields for now.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_arg&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_arg&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MYSQL_TYPE_LONG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DBUG_PRINT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;info&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Unsupported field type.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_shared&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MememTable&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_shared&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;DBUG_PRINT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;info&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[MEMEM] Created table '%s'.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not very complicated. Let's handle &lt;code&gt;INSERT&lt;/code&gt;-ing rows next.&lt;/p&gt;
&lt;h4 id="insert-row"&gt;Insert row&lt;/h4&gt;&lt;p&gt;There is no method called when an &lt;code&gt;INSERT&lt;/code&gt; starts. There is a &lt;code&gt;table&lt;/code&gt;
field on the &lt;code&gt;handler&lt;/code&gt; parent class that is updated though when a
&lt;code&gt;SELECT&lt;/code&gt; or &lt;code&gt;INSERT&lt;/code&gt; is going. So we can fetch the current table from
that field.&lt;/p&gt;
&lt;p&gt;Since we have a slot for a &lt;code&gt;std::shared_ptr&amp;lt;MememTable&amp;gt; memem_table&lt;/code&gt;
on the &lt;code&gt;ha_memem&lt;/code&gt; class, we can check if it is &lt;code&gt;NULL&lt;/code&gt; when we insert a
row. If it is, we look up the current table and set
&lt;code&gt;this-&amp;gt;memem_table&lt;/code&gt; to its &lt;code&gt;MememTable&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But there's a bit more to it than just the table name. The &lt;code&gt;const
char* name&lt;/code&gt; passed to the &lt;code&gt;create()&lt;/code&gt; method above seems to be a sort
of fully qualified name for the table. By observation, when creating a
table &lt;code&gt;y&lt;/code&gt; in a database &lt;code&gt;test&lt;/code&gt;, the &lt;code&gt;const char* name&lt;/code&gt; value is
&lt;code&gt;./test/y&lt;/code&gt;. The &lt;code&gt;.&lt;/code&gt; prefix probably means that the database is local,
but I'm not sure.&lt;/p&gt;
&lt;p&gt;So we'll write a helper method that will reconstruct the fully
qualified table name before looking up that fully qualified table name
in the global database.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ha_memem::reset_memem_table&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Reset table cursor.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;current_position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;DBUG_PRINT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;info&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[MEMEM] Resetting to '%s'.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_str&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memem_table_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_str&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memem_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we can use this within &lt;code&gt;write_row&lt;/code&gt; to figure out the current
&lt;code&gt;MememTable&lt;/code&gt; being queried.&lt;/p&gt;
&lt;p&gt;But first, let's digress into how MySQL stores rows.&lt;/p&gt;
&lt;h4 id="the-mysql-row-api"&gt;The MySQL row API&lt;/h4&gt;&lt;p&gt;When you &lt;a href="https://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html"&gt;write a Postgres custom storage
API&lt;/a&gt;,
you are expected to basically read from or write to an array of
&lt;code&gt;Datum&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Totally sensible.&lt;/p&gt;
&lt;p&gt;In MySQL, you read from and write to an array of bytes. That's pretty
weird to me. Of course you can build your own higher level
serialization/deserialization on top of it. But it's just strange to
me everyone has to know this basically opaque API.&lt;/p&gt;
&lt;p&gt;Certainly &lt;a href="https://github.com/MariaDB/server/blob/11.4/sql/handler.h#L3152"&gt;it's documented&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;The handler class is the interface for dynamically loadable
storage engines. Do not add ifdefs and take care when adding or
changing virtual functions to avoid vtable confusion

Functions in this class accept and return table columns data. Two data
representation formats are used:
1. TableRecordFormat - Used to pass [partial] table records to/from
   storage engine

2. KeyTupleFormat - used to pass index search tuples (aka &amp;quot;keys&amp;quot;) to
   storage engine. See opt_range.cc for description of this format.

TableRecordFormat
=================
[Warning: this description is work in progress and may be incomplete]
The table record is stored in a fixed-size buffer:

  record: null_bytes, column1_data, column2_data, ...

The offsets of the parts of the buffer are also fixed: every column has 
an offset to its column{i}_data, and if it is nullable it also has its own
bit in null_bytes.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our implementation, we'll skip the support for &lt;code&gt;NULL&lt;/code&gt; values. We'll
only support &lt;code&gt;INTEGER&lt;/code&gt; fields. But we still need to be aware that the
first byte will be taken up. We'll also assume there won't be more
than one byte of a NULL bitmap.&lt;/p&gt;
&lt;p&gt;It is this opaque byte array that we'll read from in &lt;code&gt;write_row(const uchar*
buf)&lt;/code&gt; and write to in &lt;code&gt;read_row(uchar* buf)&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="insert-row-(take-two)"&gt;Insert row (take two)&lt;/h4&gt;&lt;p&gt;To keep things simple we're going to store the row in &lt;code&gt;MememTable&lt;/code&gt; the
same way MySQL passes it around.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ha_memem::write_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memem_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;reset_memem_table&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Assume there are no NULLs.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_count&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Store the row in the same format MariaDB gives us.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_shared&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memem_table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which makes reading the row quite simple too!&lt;/p&gt;
&lt;h4 id="read-row"&gt;Read row&lt;/h4&gt;&lt;p&gt;The only slight difference between reading and writing a row is that
MySQL/MariaDB will tell us when the &lt;code&gt;SELECT&lt;/code&gt; scan for a table starts.&lt;/p&gt;
&lt;p&gt;We'll use that opportunity to reset the &lt;code&gt;current_row&lt;/code&gt; cursor and reset
the &lt;code&gt;memem_table&lt;/code&gt; field. Since, again, &lt;code&gt;handler&lt;/code&gt; classes are only used
once per query but they are reused for queries running at other times.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ha_memem::rnd_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;reset_memem_table&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ha_memem::rnd_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memem_table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Reset the in-memory table to make logic errors more obvious.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;memem_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HA_ERR_END_OF_FILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memem_table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Rows internally are stored in the same format that MariaDB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// wants. So we can just copy them over.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;shared_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;uchar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memem_table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current_position&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;current_position&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we're done!&lt;/p&gt;
&lt;h3 id="build-and-test"&gt;Build and test&lt;/h3&gt;&lt;p&gt;Go back into the &lt;code&gt;build&lt;/code&gt; directory we created within the source tree
root and rerun &lt;code&gt;make -j8&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Kill the server (you'll need to do something like &lt;code&gt;killall mariadbd&lt;/code&gt;
since the server doesn't respond to Ctrl-c). And restart it.&lt;/p&gt;
&lt;p&gt;For some reason this plugin doesn't need to be loaded. We can run
&lt;code&gt;SHOW PLUGINS;&lt;/code&gt; in the MariaDB CLI and we'll see it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;./build/client/mariadb&lt;span class="w"&gt; &lt;/span&gt;--defaults-extra-file&lt;span class="o"&gt;=&lt;/span&gt;/home/phil/vendor/mariadb/my.cnf&lt;span class="w"&gt; &lt;/span&gt;--database&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="go"&gt;Reading table information for completion of table and column names&lt;/span&gt;
&lt;span class="go"&gt;You can turn off this feature to get a quicker startup with -A&lt;/span&gt;

&lt;span class="go"&gt;Welcome to the MariaDB monitor.  Commands end with ; or \g.&lt;/span&gt;
&lt;span class="go"&gt;Your MariaDB connection id is 5&lt;/span&gt;
&lt;span class="go"&gt;Server version: 11.4.0-MariaDB-debug Source distribution&lt;/span&gt;

&lt;span class="go"&gt;Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.&lt;/span&gt;

&lt;span class="go"&gt;Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.&lt;/span&gt;

&lt;span class="go"&gt;MariaDB [test]&amp;gt; SHOW PLUGINS;&lt;/span&gt;
&lt;span class="go"&gt;+-------------------------------+----------+--------------------+-----------------+---------+&lt;/span&gt;
&lt;span class="go"&gt;| Name                          | Status   | Type               | Library         | License |&lt;/span&gt;
&lt;span class="go"&gt;+-------------------------------+----------+--------------------+-----------------+---------+&lt;/span&gt;
&lt;span class="go"&gt;| binlog                        | ACTIVE   | STORAGE ENGINE     | NULL            | GPL     |&lt;/span&gt;
&lt;span class="go"&gt;...&lt;/span&gt;
&lt;span class="go"&gt;| MEMEM                         | ACTIVE   | STORAGE ENGINE     | NULL            | GPL     |&lt;/span&gt;
&lt;span class="go"&gt;...&lt;/span&gt;
&lt;span class="go"&gt;| BLACKHOLE                     | ACTIVE   | STORAGE ENGINE     | ha_blackhole.so | GPL     |&lt;/span&gt;
&lt;span class="go"&gt;+-------------------------------+----------+--------------------+-----------------+---------+&lt;/span&gt;
&lt;span class="go"&gt;73 rows in set (0.012 sec)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There we go! To create a table with it we need to set &lt;code&gt;ENGINE =
MEMEM&lt;/code&gt;. For example, &lt;code&gt;CREATE TABLE x (i INT) ENGINE = MEMEM&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's create a script to try out the &lt;code&gt;memem&lt;/code&gt; engine, in
&lt;code&gt;storage/memem/test.sql&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;drop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;drop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MEMEM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1029&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MEMEM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;322&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And run it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;./build/client/mariadb&lt;span class="w"&gt; &lt;/span&gt;--defaults-extra-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/my.cnf&lt;span class="w"&gt; &lt;/span&gt;--database&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--table&lt;span class="w"&gt; &lt;/span&gt;--verbose&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;storage/memem/test.sql
&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;drop table if exists y&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;drop table if exists z&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;create table y(i int, j int) engine = MEMEM&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;insert into y values (2, 1029)&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;insert into y values (92, 8)&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;select * from y where i + 8 = 10&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;+------+------+&lt;/span&gt;
&lt;span class="go"&gt;| i    | j    |&lt;/span&gt;
&lt;span class="go"&gt;+------+------+&lt;/span&gt;
&lt;span class="go"&gt;|    2 | 1029 |&lt;/span&gt;
&lt;span class="go"&gt;+------+------+&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;create table z(a int) engine = MEMEM&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;insert into z values (322)&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;insert into z values (8)&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;--------------&lt;/span&gt;
&lt;span class="go"&gt;select * from z where a &amp;gt; 20&lt;/span&gt;
&lt;span class="go"&gt;--------------&lt;/span&gt;

&lt;span class="go"&gt;+------+&lt;/span&gt;
&lt;span class="go"&gt;| a    |&lt;/span&gt;
&lt;span class="go"&gt;+------+&lt;/span&gt;
&lt;span class="go"&gt;|  322 |&lt;/span&gt;
&lt;span class="go"&gt;+------+&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What you see there is the power of storage engines! It supports the
full SQL language even while we implemented storage somewhere
completely different than the default.&lt;/p&gt;
&lt;h3 id="in-memory-is-boring"&gt;In-memory is boring&lt;/h3&gt;&lt;p&gt;Certainly, I'm getting bored doing the same project over and over
again on different databases. However, it's minimal projects like this
that make it super easy to then go and port the storage engine to
something else.&lt;/p&gt;
&lt;p&gt;The goal here is to be minimal but meaningful. And I've accomplished
that for myself at least!&lt;/p&gt;
&lt;h3 id="on-chatgpt"&gt;On ChatGPT&lt;/h3&gt;&lt;p&gt;As I've &lt;a href="https://notes.eatonphil.com/2023-11-19-exploring-a-postgres-query-plan.html#postscript:-on-chatgpt"&gt;written
before&lt;/a&gt;,
this sort of exploration wouldn't be possible within the time frame I
gave myself if it weren't for ChatGPT. Specifically, the paid tier
GPT4.&lt;/p&gt;
&lt;p&gt;Neither the MySQL nor the MariaDB docs were so helpful that I could
immediately figure out things like how to get the current table name
within a scan (the &lt;code&gt;table&lt;/code&gt; member of the &lt;code&gt;handler&lt;/code&gt; class).&lt;/p&gt;
&lt;p&gt;With ChatGPT you can ask questions like: "In a MySQL C++ plugin, how
do I get the name of the table from a &lt;code&gt;handler&lt;/code&gt; class as a C
string?". Sometimes it's right and sometime's it's not. But you can
try out the code and if it builds it is at least somewhat correct!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Wrote a post walking you through building a super minimal in-memory storage engine for MySQL/MariaDB in 218 lines of C++.&lt;br /&gt;&lt;br /&gt;And took time again to reflect on the limitations of custom storage engines and how MySQL compares to Postgres internally here.&lt;a href="https://t.co/nImUC36DPs"&gt;https://t.co/nImUC36DPs&lt;/a&gt; &lt;a href="https://t.co/1Oj2Lcua8O"&gt;pic.twitter.com/1Oj2Lcua8O&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1744822526088282587?ref_src=twsrc%5Etfw"&gt;January 9, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 09 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-01-09-minimal-in-memory-storage-engine-for-mysql.html</guid></item><item><title>Visualizing SQLAlchemy Databases as Mermaid and Dot Diagrams using Paracelsus</title><link>https://blog.tedivm.com/open-source/2024/01/visualizing-sqlalchemy-databases-using-paracelsus/</link><description>SQLAlchemy is a powerful tool that lets Python developers define their database schemas using simple classes. I made Paracelsus to turn those SQLAlchemy defined databases into diagrams that can be used to visualize the schema.</description><author>tedious ramblings</author><pubDate>Mon, 08 Jan 2024 16:30:00 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/open-source/2024/01/visualizing-sqlalchemy-databases-using-paracelsus/</guid></item><item><title>Using a Static JSON File in Home Assistant</title><link>https://www.petekeen.net/static-json-in-home-assistant/</link><description>&lt;p&gt;Recently I found myself needing to bring some JSON from a file into a Home Assistant sensor.
        Specifically, the electricity rates for my power company are woefully out of date on OpenEI so I decided I could just maintain the data myself.&lt;/p&gt;
        &lt;p&gt;Home Assistant doesn't have a direct way to read JSON data from a file into a sensor.
        There's the &lt;a href="https://www.home-assistant.io/integrations/file/"&gt;File&lt;/a&gt; platform which has a promising name but is actually a trap.
        File is meant for use cases where something writes to, say, a CSV file continuously and you just want to read the most recent line. It specifically does not read the whole file.&lt;/p&gt;
        &lt;p&gt;After a lot of searching I came across the &lt;a href="https://www.home-assistant.io/integrations/command_line/"&gt;Command Line&lt;/a&gt; platform.
        The integration does a number of things, but for our purposes it lets you periodically run a command within the context of the Home Assistant container and bring the output back into Home Assistant as a sensor.&lt;/p&gt;
        &lt;!--more--&gt;
        &lt;p&gt;Let's say you have a JSON file named &lt;code&gt;rate.json&lt;/code&gt; in your Home Assistant configuration directory:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-json"&gt;{
        &amp;quot;name&amp;quot;: &amp;quot;Base Rate&amp;quot;,
        &amp;quot;rate&amp;quot;: 0.15
        }
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;You can bring that into a sensor with the following snippet in your &lt;code&gt;configuration.yaml&lt;/code&gt; file:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-yaml"&gt;command_line:
        - sensor:
        name: &amp;quot;Electrity Rate&amp;quot;
        command: 'cat rate.json',
        value_template: &amp;quot;{{ value_json['rate'] }}&amp;quot;
        unit_of_measurement: &amp;quot;USD/kWh&amp;quot;,
        json_attributes:
        - name
        - rate
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;This config does a couple things.
        The &lt;code&gt;command&lt;/code&gt; key specifies what command HA should run, in this case &lt;code&gt;cat&lt;/code&gt; to read the file to &lt;code&gt;stdout&lt;/code&gt;.
        &lt;code&gt;value_template&lt;/code&gt; extracts the &lt;code&gt;rate&lt;/code&gt; key from the file into the sensor's value.
        The &lt;code&gt;json_attributes&lt;/code&gt; list pulls the list of keys into attributes in the sensor, which you can later access from a template using &lt;code&gt;state_attr()&lt;/code&gt;.
        I have also specfied &lt;code&gt;unit_of_measurement&lt;/code&gt; here just because the Energy reporting system needs that if you want to use this as an input.&lt;/p&gt;
        &lt;p&gt;So, the above is great if you have one static set of attributes to bring in, but sensor values can be at most 255 characters.
        What if you have a bigger file that you need to pull just a little data out of?&lt;/p&gt;
        &lt;p&gt;Let's say we have this slightly bigger file &lt;code&gt;rates.json&lt;/code&gt;:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-json"&gt;[
        {
        &amp;quot;id&amp;quot;: &amp;quot;d1-11_summer_on_peak&amp;quot;,
        &amp;quot;name&amp;quot;: &amp;quot;Summer Peak&amp;quot;,
        &amp;quot;months&amp;quot;: [6, 7, 8, 9],
        &amp;quot;days&amp;quot;: [1, 2, 3, 4, 5],
        &amp;quot;hours&amp;quot;: [15, 16, 17, 18],
        &amp;quot;rate&amp;quot;: 0.23525,
        &amp;quot;peak&amp;quot;: true
        },
        {
        &amp;quot;id&amp;quot;: &amp;quot;d1-11_summer_off_peak&amp;quot;,
        &amp;quot;name&amp;quot;: &amp;quot;Summer Off-Peak&amp;quot;,
        &amp;quot;months&amp;quot;: [6, 7, 8, 9],
        &amp;quot;days&amp;quot;: [0, 1, 2, 3, 4, 5, 6],
        &amp;quot;hours&amp;quot;: [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
        &amp;quot;rate&amp;quot;: 0.17859,
        &amp;quot;peak&amp;quot;: false
        },
        {
        &amp;quot;id&amp;quot;: &amp;quot;d1-11_winter_on_peak&amp;quot;,
        &amp;quot;name&amp;quot;: &amp;quot;Winter Peak&amp;quot;,
        &amp;quot;months&amp;quot;: [1, 2, 3, 4, 5, 10, 11, 12],
        &amp;quot;days&amp;quot;: [1, 2, 3, 4, 5],
        &amp;quot;hours&amp;quot;: [15, 16, 17, 18],
        &amp;quot;rate&amp;quot;: 0.17879,
        &amp;quot;peak&amp;quot;: true
        },
        {
        &amp;quot;id&amp;quot;: &amp;quot;d1-11_winter_off_peak&amp;quot;,
        &amp;quot;name&amp;quot;: &amp;quot;Winter Off-Peak&amp;quot;,
        &amp;quot;months&amp;quot;: [1, 2, 3, 4, 5, 10, 11, 12],
        &amp;quot;days&amp;quot;: [0, 1, 2, 3, 4, 5, 6],
        &amp;quot;hours&amp;quot;: [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
        &amp;quot;rate&amp;quot;: 0.1658,
        &amp;quot;peak&amp;quot;: false
        }
        ]
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;This file describes the rate structure that's in effect at my house, DTE rate D1.11.
        Each entry in the array is a rate, and the first matching rate based on month, weekday, and hour is the effective rate.&lt;/p&gt;
        &lt;p&gt;Ordinarily one might reach for a HACS integration or something, but the Home Assistant container has another trick up it's sleeve: it bundles &lt;code&gt;jq&lt;/code&gt;.&lt;/p&gt;
        &lt;p&gt;&lt;a href="https://jqlang.github.io/jq/"&gt;&lt;code&gt;jq&lt;/code&gt;&lt;/a&gt; is a tool for querying and manipulating JSON streams.
        This essay isn't meant to be a &lt;code&gt;jq&lt;/code&gt; tutorial so we're not going to go in depth into what this query in &lt;code&gt;rate_filter.jq&lt;/code&gt; does, but in broad strokes it picks the first matching rate from the input file and extracts just the name, rate, and peak keys.&lt;/p&gt;
        &lt;pre&gt;&lt;code&gt;map(select(
        (.months[] | contains($ARGS.positional[0] | tonumber))
        and (.days[] | contains($ARGS.positional[1] | tonumber))
        and (.hours[] | contains($ARGS.positional[2] | tonumber))
        ))[0] | {name, rate, peak}
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;Here's a modified command line sensor that runs &lt;code&gt;jq&lt;/code&gt; appropriately:&lt;/p&gt;
        &lt;pre&gt;&lt;code&gt;command_line:
        - sensor:
        name: &amp;quot;DTE Rate&amp;quot;
        command: 'jq -f rate_filter.jq rates.json --args {{ now().month }} {{ now().isoweekday() % 7 }} {{ now().hour }}'
        value_template: &amp;quot;{{ value_json['rate'] }}&amp;quot;
        unit_of_measurement: USD/kWh
        json_attributes:
        - name
        - rate
        - peak
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;The relevant change here is to the &lt;code&gt;command&lt;/code&gt; key, which now invokes &lt;code&gt;jq&lt;/code&gt; with the &lt;code&gt;-f&lt;/code&gt; argument to pass the filter as a file rather than trying to quote everything properly within HA, then passes the actual &lt;code&gt;rates.json&lt;/code&gt; file, then treats the rest of the arguments as positional args.
        These are accessed within &lt;code&gt;rate_filter.jq&lt;/code&gt; as &lt;code&gt;$ARGS.positional[0]&lt;/code&gt; etc.&lt;/p&gt;
        &lt;p&gt;With this set up I can access the current electric rate within my Home Assistant in a way that is compatible with the Energy dashboard, which is completely local, and which should be easy to maintain in the future.&lt;/p&gt;
        &lt;p&gt;The above isn't at all specific to electric rates, by the way. This technique should work for any data that you need in HA but is more complicated than a plain input can work with.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Mon, 08 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/static-json-in-home-assistant/</guid></item><item><title>DAPLs: Domain Agnostic Programming Languages</title><link>https://blog.metaobject.com/2024/01/dapls-domain-agnostic-programming.html</link><description>Every once in a while, you get an insight that hits you like a truck.  Or maybe a ton of bricks.
Or a truck carrying a ton of bricks.  Developing &lt;a href="https://objective.st"&gt;Objective-S&lt;a&gt; has
delivered a bunch of these, but one of the biggest was that our &lt;em&gt;General Purpose Programming
Languages&lt;/em&gt; are nothing of the sort.  They are Domain Specific Languages for the domain
of algorithms.  See also: ALGOL.  To move forward, programming languages will have to support
more than just this one architectural style.&lt;p&gt;

Alas, communicating this insight has been...&lt;em&gt;challenging&lt;/em&gt;.  One method was branding
&lt;a href="https://objective.st"&gt;Objective-S&lt;a&gt; as "the first general purpose programming language".
This did not always go over well.&lt;p&gt;

&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;This doesn't really sound much like a discussion...&lt;br /&gt;&lt;br /&gt;I understand you're trying to make a fun rhetorical point with the framing, but I have to say it just makes me uninterested in any serious engagement.&lt;br /&gt;&lt;br /&gt;Makes it seem like you don't care to even acknowledge existing PL work. =/&lt;/p&gt;&amp;mdash; Chandler Carruth (@chandlerc1024) &lt;a href="https://twitter.com/chandlerc1024/status/1391653250529513473?ref_src=twsrc%5Etfw"&gt;May 10, 2021&lt;/a&gt;&lt;/blockquote&gt; 

Or 
&lt;figure&gt;
&lt;blockquote&gt;
It looks interesting. But stuff like this: &lt;br /&gt;
&amp;gt; Objective-S is the first general purpose programming language &lt;br /&gt;

Those kind of statements annoy me to be honest. Is it really true? Or is it over-the-top marketing hype?
For me – and I'm sure I'm not the only person who feels this way – it creates a negative first impression.
&lt;/blockquote&gt;
  &lt;figcaption&gt;
     &lt;cite&gt;&lt;a href="https://news.ycombinator.com/item?id=27100887"&gt;skissane on May 9, 2021  on: Objective-S: architecture-oriented language based ...&lt;/a&gt;&lt;/cite&gt; 
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;

Of course, there is no such thing as bad publicity, but this had a bit too much of a lunatic-fringe
vibe, no matter how correct the insight, and no matter how ill-fitting the moniker "general purpose
language" really is for our call/return-oriented algorithm-DSLs.&lt;p&gt;

As Richard Feynman once put it, "One of the miseries of life is that everyone names everything a litte bit wrong, and so it makes everything a little harder to understand in the world than it would be if it were named differently".  Calling our algorithm-DSLs "general purpose" implies that we have solved the problem of
generality, when we have not, and that the only real alternative is to be more specific, hence DSLs.
But DSLs also don't really work that well, because the successful ones almost invariable grow 
non-domain-specific features, just in a haphazard way.  Or they need to be combined to cover different
fields, so we get language workbenches that allow us to define lots of little DSLs and combine them.&lt;p&gt;

This all points to the fact that our problem is not being too general, but too specific.  Our algorithm-DSLs
just aren't very good at covering a lot of the problems programmers have to solve, though of course
they are Turing-complete and can get us there, somehow.&lt;p&gt;

Riffing off those ideas, and leaving aside the minefield of incorrect but entrenched terminology, I
propose the term &lt;em&gt;Domain Agnostic Programming Language&lt;/em&gt;.  Because any sufficiently powerful
DSL can be bent out of shape sufficiently for any purpose, just like our algorithm-DSLs can.  They
just aren't a good fit.  And so Objective-S is not the first general purpose language, it is the 
first, and almost certainly the worst, DAPL.  And hopefully its programming environment will be
dapper.&lt;p&gt;</description><author>metablog</author><pubDate>Wed, 03 Jan 2024 11:29:07 GMT</pubDate><guid isPermaLink="true">https://blog.metaobject.com/2024/01/dapls-domain-agnostic-programming.html</guid></item><item><title>My Fave New Podcasts of 2023</title><link>https://www.swyx.io/fave-podcasts-2023</link><description>As someone who does a lot of my learning via podcasts, I've been putting up picks lists for 4 years straight (see main 2019 list, then my 2020 and 2021 and 2022 diffs), so it's time to do year 5(!)</description><author>swyx's site RSS Feed</author><pubDate>Wed, 03 Jan 2024 09:11:43 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/fave-podcasts-2023</guid></item><item><title>Looking Ahead to 2024</title><link>https://benovermyer.com/blog/2024/01/looking-ahead-to-2024/</link><description>&lt;p&gt;This year I want to continue my introduction to bodybuilding and round out
my fitness regimen with regular cardio. For January and February, I'll
finish the "Start From Scratch" series of workouts. I will also start running
three days a week again. I'll be taking progress photos. By July, I want to
see distinct muscle definition in the mirror. I also want to try and get my
VO2 max consistently up to 38 in the same timeframe.&lt;/p&gt;
&lt;p&gt;Last year January and November were alcohol-free months. This year, I'll go
alcohol-free for three months out of the year. January is one of them. I
might make March and August the other two, but I'll leave that up to planning
later.&lt;/p&gt;
&lt;p&gt;For Silver Gryphon Games, my sole objective this year is to get Aethermancy
released. Once that's done, I'll try and get the second editions of Aether
and Ingenium out, but those are bonuses.&lt;/p&gt;
&lt;p&gt;For Iron Arachne, I want to really push on the sci-fi side of generators. I
will expand the details for the planet, star nation, and star system generators.&lt;/p&gt;
&lt;p&gt;I have three trips planned for this year so far. One is a trip to Mexico with
college friends. The second is an eclipse viewing party. The third is a father-son
cruise in the Mediterranean. For each of these I want to make sure I journal about
them.&lt;/p&gt;
&lt;p&gt;Finally, I'm going to try and set a journaling habit. Some of this will turn into
blog posts, but the idea is to record more data about my life and use that for
reflection later.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Mon, 01 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2024/01/looking-ahead-to-2024/</guid></item><item><title>2023: year in perspective</title><link>https://learnbyexample.github.io/2023-year-in-perspective/</link><description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Updated six programming ebooks, created four interactive TUI apps for exercises, wrote blog posts, recorded YouTube videos, newsletter prospered, read 100+ novels, and so on. Had a great year in terms of ebook sales despite worries over AI tools 😇&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="books-updated"&gt;Books updated&lt;a class="zola-anchor" href="#books-updated"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This year I focused on updating my existing ebooks instead of working on a new one. I managed to revise 6 out of my 13 published works so far. Examples and exercises were added and improved. Typos were corrected, sections added for new features (if any), new book covers, &lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NdoeZIhEAPgPhojD8kvRdQ"&gt;promo videos&lt;/a&gt; and so on.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; — Learn Python Regular Expressions step-by-step from beginner to advanced levels with 300+ examples&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep"&gt;CLI text processing with GNU grep and ripgrep&lt;/a&gt; — Example based guide to mastering GNU grep and ripgrep&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnused"&gt;CLI text processing with GNU sed&lt;/a&gt; — Example based guide to mastering GNU sed&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; — Example based guide to mastering GNU awk one-liners&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_perl_oneliners"&gt;Perl One-Liners Guide&lt;/a&gt; — Example based guide for text processing with Perl from the command line&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;Understanding JavaScript RegExp&lt;/a&gt; — Learn JavaScript Regular Expressions step-by-step from beginner to advanced levels with hundreds of examples and exercises&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="tui-apps"&gt;TUI apps&lt;a class="zola-anchor" href="#tui-apps"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Last year, I had learned a bit of &lt;a href="https://textual.textualize.io/"&gt;Textual&lt;/a&gt;. My aim was to create interactive apps for practicing exercises from my ebooks. I wrote the following apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexExercises"&gt;Python re(gex)? exercises&lt;/a&gt; — 100+ exercises for Python Regular Expressions
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexPlayground"&gt;Python re(gex)? playground&lt;/a&gt; — interactive playground, also includes a cheatsheet&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/GrepExercises"&gt;Grep Exercises&lt;/a&gt; — 50+ exercises for &lt;code&gt;GNU grep&lt;/code&gt; (or alternate implementations like &lt;code&gt;ripgrep&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/SedExercises"&gt;Sed Exercises&lt;/a&gt; — 50+ exercises for &lt;code&gt;GNU sed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/AwkExercises"&gt;Awk Exercises&lt;/a&gt; — 80+ exercises for &lt;code&gt;GNU awk&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I also added more exercises for the &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises"&gt;Linux CLI Text Processing Exercises&lt;/a&gt; app.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="blog-posts"&gt;Blog posts&lt;a class="zola-anchor" href="#blog-posts"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most of my blog posts this year were related to book and interactive app announcements. So, not really a choice to pick favorites from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/python-regex-surprises/"&gt;Python Regex Surprises&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/mini/cli-text-editing-with-ed/"&gt;CLI text editing with ed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also posted some &lt;a href="https://learnbyexample.github.io/tips/"&gt;weekly programming tips&lt;/a&gt; (Python, Linux, Vim).&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="book-sales"&gt;Book sales&lt;a class="zola-anchor" href="#book-sales"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Revenue from ebook sales were about 10% lower than last year. At the start of the year, I'd have been satisfied even if it had been 50% lower. I wasn't writing new ebooks and AI tools were all the rage on social media. Somehow, I got lucky with self-promotion posts for my &lt;code&gt;GNU awk&lt;/code&gt; ebook and the rest of the months weren't too shabby. Here's my Gumroad revenue chart for 2023:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Gumroad sales in 2023" src="/images/books/gumroad_sales_2023.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can clearly see when the &lt;code&gt;GNU awk&lt;/code&gt; ebook was updated. Sales on Gumroad was actually just a bit higher than last year. It was on Leanpub that sales were much lower, almost half compared to last year. Profits reduced more than 10% since Gumroad increased their fees. Overall, I'm still earning more than I need and I'm hoping that next year wouldn't see too much drop in sales.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started a newsletter, &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt;, two years back. I've managed to send an email every Friday without fail so far and I'm proud of that. Sometimes I had to schedule issues weeks ahead. Total subscriber count crossed 1000 earlier this month and some readers are even paying me monthly despite this being a free newsletter.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="fictional-reading"&gt;Fictional reading&lt;a class="zola-anchor" href="#fictional-reading"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I enjoy reading fantasy and science-fiction novels. I read 100+ SFF books this year despite aiming for less than 100! Anyway, I wrote a post listing &lt;a href="https://learnbyexample.github.io/escapist-reviews/lists/2023-favorite-sff-novels/"&gt;my favorites here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I even participated in NaNoWriMo. I only wrote 20K words, but I did have some fun. The novel went nowhere though and it is languishing now. Not sure if I'd get back to it someday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="goals-for-2024"&gt;Goals for 2024&lt;a class="zola-anchor" href="#goals-for-2024"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are seven more books I need to update. Hopefully I get them done in a year, though I won't be pushing hard. If I crave to write some new books instead, I'd switch over to them. Or even do something else entirely. After more than six years writing tutorials and books, I sure can do with a break.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;Here's wishing you a very happy, healthy and prosperous 2024 👍 😇&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 01 Jan 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/2023-year-in-perspective/</guid></item></channel></rss>