Phpizza Blog

New Year, New Filesystem

February 01, 2023

Following up on my post from 2019, I’ve upgraded my server yet again. Most of the changes were made in late 2022, but I just finished the last major changes that I wanted to make and I’m really happy with where it’s at now.

Four big changes overall:

  • Moved from an old quad-core AMD A8 to an 8-core Ryzen 7 2700X
  • Added a GTX 1050 GPU for video transcoding and display output
  • Upgraded from 32 GB of DDR3 to 64 GB of DDR4
  • Moved bulk storage from a random assortment of disks and filesystems to several identically-sized disks in a ZFS pool

All of these come together to make a huge difference in performance and maintenance overhead, and it’s an excellent little server. Also it’s named Kanade now, after 宵崎奏.

The previous btrfs storage setup was fairly nice, but I ran into issues with capacity limits and management overhead when I had multiple filesystems. For a while I was running a mix of RAID1 btrfs, single-disk btrfs, and a few ext4 disks, with each configured inconsistently to maximize capacity or resiliency for each data set.

This worked well enough, but I wanted to move to a single filesystem across all of my disks, with redundancy everywhere and a larger total storage amount. Before this change, my YouTube archive for example was split between a 2x8 TB /archive btrfs RAID1 filesystem, and another 12 TB /youtube-2 btrfs disk without redundancy. This meant that I had to decide which videos were worth having archived on redundant storage, while keeping the majority of them on the single disk.

Since the files are primarily backups of content still available publicly anyway, this isn’t a huge deal from a resiliency perspective, but it did add a lot of manual work in moving things around when a filesystem got too full.

By the final stage of my old btrfs/ext4 setup, my disks were arranged in:

  • 2x12 TB btrfs RAID1
  • 2x8 TB btrfs RAID1
  • 12 TB btrfs (single disk)
  • 12 TB ext4 (single disk)
  • 3 TB ext4 (single disk)
  • 128 GB ext4 SSD (single disk)

This is a lot of different filesystems with a lot of different capacities, levels of fragmentation, resiliency, and speeds. I wanted something simpler, and had wanted to try out ZFS for quite a while.

The biggest thing holding me back from ZFS had been the weirdness around the licensing. Oracle being involved in something is never great for long-term usability. With the OpenZFS project actively working on a lot of great features for Linux support and the ArchZFS team doing a great job keeping packages up to date for Arch Linux, it seemed solid enough to trust.

Yes, I run Arch on a dedicated server I rely on for basically everything. It’s great.

My new setup is 8x12 TB drives in a single ZFS pool, with a small SSD for L2ARC. I also have another ext4 SSD for temporary writes (compiling, transcoding, etc.) to reduce fragmentation in the ZFS pool.

I have a variety of datasets within the pool, with different levels of compression and different auto-rotating snapshots, but having everything in a single pool of disks is great for management. Snapshots are configured with zrepl, with a staggered cycle keeping 15m, 1h, 1d, and 7d snapshots. The top-level dataset is set to lz4 compression, and my long-term backup dataset uses gzip-9 since it is rarely written to and saving a few GB here and there can make a big difference.

My initial pool included some older disks along with some newer ones. One of the old disks failed fairly early on after moving everything to the new pool, which was a great way to familiarize myself with the ZFS recovery and disk replacement procedures early on. I’ve also run into a few issues with my storage controller dropping one of the disks randomly, but ZFS makes it easy to identify when those issues are occurring and correct any missed writes on the affected disks without data loss.

I still really miss using reflinks in btrfs as a lightweight offline deduplication option, but there is a block reference tree branch being worked on in OpenZFS that will add support for that at some point. While waiting for the feature, I’ve finally gotten around to doing some much-needed cleanup of duplicate files I didn’t want in the first place, and I’ve hard-linked large files that won’t be changed but are helpful to have in multiple places.

If you’re looking for a flexible, fast, resilient filesystem with a really dedicated community of users and developers, ZFS is a great option.

Playdate

April 25, 2022

I finally got my Playdate! I had placed my preorder within less than a minute of them opening up orders, and while it was supposed to ship last December, things happened and it was delayed a bit. They started shipping last week though, and I have mine now! Thanks, Panic ❤️

Playdate console in its yellow box, with a charging cable visible on a solid purple background.

I love the aesthetic of this thing. It’s tiny, bright yellow, has a purple cover accessory, and is just the cutest thing ever. I’m definitely getting a Stereo Dock when it’s available, but there’s no date or anything for that yet.

The form factor is a little rough to play on. The display is tiny and doesn’t have a backlight, and the crank requires more force than I was expecting which can be tricky for some games that require quick or precise movement. Overall though it’s a fun toy that I’d recommend to anyone

Let’s get to the real fun though.

The Games

New Playdate owners get a collection of games included with the purchase, rolling out with two games a week, known as Season One. I’ll be updating this with reviews of any of the games that stand out to me throughout the season. You can also easily sideload games or make them yourself!

Overall I’ve found the Season One games to be fine but not anything I’ve immediately loved. They’re unique, but not especially fun or engaging. That said there are 24 games rolling out in the coming weeks and I’ve only tried the first few so far, so I’m sure there are some that I’ll love.

Bloom

My favorite game on Playdate so far isn’t part of the Season One package, but is instead sold independently on itch.io. I love casual games around playing for a few minutes here and there, which the Playdate seems perfect for, and I also love simple narrative-driven games like visual novels. Bloom combines those elements excellently, with a relaxed, cozy gameplay around running a flower shop and talking with friends. This game is wonderful and I love it.

Skiwsh

This is another fun game available on itch.io. A simple but very creative puzzle game with a good selection of levels, with very unique mechanics. Definitely recommended for anyone with a Playdate, and if you don’t there’s also a web version of the original game.

Casual Birder

One of the early Season One games, Casual Birder is an interesting one. You play as someone in a small town, tasked with taking photos of birds. Use the crank to focus your camera. I like a lot of things about it, but the overall gameplay feels clunky, and generally off in a way that’s hard to describe. I like a lot of the writing and visual design, but it feels like an idea that needed a bit more polish to really be fun and engaging.

Lost Your Marbles

This is a fun, silly visual novel mixed with a simple physics game played with the crank. It’s relatively short, but has a lot of branching from the marble minigame that offers decent re-playability. I like the art style and characters, and the gameplay is fun, but it sometimes plays a bit janky with frame rate dips and difficult crank control.

Pick Pack Pup

A matching puzzle game about being a dog working in shipping, this one’s fun and engaging. Very creative expansion on the typical match-3 mechanics, with some interesting and challenging levels. Super cute cartoon dogs.

Flipper Lifter

This game relies on the Playdate’s crank to move a lift up and down floors, helping penguins reach the floors they’re heading to. It’s very simple, but quickly becomes challenging when the number of penguins and floors increases. A fun game worth a bit of play, but not something I’d come back to as often as some of the others.

The SDK

As a software developer, the thing that made me most excited for Playdate was the idea of making my own stuff for it. I’m not exactly a competent dev when it comes to making games, but I loved the idea of a very simplified form factor that limited the scope of a potential game to something more manageable.

The Playdate SDK is really, really good. It has solid documentation, a great selection of example projects, and is incredibly approachable. I’ve done a bit of work in lower level game dev stuff in the past, but Panic has done an excellent job of making input handling, file IO, audio playback, sprite rendering, and a lot more quite painless.

I started off by digging through their example projects, as I always find it way easier to learn something in development by seeing actual implementations of something rather than a list of available functions without any real context. I got rickrolled, learned how text and sprite rendering worked, and started on the easiest type of game I could build: a visual novel.

If you’re building a visual novel for PC or mobile, you should basically only ever choose one engine: Ren’Py. It’s been around an incredibly long time, it’s stupidly simple to implement games in, and there’re loads of games built in it to use as examples of how to do unique gameplay. I wanted to implement a simple subset of Ren’Py’s core functionality as a way to learn the SDK, so I started on a generic VN engine.

I have an original character named Ren, so I drew him in 1-bit pixel art and coded some stuff. It’s a really mediocre “game” that doesn’t really do anything other than test that conversations are somewhat possible to script in the engine, but I was able to quickly learn font rendering and editing, how to dynamically load in game objects, animating sprites, and a bunch of other stuff.

I also built a simple Ren’Py conversion script in PHP, and I want to try porting a full existing game to the Playdate at some point, but for now I’m going to focus on some less dialog-driven games. My current plan is to implement something similar to the Pizzatron 3000 minigame from Club Penguin, complete with using the crank to spread cheese and toss dough.

Tailwind 3 Redesign!

December 29, 2021

Tailwind CSS v3 was recently released, and it’s a pretty simple upgrade process for most v2-based sites. Since I like redesigning things and playing around with Tailwind though, I took the opportunity to update this site again!

This blog probably doesn’t even have an actual audience, but it’s been a good thing to maintain over the years as a way to learn new tools and play around with ideas. This new visual style is the first in what may be a series of changes that are pushing for a more distinct look, rather than looking like a generic site.

I still want to adjust things more, moving components around in a more fun way, adding more colors, etc. I like this as it is, but would love to see where I could take it with a more artistic perspective. So much of my front-end development work centers around simple repetitive UIs, and I really want to do more creative things with the design.

If you do anything with web design and haven’t tried Tailwind yet, you totally should. It’s pretty great!

Beat Saber

December 01, 2021

I’ve played… a lot of Beat Saber. If it was a typical video game I’d consider it about at the limit of what I should reasonably play, especially considering it hasn’t been around as long as Skyrim, Terraria, Minecraft, Half-Life, etc. but I’ve spent about as much time in it.

It’s a very, very good game. It’s incredibly simple, but very polished. It’s the most intuitive game I’ve played, but mastering it takes a lot of practice. I’m at a point now where I can pretty consistently finish ~7 note per second “Expert+” songs, and I’m usually in the top few hundred players score-wise on the songs I play often, but there are definitely plenty of official and fan-made beat maps that I’m nowhere near able to play.

Beat Saber has been an interesting game to follow. I started playing when it was in early access on Steam, with only a handful of official songs. The modding community immediately took off, with loads of excellent “beat maps” being made for songs, and a good selection of quality of life mods adding features and fixes to the base game. The first official paid DLC seemed like an obvious choice, a Monstercat music pack, which is easy to license and fits the gameplay.

From there, the game had grown more popular and Beat Games could more easily license music in a more traditional way for DLC. The occasional first-party song would get added as a free update, but the majority of music that’s officially available these days for Beat Saber is paid DLC of licensed popular music. By the end of 2019, the game was doing so well that Beat Games was acquired by Facebook, as part of their Oculus division.

I’ve played on Oculus Rift, Oculus Quest, and Valve Index. The modding process on the Quest is definitely a lot more painful as it’s running a weird Android OS and doesn’t officially support custom songs, but is still possible. Facebook’s continuous restrictions being added to their Oculus software over time has made me question how much longer the Quest version will be moddable at all, but for now it’s still possible despite the launcher constantly whining about how you’re running unofficial software and they may ban you.

Facebook needs to stop existing. Meta doesn’t count.

Playing on SteamVR is great. The Index is especially great for Beat Saber, with the 144hz mode and bright, high-res displays genuinely feeling like a significant improvement over other hardware. Mods are super simple to install and use, and custom songs are natively supported. Excellent projects and communities like the now-defunct ModSaber, BSMG, ModAssistant, BeastSaber, and many more keep the game constantly improving, with an endless collection of good beat maps, new features, and integrations.

I particularly like color mods, custom sabers, and avatars. For whatever reason I’ve currently settled on playing as a particular Hatsune Miku avatar, when I have that mod enabled. If for some reason you want to watch me play Beat Saber, I occasionally (like 2-3 times ever so far) stream on Twitch, and also have a playlist with some of the maps I’ve enjoyed playing.

I’ve really grown to love the game. It’s consistently the most fun I have in any game, and I love that I can spend an hour playing a great game and burn a few hundred calories doing it.

If you have a VR headset, you should play Beat Saber.

Applying SOLID Principles to Modern PHP

August 31, 2021

The SOLID principles are one of a few groups of software design principles that are often and easily applied to most modern software development. I work primarily in PHP development on the back-end, where object oriented programming is still the usual standard and SOLID is of particular relevancy.

In the modern PHP world, most new development is done in one of a few major frameworks. My framework of choice is Laravel, but each include some level of implied and explicit structure to your application which can determine a lot about how you plan the specifics of new feature implementations.

The SOLID principles include:

  • Single responsibility — Each class should have one responsibility
  • Open/closed — Objects should be open to extension, but closed for modification (private attributes, immutability, etc.)
  • Liskov substitution — An extended object should behave the same as the object it extends for the behavior the original object exposes
  • Interface segregation — Functionality should be split into small logical objects
  • Dependency inversion — Rely on abstractions and interfaces when building common implementations, but don’t couple abstractions to an implementation

I personally find that these principles feel somewhat obvious once you’ve worked in OOP for a while, and that the SOLID acronym does little to help remember or educate (unlike, for example DRY). However they’re still important to learn and truly understand, particularly when designing an application from early stages, to avoid running into major problems when trying to scale the application later.

Let’s take a look at how each one affects how I architect a PHP application.

Single responsibility

One of the most common approaches to structuring a modern web application is the MVC pattern. Many variations of this pattern exist, but the basic concept is that you break an application into at least three major areas:

  • Models, which represent the data objects (e.g. a record in a database table)
  • Views, which represent the renderable representations of data
  • Controllers, which connect the models and views for a given request

Within this simple architecture, there are a lot of things left open. Choices like where to put business logic, how to handle authorization, and many other common realities of an application are not predetermined.

To keep classes responsible for only their relevant behaviors, it is necessary to supplement MVC with additional areas. Common additions include Middleware and View-Models, which offer greater control over the different layers of the application.

In particular when building in a modern framework, splitting code into smaller logical areas within each MVC area is important to keeping things clean. As an example, putting everything related to a user login process in a single controller class may make sense when it only includes one or two routes to handle showing a login page and processing the POST action, but can quickly become too complex when you start including validation, two-factor flows, password resets, etc.

Instead of putting everything involved in a user logging in in a single controller class, splitting the behavior into several classes could include:

  • A controller for each action (showing the form, handling the POST, etc.)
  • A data transfer object for representing the form data, which performs validation
  • A view model that ties the DTO values to the view

Each of those classes have one clearly defined role within the action the user is performing, and make it easy to find and change that behavior without affecting anything unexpected.

Open/closed

When designing a class, it’s important to pay attention to which attributes are handled by the class internally, and which should be accessible to both outside accessors and child classes that extend the class. Failure to correctly enforce these conventions can have huge impacts on the quality of a codebase, and cause classes and behaviors to become entangled in very unpredictable and unmaintainable ways.

Various conventions and language constructs exist to both imply and enforce the open and closed nature of properties and methods on a class, including a very useful readonly keyword coming to class properties in PHP 8.1 later this year. I typically find that I make most class properties protected, and most methods public.

In cases where a class needs to perform some operation as an internal effect of a public method call, it often makes sense to put that behavior in a protected function within the class, especially if it is used in multiple places within the class.

When you do need to share a property value with a class, most applications currently implement this indirectly through the use of getter functions, rather than exposing the property publicly. PHP 8.1’s addition of the readonly keyword will change this in many cases, as it will be safe to set a value once within a class constructor, and make it accessible publicly without allowing external behavior to mutate the property.

Using protected instead of private when declaring properties and methods is particularly helpful down the road when you need to extend a class to modify or wrap some portion of its original behavior. It is possible to override private class members, but doing so often requires completely reimplementing a large amount of the original class’s behavior, which can be error-prone and introduce many side effects, as well as making it more difficult to maintain.

Liskov substitution

When extending classes, it’s important to keep in mind that the class may be used in a variety of ways, especially when building a library that’s used in multiple projects. To avoid breaking implementations, keeping the public behavior consistent is critical.

A common point of concern is method signatures. Given a method that expects a certain list of parameters, extending that class should not change the order, type, or structure of the parameters. When it is necessary to supplement the parameters, ensure that behavior when called with parameters designed for the original implementation still behave as the parent class does.

Here is a simple example of extending a class while preserving the initial behavior:

class ObjectA
{
    public function updateCount(int $count): void
}

class NotifiableObjectA extends ObjectA
{
    public function updateCount(int $count, bool $notify = false): void
}

The default value on $notify is required to prevent callers from

One of the best ways to avoid accidentally changing method signatures is to ensure that strict types are used for arguments and return values whenever possible. PHP will enforce types being identical between implementations.

Without defined types, we could easily break this behavior without the language enforcing anything:

class ObjectB
{
    public function updateCount($count)
}

class NotifiableObjectB
{
    public function updateCount($notify, $count)
}

PHP 8 introduced named arguments which, when used, mean that the name of each argument in your method definition must also stay unchanged in new implementations. It’s also a good idea to avoid changing argument names entirely for any public methods when using PHP 8, as this can easily break functionality anywhere in an application.

There are many more complex things to be aware of to safely extend a class, but they are often more implementation-specific, and a result of poor design of the initial class. In those cases, it’s often necessary to find where the class is being used and manually verify that the behavior is not changed unexpectedly.

Interface segregation

Segregation of code goes along well with both the Single Responsibility and Dependency Inversion principles. In particular, it’s important to keep classes and interfaces small, splitting common behavior into separate interfaces.

This is particularly helpful to avoid requiring an implementer to add definitions for behavior that is not supported or is unrelated to an implementation of an interface.

For example, let’s define an interface of common features of a YouTube channel:

interface Channel
{
    public string $id;
    public string $name;

    public function listVideos(): array;
    public function listPlaylists(): array;
    public function listPlaylistItems(string $playlistId): array;
}

This works fine for YouTube, but later on if it’s necessary to support another video platform that doesn’t include playlists for example, the expected behavior of the playlist-related methods becomes a lot more complicated.

You could simply return an empty array when listPlaylists is called, but what about listPlaylistItems? Do you throw a generic exception? Do you introduce a new exception type and hope other implementations also throw the same exception? All of these options have negative side effects and can break the application and result in widely varied implementations, which is exactly what we’re trying to avoid by using an interface!

Instead, it’s best to define interfaces in a way that is more flexible for future implementations:

interface Channel
{
    public string $id;
    public string $name;

    public function listVideos(): array;
}

interface ChannelWithPlaylists extends Channel
{
    public function listPlaylists(): array;
    public function listPlaylistItems(string $playlistId): array;
}

If you architect the application this way from the beginning, code that’s aware of the playlist functionality and needs to work with playlists can easily check if it’s supported by the platform via $channel instanceof ChannelWithPlaylists.

This principle can be hard to implement effectively since you never really know what’s going to happen to your application in the long-term, but doing what you can to get this right early on can be very impactful.

Dependency inversion

Correctly structuring the layers of an application is important to avoid making things too tightly coupled and dangerous to change. For example, it is common to have a Controller that connects a DTO to a View-Model.

A View should not be accessing an HTTP request directly, for many reasons including security, validation, and extensibility, so additional layers to facilitate that flow are necessary to ensure all aspects of the HTTP request can be handled in an intuitive and maintainable way.

It is also important to use interfaces and abstract classes whenever there is a chance that an object will need to be implemented differently in another context. Doing this before you have a need for multiple implementations is important, as it’s harder to restructure an existing application than it is to design with abstractions to begin with.

Having multiple implementations is particularly common in unit testing, as testing directly against an implementation of a class may require additional things like a database, web server, or external API access. For example if a a typical implementation of a class requires accessing a third-party API, it can be undesirable for many reasons (rate limits, credential sharing, performance) to trigger those same API calls during automated testing.

Instead, it’s best to make a mock implementation of the interface, matching the behavior of the external API with hard-coded or procedurally-generated responses, and handling data in a similar way to the actual API. Changing which implementation is used in the testing context can depend on how you’re running tests and which framework you use, but if the choice is between mocking API responses and not testing API integrations at all, it’s definitely best to test them.