Two apps one process
So what if you can handle thousands of concurrent users per core? Even the front page of HN gets you what? 100 concurrent users on a good day?
More like 30.
Exactly! Most successful apps have like what 10 or 20 concurrent users at any given time? This is basically academic for most projects. What does performance give you other than knowing that your app won't need to scale past a single server? Pointless!
Well what if you could run multiple apps in one process?
Isn't that the same as a bunch of apps behind a reverse proxy?
No. It's not. Separate processes have to compete for OS resources, have their own thread pools, schedulers, heap, GC, caches and so on. Each process needs to be sized relative to the other apps running on the system.
But, in a single process you can size the process for the machine and then run an arbitrary number of apps in that process. If the apps share a process, they can share all sorts of things: a single JVM, GC, caches, buffers, http server, HTTPS certificate management, batching, game loop etc. You'd eliminate thread contention between apps. This lets you scale further with less resources!
Performance lets you run as many apps as you want in that one process.
...
Disclaimer: all my writing and code is handcrafted by me. By the end of this you may wish it wasn't.
Some numbers
If you have multiple JVMs you duplicate a bunch of fixed costs:
- Separate heaps
- Separate JIT
- Separate GC
If you have multiple HTTP servers you duplicate:
- event loop threads
- byte buffers
- queues
If you have multiple framework engines you duplicate:
- html buffers
- render threads
- fragment caches
- queues
All of this adds up to more memory, more threads and more contention. If you own all your app and don't need the isolation a processes give you, then it can be a big performance win to have one JVM, one http server, and one framework engine.
Where were the numbers?
X * n where X is you fixed cost for the above and n is your number of apps. If all of the above takes 500MB, then four apps is an extra 1.5GB.
What is an app?
A miserable little pile of secrets
Indeed. If you think about it an app at it's simplest is a domain, a database and some routes.
(def app
{:domain "example.andersmurphy.com"
:db {:name "gol.db"
:migrations migrations
:pragma-writer {:cache_size 15625}
:pragma
{:cache_size 2000
:page_size 4096
;; 1GB
:mmap_size 1000000000}}
:routes #'routes
:batch-fn #'batch-fn})
HTTPS certificate management with Clave
But, my reverse proxy handles HTTPS certs and stuff! Where is your god now?
Yes certs are a pain. But Clave: Automatic HTTPS certificate management and renewal via ACME, implemented in pure Clojure with minimal dependencies exists. I use it with aleph. But, it also works with jetty and it's simple to extend to other servers.
(clave-aleph/start-server router
(merge
config
{:http-versions [:http2 :http1]
::clave-aleph/http-options {:port 80}
::clave-aleph/config
{:domains (mapv :domain apps)
:issuers
[{:directory-url
"https://acme-v02.api.letsencrypt.org/directory"
:email email}]}}))
So no reverse proxy needed.
Bundling our apps
To bundle our apps we import them as dependencies into a separate project who's only purpose is to configure and start the server.
(ns app.main
(:gen-class)
(:require
[hyperlith.core :as h]
[billion-cells.main :as cells]
[billion-checkboxes.main :as boxes]
[game-of-life.main :as gol]
[clj-async-profiler.core :as prof]))
(defn start-app! [& {:keys [mode]}]
(h/start-server!
+ {:apps [cells/app boxes/app]
:tick-ms 100
:render-buffer-size (* 64 16384)
:email (h/env :email)
:mode mode}))
(defn -main [& _]
(start-app! :mode :prod))
So adding a new app is as simple as importing it and adding it to the list of apps:
:apps [cells/app boxes/app gol/app]
We can then build this project as an uberjar and scp it to the server.
Deployment
Well this is nice and all, but whenever you deploy an app, you restart the process. DOWNTIME! UNACCEPTABLE!
Here's a gif of me deploying the jar to production:

If a deploy is indistinguishable from network blip did the app really go down?
Conclusion
That's it. We've reduced the overhead and cost of deploying a new app to zero.
Code can be found here. All three apps are running on the same JVM, on the same 10$ VPS , here, here and here.
Thanks to Everyone on the Datastar discord who read drafts of this and gave me feedback.