Latest Posts (5 found)
Stavros' Stuff 4 days ago

Switch to Jujutsu already: a tutorial

As all developers, I’ve been using git since the dawn of time, since its commands were an inscrutable jumble of ill-fitting incantations, and it has remained this way until today. Needless to say, I just don’t get git. I never got it, even though I’ve read a bunch of stuff on how it represents things internally. I’ve been using it for years knowing what a few commands do, and whenever it gets into a weird state because I fat-fingered something, I have my trusty alias, , that deletes the directory, clones the repo again into a temp folder, and moves the directory from that into my directory, and I’ve managed to eke out a living for my family this way. Over the past few years, I’ve been seeing people rave about Jujutsu , and I always wanted to try it, but it never seemed worth the trouble, even though I hate how hard git makes some things. I idly read a few tutorials, trying to understand how it works, but in the end I decided it wasn’t for me. One day I randomly decided to try again, but this time I asked Claude how to do with Jujutsu whatever operation I wanted to do with git. That’s when the mental model of jj clicked for me, and I finally understood everything, including how git works . I never thought a VCS would spark joy in me, but here we are, and I figured maybe I can write something that will make jj click for you as well. It also doesn’t hurt that Jujutsu is completely interoperable with git (and thus with providers like GitHub), and I can have all the power of Jujutsu locally on my git repos, without anyone knowing I’m not actually using git. The problem I had with the other tutorials, without realizing it, was that there was a fundamental tension between two basic things: The best way to explain jj to someone who knows git is to use all the git terms they already know (because that makes it easy for them), but also to tell them to think about the git terms they know differently (because otherwise they’ll form the wrong mental model). You can’t really explain something by saying “a jj commit is like a git commit, except where it’s not”, so I’ll try to do things a bit differently. This will be a short post (or, at least, not as long as other jj tutorials), I’ll explain the high-level mental model you should have, and then give a FAQ for how to do various git things with jj. Just a disclaimer before we start, this is going to be far from an exhaustive reference. I’m not an expert in either git or Jujutsu , but I know enough to hopefully make jj click for you enough to learn the rest on your own, so don’t be too annoyed if I omit something. Also, you’re going to read here some things about the way Jujutsu likes doing things that will offend you to your very core, and your first reaction will be “madness, this cannot possibly work”. When you think this, I want you to relax, it’s fine, it does work, it just means I haven’t managed to make the whole thing click together for you yet. Just read on. I’m not going to show you any Jujutsu commands here. I might refer to them by name, but I want you to understand the mental model enough to go look stuff up on your own, Jujutsu only has, like, three commands you’re going to use for everything anyway (yes, you can do everything you do with git with them). (By the way, if you’re going to be trying things out while reading this post, definitely get jjui , it lets you visually work with the repository in a way that makes everything much easier to understand.) First of all, all the basic git things you’re already familiar with are there in jj: Commits, branches, operations on those, all those things carry over, with some small differences. The main difference is in the general way the two work, jj simplifies git’s model a lot by getting rid of some inconsistencies, and makes it much easier to understand what’s going on “under the hood”, because the “under the hood” is now so much smaller and simpler, that it can just be over the hood. The mental model that you probably have with git is something like an assembly line. You take a bunch of components, you form them into a widget, you put the widget into a box, you write “General bug fixes” onto the box, seal it, and send it off, never to be seen again by anyone. That’s what git thinks of as a commit. You have some work that is The Thing You’re Working On Now, and then at some point that’s kind of done, you select which pieces of that work you want to immortalize, and you commit them, freezing them in time forever from then on. (I know you can edit commits, but this is largely git’s mental model, commits are immutable). Jujutsu, in contrast, is more like playing with Play-Doh. You take a lump, cut it into two, shape one piece into something, give it a name, change your mind, give it another name, take a bit of the second piece and stick it on the first piece, and generally go back and forth all around your play area, making changes. Jujutsu wants you to be able to go back to an old commit, change it (gasp!), go to another branch (three commits back from that HEAD), change that commit too, move whole branches of your tree to other parts of it, whatever you want. Your worktree in Jujutsu is a free-for-all where you can rearrange things as you like. Basically, in git, you manipulate the code, put it in a commit, and you’re largely done. In Jujutsu, the commits themselves are also the object of manipulation. This isn’t the most natural workflow in git, as git makes it much harder than jj does, but maybe this is the workflow you already have in git (with extensive squashing/rebasing/amending). In that case, grasping the Jujutsu workflow will probably be easier, and will make things easier for you. Yes yes, nobody wants their commits changing from under them, that’s why Jujutsu doesn’t let you easily change commits that have been pushed to a remote, you can relax now. However, if you spend a moment thinking about what I said above, you’ll probably realize that a few things need to be different from git for this to work (and they are): Indeed, Jujutsu commits are mutable (until you push them). Right now you’re thinking of commits as something that can’t change, but this is one of the things you need to accept. You can (and will) go back to a previous commit (that you haven’t yet pushed) to fix a bug in it that you just hit, and it’s as simple as checking out (jj calls it ing) that commit and making the change. You don’t have to commit again! Jujutsu does whatever it needs to do under the hood when you run the command, to you it just looks like your edits are automatically persisted in the commit, in real time. To clarify, Jujutsu doesn’t create new commits while this goes on, you just see one “open” commit that you keep making changes to your code in. Indeed, there is no staging area like git has. git splits code to either be in the repo (in a commit), or outside it (staged/unstaged). Jujutsu doesn’t have that, you are always in a commit . This is important: In git, you’re outside a commit until you create one. In Jujutsu, you are always inside a commit . Nothing is ever outside a commit, “outside a commit” isn’t a thing in Jujutsu. Even the very command in Jujutsu is an alias that adds a message to the commit you’re on, and then creates a new (empty) one that you’ll now be working on. Even when you create a new repo, you start in a commit. This is the most important difference between jj and git , and the one thing you should think a bit about, as it enables many really interesting workflows. Always being in a commit means that yes, you will have commits that are half-finished work. Maybe lots of them! I usually indicate this in the commit message, to remind myself. You are impressively perceptive for a hypothetical straw man in whose mouth I’m putting words. Exactly, commits might not have a commit message. They start out blank, and you can add a commit message at any point, whenever you have an idea of what that commit will do. It might be when you start working on it, it might be half-way through, or it might be at the end. Personally, I usually add the message at the end, but that’s just preference. Yes, since everything is always in a commit, there’s nothing to stash. In git, if you have some uncommitted changes and want to check out an old commit, you need to stash them first. In Jujutsu, since all your changes are automatically persisted in a commit at all times, you can have some new changes (which, if this were git, would be uncommitted), you can check out (or ) an older commit, then come back to your new changes in the latest commit, and they’ll all be there. If you’re going to be jumping around the tree all the time, making commits and branches, they can’t require names. Jujutsu lets you create branches by just creating a commit, you don’t need to name the branch. In Jujutsu (and in git!), branches are simply two or more commits with the same parent, it’s just that git artificially makes you think of branches as special, because it makes you name them. In Jujutsu, creating a branch is as simple as checking out the commit you want to branch from, and creating a new commit on top of it. This is one thing Jujutsu simplifies over git. In git, branches are a fairly heavy thing, you have to name them, you have the mental model of “being” on the branch, and your workflow is centered around them. In Jujutsu, you just… add a new commit, and if that commit has siblings, well, that’s now a branch. I haven’t talked about conflicts much, because, unlike git, in practice they haven’t really been anything special. Jujutsu doesn’t stop the world at all, it doesn’t even particularly complain, it just marks a commit as conflicted, but you can continue working on other places in the worktree and then later come back at your leisure and fix that commit’s conflicts! Whereas in git you have to quit what you’re doing and fix the conflicts right now , jj is more “by the way, when you have some time, let me know what this commit should look like*. The changes also cascade to all subsequent commits, which is fantastic. You only fix conflicts once, and jj takes care of the rest. Under the hood, jj automatically and transparently commits whatever you’re working on when you invoke the jj command (it can also be configured to do it on its own whenever a file in the repo changes). This is safe, as these intermediate changes won’t be pushed anywhere, but this means that you get snapshots for free! . If you’ve ever had Claude get to a working solution, but then trip over itself and mess it up, jj can help, you can use the oplog to go back to the way your repo looked a few minutes ago, even if you didn’t explicitly commit anything! Even using the or command to look at stuff will take a snapshot of your repo, allowing you to return to it if something goes wrong. No more losing unstaged changes, ever! This has saved my ass a few times already. By now you probably have lots of questions, I’ll try to answer some of them here. If you have more questions, just send them to me and I’ll add them here, along with the answer. You don’t really branch off main, in that you usually won’t need to create two commits off main, you’ll only create one. In git, we branch off of main, and now our mental model is that “we’re in that branch”. In reality, if you look at the graph on the right, it’s all still just a line, we’ve just made a mental “bend” in the graph to tell ourselves that we’re on a branch. As far as the graph is concerned, though, nothing special really actually happened, we just added more commits. The only real difference is that “main” stops at the third commit, whereas “my branch” stops at the sixth commit. Other than that, the entire history is just one line. Jujutsu, on the other hand, doesn’t care what you think. It only cares what parents, children, and siblings commits have. There are two reasons you might want to branch: To Jujutsu, this repo’s history is a straight line, so there is no actual “branching”. The only reason to have branches here is communication, so Jujutsu asks you to label the commits that you want on the branches yourself. You can see these tags on the example on the right, and it’s the same as the git example above. There are still three commits in , and three more in . Jujutsu calls these labels “bookmarks”, and they correspond to whatever git uses to tag branches. Bookmarks are what you’ll tag your commits with to tell git what your branches are. Continuing the earlier example, if we create a second commit off main, even if that’s a merge commit (a commit with two parents) that’s when the tree actually diverges. In the graph on the right, the commit where we branched off now is a parent to two commits, and history is no longer linear. This isn’t special, it’s just how things are, but this is what’s actually a real “branch” to Jujutsu. The way that git does things, ie creating a branch without history actually diverging, is just for us humans and our communication needs. Jujutsu doesn’t require you to name its branches. You can happily work without any branch names at all, and you can easily see what branch is for what from the commit descriptions. You can name them, if you prefer, but you don’t have to . This sounds a bit alien right now, but it’s actually a really nice way to work. I’m worried I’ve lost you here, but it doesn’t matter. You’ll understand all of this easily when you play around with the tree a bit in jjui. You can add a commit message at any time to the current, using the command. You can do this at any time, you can even go back to other commits and amend their messages (again with the command). You don’t! Everything is already in a commit! What you do is you interactively select some of the changes in the current commit (whether this commit is blank/new or an old commit, it doesn’t matter), and you that commit into two. Jujutsu can also do this automatically! If you have a commit with a bunch of small changes to various files, jj can these changes into the closest ancestor commit where each thing changed. This is pretty magical, as you can add a few one-liner bugfixes here and there, and jj will just automatically include them in the commits where those lines were touched. Without getting too much into specifics, you just the commit you want. This checks it out and you can make changes to it, however keep in mind that, if the commit was previously pushed to a remote, jj will give you a warning that you shouldn’t change commits you’ve pushed. jjui will make navigation around the repo really easy, so use it for checking out commits as well. You just… move it. In jjui, go to the commit you want to move, press r (for ), go to the commit you want to move it after, press enter, and that’s it. There isn’t really a soft reset, as there isn’t a staging area for your changes to be reset in. Simply check out ( ) the commit you want to edit, that’s a soft reset in Jujutsu. For a hard reset (ie to throw away a commit), you that commit. jjui will, again, make it much easier to do this. No matter what you do, you can it. Not just changes, but any jj operation, you can undo rebases, pulls, anything. You can also use the oplog (again, jjui makes this really easy) to go back to how the whole repo looked at any point in time. Don’t be afraid to try things, with jj it’s really easy to undo any mistake. Simply it and make the changes you want. There are no unstaged changes in jj. All changes are in a commit, if you want to move the changes in your current commit to another branch, simply move your current commit to the target branch by rebasing. I can never remember what “rebase X onto Y” does, so just move the commit with your changes to be a child of your branch’s tip (again, use jjui for this). To do that, you need to push a new branch. Go to the commit you want to push, then probably create a new one on top of that (I tend to create a new commit when I’m done with an old one, just so I’m remember I’m done, but this is personal preference). Then, bookmark that commit with the branch name you want to give your PR, and push the commit along with the bookmark. That’s all, now you can open the PR. Here, jj exposes the low-level operations much more than git: You need to move the bookmark on your own to the commit you want to push (git does that automatically for you), and you need to push the bookmark manually as well. This is very helpful for understanding how things work under the hood, but usually you’ll set a jj alias to do this in one step. Personally, I have an alias (which I’ll include below) to find the bookmark name, move it to the latest commit, and push. Here’s my alias config: This means I can to add jj to a git repo, and to describe the current commit and create a new one on top of it (that’s what does under the hood). is a convenience alias that: I use this a lot! Jujutsu doesn’t do anything that git can’t do, but it removes so much friction that you’ll actually end up doing things all the time that git could do, but that were so fiddly with git that you never actually did them. Creating a branch for a minute just to try an idea out even though you’re in the middle of some changes, going back to a previous commit to add a line you forgot, moving commits around the tree, all of these things are so easy that they’re now actually your everyday workflow . With git, I never used to switch branches in the middle of work, because I was too worried that stashing multiple things onto the stack would eat my work. I’d never go back to a previous commit and amend it, because here be dragons. I was extremely afraid of rebasing because I always got one conflict per commit and had to unconflict the same thing fifty times. Jujutsu gives you the confidence and understanding to do all of these things, and if you fuck something up (which I haven’t yet, miraculously!) the oplog is right there to fix everything to how it was 30 seconds ago. I hope this tutorial made sense, but I’m worried it didn’t. Please contact me on Twitter or Bluesky , or email me directly, if you have feedback or corrections. History legitimately diverges into multiple directions, or You want to communicate to other people (or to yourself) that this part of the history is different (e.g. it contains some feature). This is also the case when you want to create a new branch so you can open a PR for it. Looks backward in history Finds the last bookmark there (if this were git, this would be my branch name) Checks if the current commit has changes in it If it does, it creates a new commit Moves the bookmark to the parent commit (the one I was on before I ran the command) Fetches changes from upstream (to update my tree) Pushes the changes to the remote

0 views
Stavros' Stuff 1 weeks ago

I made a really small LED panel

I bought a really small 8x8 LED panel a while ago because I have a problem. I just can’t resist a nice WS2812 LED panel, much like I can’t resist an e-ink display. These days I manage to stay sober, but once in a while I’ll see a nice cheap LED panel and fall off the wagon. It has now been thirteen minutes that I have gone without buying LED panels, and this is my story. This isn’t really going to be super interesting, but there are some good lessons, so I thought I’d write it up anyway. On the right you can see the LED panel I used, it’s a bare PCB with a bunch of WS2812 (Neopixel) addressable LEDs soldered onto it. It was the perfect excuse for trying out WLED , which I’ve wanted to take a look at for ages, and which turned out to be absolutely fantastic. As with every light-based project, one of the big issues is proper diffusion. You don’t want your LEDs to show up as the points of light they are, we really like nice, big, diffuse lights, so you need a way to do that. My idea was to print a two-layer white square out of PLA (which would be translucent enough to show the light, but not so translucent that you could see the LEDs behind it. I also printed a box for the square to go in front of: I printed the diffuser (the white square) first, held it over the LED panel and increased or decreased the distance of the square from the LEDs until the LEDs didn’t look like points, but the colors also didn’t blend into the neighboring squares’ colors. This turned out to be around 10mm, so that’s how thick I made the box. The eagle-eyed among you may want to seek medical assistance, but if you have normal human eyes, you may have noticed that there’s nowhere in the box for the microcontroller to go, and you would be correct. For this build, I decided to use an ESP8266 (specifically, a WeMos dev board), but I didn’t want to make the whole box chunky just to fit a small microcontroller in there, so I did the next best thing: I designed a hole in the back of the box for the cables that connect to the LED panel, and I glued the ESP8266 to the back of the box. YOLO. Look, it works great, ok? The cables are nice and shortish, even though they go to the entirely wrong side of the thing, the USB connector is at a very weird place, and the ESP8266 is exposed to the elements and the evil eye. It’s perfect. Here’s the top side, with the diffuser: And here’s the whole mini tiny cute little panel showing some patterns from WLED (did I mention it’s excellent? It is). That’s it! I learned a few things and made a cute box of lights. I encourage you to make your own, it’s extremely fun and mesmerizing and I love it and gave it to a friend because I never used it and it just took up space and then made a massive 32x32 version that I also never use and hung it on my wall. Please feel free to Tweet or toot at me, or email me directly.

0 views
Stavros' Stuff 5 months ago

I couldn't wait for a TRMNL device, so I made my own

Some time ago, my friend George linked me to TRMNL , a new battery-powered e-ink display with an associated service that generates the images that the display will actually show. It looks really well-made, and I have an irrational attraction to e-ink displays, so naturally I had to pre-order one. Their website is a bit confusing, and doesn’t entirely tell you what the things you’re buying are, which made the purchasing experience somewhat more painful than it needed to be. For example, along with the device, they sell a $20 “developer edition” license which “unlocks their API”. I expected this upgrade to give me API access so I could retrieve the image that’s displayed of my device, but that’s not actually the case, and you need a $50 “virtual device” license for that. Anyway, I did manage to preorder the thing, but it will take a few months to arrive, and if there’s one thing I’m not known for, it’s my patience. Having seen quite a few AliExpress listings in my time, I knew that the screen that TRMNL are using goes for under $50, and that I can get an ESP32 driver board for around $20. I would post links to these components on AliExpress, but they would probably go stale before the ink on your screen is dry, so I’ll post links to the Waveshare website: The display is a Waveshare 800x480 7.5” e-ink (get the black and white version, more colors are much slower) and the driver is a Waveshare ESP32 driver board . With those components, a LiPo battery, a charging board, a bit of designing, and some 3D printing, I could have my own TRMNL for under $80, or, if you factor in my time, around $5,000, which is a bargain. Plus, these would arrive much more quickly than the actual TRMNL, which is still months away, so I ordered and waited. This isn’t my first time working with e-ink displays, as I’ve made things like the Timeframe , the Weatherframe (which I never wrote about), and Calumny (which I also never wrote about), so I knew more or less what to expect. You see, the issue with most e-ink projects these days is that they mostly do one of two things: Basically the only projects I’m aware of that fetch an image from a server and display it on the e-Ink display using an ESP32 are the Timeframe (my project that I linked above), and the TRMNL, so I wanted to use what they built. My hardware setup was pretty simple, I just used the display and the driver. I don’t need a battery or a charger for this particular use case, so I just omitted those and connected my device to USB directly. This also means that I don’t need the display to sleep for a long time (because it doesn’t need to conserve battery), so I can have it refresh itself pretty often. In the future, I might connect a LiPo battery and a charging circuit to the driver, at which point I’ll figure out how to read the voltage so the former can report the latter to the server, but for now that won’t be necessary. TRMNL have open-sourced one part of their stack: The firmware that runs their custom ESP32 board. This firmware is pretty nice, it fetches a bog-standard PNG from a server and displays it onto the e-ink display, which is a really nice way to draw stuff onto the display. It also takes care of sleeping between updates (for consuming very little energy), reporting the device’s battery, etc, which is very nice to have. The only problem is that the firmware is pretty custom for their own board, and didn’t run on the generic Waveshare ESP32 driver I had. I thought, however, that if I could get the firmware to run on the generic, $20 Waveshare driver, that would be a great addition to the ecosystem, and it would make TRMNL more money, since you have to buy their $50 BYOD license to connect custom devices to their servers, so it’s a win-win. To clarify, using their firmware on your own hardware, you can create an e-ink device that works with TRMNL’s online service (the one that generates the screens to show), you just need to pay TRMNL $50 to use their service forever (“forever” is startup-speak for “an average of three years until our VCs force us to extract more money from you in the form of a subscription”) . I immediately set out programming and debugging, by which I mean “I pasted the error messages into Aider and let Gemini figure out what the hell was wrong”. The AI very quickly fixed all the stuff that was wrong, and the display sprang to life, showing the TRMNL logo! Isn’t the future amazing? Really, TRMNL’s firmware required minimal changes to work with the Waveshare driver. Most of the changes were just changing the pin numbers from one board’s to the others, as well as adapting for the fact that the Waveshare board doesn’t have a button or a battery, and uses an ESP32 instead of an ESP32-C3. If you want to use the official firmware with the Waveshare driver, the changes I’ve made are in my TRMNL firmware repo fork: https://github.com/skorokithakis/trmnl-firmware My patch has now been upstreamed! All you need to do to use this is to clone the official firmware and run ! Using TRMNL’s online service is very easy once the firmware is running on the hardware. All you need to do is buy the BYOD license, and claim the device by entering the ESP32 driver’s MAC address. Then your HACKRMNL will talk to the server, provision an API key for itself, and go to work! I 3D-printed a holder for the Waveshare display , but I saw a really nice project that uses the same display on a wooden base that I think I’ll copy instead. I’m really pleased with the final result, and I think the TRMNL itself will be even better, as it won’t have the annoying cable and will come with a better-looking case. That’s all for this build, I was really happy that I managed to get the official firmware working with generic components, and I wanted to share it with you in the hopes that it would help you with your own builds. As always, you can Tweet or BlueSky at me, or email me directly. I ended up not really liking the base that I printed above, and I decided to make a simpler, rectangular one. Since aesthetics dictated that the base be a bit bulky, I figured I might as well stick two 2,500 mAh batteries in it, for month-long battery life. Here’s the new design: Everything is hidden inside the base, and the whole device looks really sleek and minimal. It is a bit dangerous, because any slight amount of pressure at the top could cause leverage at the bottom, and cause the display to break, but hopefully I’ll be careful with it. However, I will say it’s really impressive to see a 1mm-thick pane of glass change appearance before your very eyes, and flash into new text and graphics. It’s exactly how a Kindle (or any other e-ink) display works, but it’s really impressive to see that happen to a thin sliver of glass, rather than a thick tablet. Anyway, to convert the board to work on a battery, we need to solve a few issues: Powering it via USB originally was simple, as you can see above (I just plugged the USB cable into the driver, and that was it), however batteries need a bit more care. Because e-ink displays consume basically no energy unless they’re refreshing, and the driver spends most of its life sleeping, you really need to make sure that none of your random components consume power, otherwise your battery is going to be wasted on things that don’t matter. To ensure minimal waste, a few things needed to be taken care of: First of all, the driver has two switches on it. One controls some resistor for driving the display, so you either set it to A or B, depending on what makes your display work, but that already works for me, so we aren’t touching that. The second switch can turn the USB to UART component off, so you can save power if you don’t want to connect your driver to USB. Since I’ve already flashed the firmware on it, and since I can update the firmware over WiFi in a pinch (since the TRMNL firmware supports that), I’m going to turn the switch off to save power. After that’s done, we need a low quiescent current step-down converter, to convert the 3.5 V-4.2 V of the battery to the 3.3V that our ESP32 and display need to work, without wasting current in the process. For this, I used a ND0603PC step-down converter with 260 μA of quiescent current, which should be good enough. This takes care of powering the driver. This was fairly simple, the two batteries are connected in parallel, so to charge them I used a TP4056 USB-C charging circuit. I just connected them to the “output” side of the charger, and it took care of the rest. Our device is now alive, but, like life, the hard part just began. We can’t have a battery-powered device with no way to read the battery voltage, we need to know how full our battery is so we know when to charge it. The TRMNL firmware makes that easy, as it can read the voltage out from one of the analog pins. Unfortunately, the ESP32 only supports up to 3.3 V on its pins, so we can’t connect the battery directly to it, or we’ll fry the ADC. We need a voltage divider, which fortunately is a very simple circuit, consisting of two resistors. I used two 47 kΩ resistors, to divide the voltage by two, which means the maximum voltage that will reach the pin will be 2.1 V when the battery is at 4.2 V. The TRMNL firmware read the battery level very accurately, and reported it back to the TRMNL service, so now I can see the exact battery level of both batteries (it’s always the same between the two, since they’re connected in parallel). That’s the circuit done! There are a few pin definitions to be changed in the software, but those are trivial (we basically only need to declare the pin we connected the voltage divider to, so the ESP32 can read the battery voltage). That’s pretty well explained in the code (it’s just a one-line change), so I won’t go into it here. Here’s the complete circuit I ended up with: If you want to print the base for your display, you can find the designs at OnShape . Thanks for reading this far, see you on the next post! It’s probably going to be about e-ink again, since I’ve made stuff I still need to write up. They use an ESP32 to drive the display, which is cheap and convenient, but they generate their own graphics on the ESP32 itself, making the project very ad-hoc. They use a Raspberry Pi (usually a Zero W), making the project pretty generic, as you can show advanced graphics or fetch files from remote servers, but that’s not very convenient because you now need to maintain a whole Linux device on your network.

0 views
Stavros' Stuff 6 months ago

Why is everything so scalable?

I’m entirely convinced that basically every developer alive today heard the adage “dress for the job you want, not the job you have” and figured that, since they always wear jeans and a t-shirt anyway, they might as well apply it to their systems’ architecture. This explains why the stack of every single company I’ve seen is invariably AWS/GCP with at least thirty microservices (how else will you keep the code tidy?), a distributed datastore that charges per query but whose reads depend on how long it’s been since the last write, a convoluted orchestrator to make sure that you never know which actual computer your code runs on, autoscaling so random midnight breakages ensure you don’t get too complacent with your sleep schedule, and exactly two customers (well, potential customers). I don’t know the exact point when everything went wrong, but I suspect it was somewhere in the 2000s, when Google introduced Map/Reduce and every developer thought “well that’s cool, I’m going to base all our production code on that paradigm, and eventually I will hopefully understand how it works”. We’ve been in “FAANG architecture by default” hell ever since. The first problem every startup solves is scalability. The first problem every startup should solve is “how do we have enough money to not go bust in two months”, but that’s a hard problem, whereas scalability is trivially solvable by reading a few engineering blogs, and anyway it’s not like anyone will ever call you out on it, since you’ll go bust in two months. Solving problems like “how do we make something people want” and “how do we make people give us more money” are questions as uninteresting to developers as “how do elderly people have sex”. In both cases, the answer is “with great difficulty and by taking risks”, but developers would much rather answer the question “how do we make our infrastructure scale to millions of users”, which, to a developer, has the same answer as the elderly sex issue: It’s not hard. You just get some AWS products, abstract the hardware away, turn all your code into functions instead of services, put a network under those services, and boom! Infinite scalability, and it costs nothing because we got a few million in AWS credits, which means it’s all free forever, where “forever” means “for much longer than we’ll be in business”. Do you know what the difference between Google and your startup is? It’s definitely not scalability, you’ve solved that problem. It’s that Google has billions upon billions with which to pay for that scalability, which is really good because scalability is expensive . Scalability is expensive because it’s complicated, and complexity doesn’t come cheap, in whatever form you encounter it. The tragedy I see these days is that building scalable services is the height of tech fashion, and every engineer wants a fashionable CV that will help her get the next job. Nobody has ever gotten a job with a CV that said “I don’t know AWS and Kubernetes, but I know how to fulfill all your SLAs for a $100/mo infra bill”, because every hiring manager has stopped reading before the comma, and that engineer has starved to death. Anyway, all this is to say that complexity is expensive, and scalability needs a whole bunch of complexity, so make sure you don’t pay the cost until you absolutely have to, and even then you should still accept some slowness if it gets you a few more months of the simple architecture, because that means you’ll be much faster than your competitors for a little longer. Look, honestly, I get it. Who among us really has the wherewithal to avoid having all the separate components of our architecture call each other in obscure and convoluted ways? Isn’t making each component its own service an elegant solution? No it isn’t, and if that was the first solution you jumped to, I don’t want to know how many separate services you managed to make a distributed monolith out of. First of all, you should come to terms with the idea that you really should deploy a monolith. Putting a network under function calls was always meant to be a horrific atrocity, one that should only be committed when you absolutely have no choice, not the first thing you do when setting up a project. And, before you ask, no, “monolith” doesn’t mean you’ll only have one web worker for your code, you can deploy the same code on multiple servers and load-balance between them. You’ll usually only have one database, though, and that setup should be enough for you all the way up to you having so many customers you really don’t know what to do with all that money. What has worked well for me is the following architecture: The above is enforced via automated checks on CI and in the git pre-commit stage. I’ve heard Tach is a good tool to enforce the above programmatically, though I haven’t used it yet. The above simple rules bring massive benefits. First of all, you’re guaranteed to always have clean separation between modules, as they can’t reach into each other’s internals, and they have to always call the designated interfaces. This means that you won’t end up with the dreaded “ball of yarn” monolith that nobody can ever debug or extend. Another incredible benefit is that you’ll be able to change any API literally whenever you want without having to version your APIs, or to worry about who’s using which version, or having to write backwards-compatible endpoints, or any of that. If you change an API and break a client, your type checker will tell you immediately exactly what you broke. You then just go to each of those call sites, change the calls to the new API, and you’re done, and that change is atomic . You can deploy the change to the module and all the callers at the same exact time! Amazing. You have rich type information at every call site. You’ll no longer have to debug HTTP endpoints with payloads that could have whatever random crap in them. Now, if your function gets called, you know exactly what’s in the input arguments, and if you’re the caller, you know exactly what you’ll get back. You’ll never have to deal with random dicts nested ten levels deep. It also kind of goes without saying that there’s a massive speed difference as well. Calling a function in your own process is a few thousand times faster than a network roundtrip, and those savings add up. This isn’t all upsides, there are some disadvantages to the method as well: It’s not as easy to scale each component on its own. If you have an authentication service that everyone calls, and which is I/O heavy, it’s hard to split that out into its own thing, because it now gets called not via the network, but via intra-process communication. You can still add more workers to your entire monolith, as well as scaling each worker vertically, but you can’t add more resources to a specific module, you’ll have to add more resources to all of them. This hasn’t been such a big problem for us in practice, since you usually can just get a bigger server (and that’s what you should do), but it is a downside. That’s it for downsides, really. By far the most horrified question I get when I describe this architecture is “what, we’ll have to code in a monorepo ?! ( disgusted face )”. No, no you don’t, you can deploy your modules to their own repos and work on them that way, though then you lose the nice property of being able to atomically deploy changes across your entire codebase when an API changes. That’s a tradeoff you’ll have to decide for yourself, though. All in all, this approach has worked well for us. The main gist of the matter, though, is this: Avoid paying the cost of a distributed architecture for as long as you can. Everybody else pays it up-front, and almost nobody gets a positive ROI on that, so you’ll be very far ahead of the pack with just this one simple trick. If you have any feedback or hatemail, tweet or toot at me, or email me directly. All the code that’s part of our architecture goes into the one monolith we have. The monolith is composed of separate modules (modules which all run together in the same process). Modules cannot call each other, except through specific interfaces (for our Python monolith, we put those in a file called , so other modules can do and call things that way. All of these interfaces are statically typed. What the functions accept and what they return are statically typed, with types usually being Pydantic classes (no passing a bunch of opaque dicts!).

0 views
Stavros' Stuff 1 years ago

Making the Strofara

This is going to be pretty specific to a Greek audience, as it’s all based on a Greek meme video, but I’ll try to explain. Watch the video first so you know what I’m talking about while I describe it: Two guys are driving on a road near a remote village in Crete, and the guy on the passenger seat is excited about the prospect of speeding around the turn. At some point, he exclaims “wow, what a turn, come on Giorgi!”, and Giorgis speeds into the turn and they promptly crash and tumble, with perfect comedic timing. (They were both unharmed, by the way, and their friends made a joke video commemorating the feat). I recently saw the video, and thought it was pretty funny, so it was kind of stuck in my head. One day, while driving, I took a turn, and thought of the guy saying the phrase he became famous for, and then thought it would be funny if my car could, autonomously, encourage me in a similar way whenever I took a big turn. I realized that it would be fairly easy to detect turns, using an accelerometer, and I had a board that can play MP3 files off a MicroSD card, so I figured I’d cobble them all together and see if it worked. I didn’t want to play the audio through the car’s speakers, I wanted the audio to come from somewhere inside the car, so I decided to use a speaker and bury it somewhere in my car’s insides, to pleasantly surprise any second-hand owner that might eventually buy the car, long after I’ve forgotten that this thing is in its bowels. I used an MPU-6050 accelerometer breakout board, it was really easy to connect it to an ESP8266 , and with some sample code and existing libraries, I was quickly on my way to detecting turns. I used the X axis component of the accelerometer, as that’s the lateral acceleration component in the way I was planning to mount it, and wrote some code to detect whether I’m in a tight turn for longer than half a second. If both those conditions are true, then a GPD2856A board is used to play the actual audio file. The GPD2856A is pretty convenient if you want to make an MP3 player, as it includes all the volume/play/pause/repeat/whatever controls you need for a standalone player, but pretty inconvenient if you just want to use a microcontroller to play an audio file, as the GPD2856A won’t interface with the microcontroller at all. All it does is play files in sequence (and in a loop) when it starts up, forever. This wouldn’t do me any good, as I just want to play one file once, so I hooked it up to an N-MOSFET so I can turn it on and off with the ESP8266. When I want to play the file, I tell the MOSFET to turn on the entire board, and when I want to stop, I just tell it to turn off. This works decently, but the board has some timing issues that mean that it doesn’t always start playing from the very beginning of the file, but sometimes skips half a second at the start. This means that the “wow” at the beginning is cut off, which is less than ideal. The way I fixed that is to simply insert half a second of silence at the start of the audio file. This means that I consistently get the whole phrase, but there’s a half a second delay, which might mess with my comedic timing while driving. I’ll have to test that and see, but, if it doesn’t work, my only other alternative will be to use an ESP32 and a proper driver. The board plays over a fairly big speaker that my friend Agis salvaged from some twenty-year-old PC speakers he had, and it’s really loud. I thought the USB current wouldn’t be enough to drive any significant volume, but it’s loud enough to startle me the first time the audio played over the speaker. After connecting the ESP8266, the accelerometer, the MP3 player, and the speaker together, the only thing that remained was an enclosure worthy of such a legendary device. I decided to not bother with that, and instead designed a simple square case, though I did emboss the immortal words all around it. I quickly printed the enclosure out, and after some creative glueing, lamenting that I had measured wrongly and it was 1mm too short to fit everything, and printing it again, the Strofara was complete! You can see a photo of it to the right, it looks every bit as regal in real life as it does in that photo. On the middle bottom of the right face, you will notice the USB connector that powers the device (and also gives me access to the ESP8266 if I need to reprogram it). Of course, what would this post be without a taste of the actual, finished product, complete with audio? Just text. Here’s a video demonstration, by yours truly, in Greek (you don’t really need to understand anything I’m saying, it’s all random filler anyway): </lite-youtube> Amazing, isn’t it? As with mostly everything else, the code is open source, though there isn’t really much code to speak of: https://github.com/skorokithakis/strofara If you have any comments or feedback, please tweet or toot at me, or email me directly.

0 views