How to fix Memory Exhaustion Bugs in My Golang Web App

Earlier this year, the author created an open-source app called PicoShare.

It’s a simple Golang web app for sharing files. I use it to send files too large to be email attachments, but I don’t want the recipient to deal with Google Drive or Dropbox.

Animated demo of uploading a video file to PicoShare and streaming it in another browser window

A few months ago, I started seeing the PicoShare server die every few days. When I checked the logs, I saw an out-of-memory error:

Out of memory: Killed process 515 (picoshare)

I didn’t have time to debug the crash, so I increased the server’s memory from 512 MB to 1 GB. And then I kept seeing crashes, so I increased it to 2 GB.

It’s unsatisfying to fix a crash by just throwing more RAM at the problems, so I’ve been debugging the crashes for the past two weeks and sharing my progress on Twitter.

At this point, I’ve fixed all the issues causing crashes and learned useful lessons about Go, SQLite, and debugging.

Check out the Twitter thread if you want to see the story as it unfolded. Read on if you’d like a cleaned-up, condensed version of what I learned.

Preface: I use SQLite strangely

One of the strange architecture decisions I made with PicoShare was to store all file data in SQLite. This is an unusual choice, as web applications typically store file uploads directly on the filesystem, not in a database. This is especially true when the uploads can be arbitrarily large.

The advantage of writing file data to SQLite is that all of PicoShare’s application state is in a single database. That’s not so special, but I designed PicoShare to integrate with Litestream, a tool for replicating SQLite databases to cloud storage. Litestream essentially gives PicoShare backup and restore “for free.” I can completely blow away a server and then redeploy it anywhere (even another cloud hosting provider), and PicoShare will wake up with the same state, serving all the same files.

The debugging process

Reproducing the error

I only saw PicoShare crash every few days, so my first step was to find a way to force the crash more quickly.

I managed to reproduce the error by deploying PicoShare on a Fly instance with only 256 MB of RAM and then uploading large files. I used high-resolution versions of the short film Big Buck Bunny with sizes ranging from 268 MB to 617 MB.