Fork me on GitHub
#clojure
<
2020-09-16
>
gibb03:09:05

Can I create a really tiny (in RAM & cpu usage) REST sever with clojure in some way?

dominicm11:09:16

I've done some benchmarks, httpkit can run very fast with 10M of RAM.

dominicm11:09:03

Everything fails around 8M

borkdude11:09:18

httpkit also compiles with graalvm so you could run this natively. See also here: https://github.com/kloimhardt/bb-web

dominicm13:09:17

I read that graalvm doesn't really use less memory at runtime. I haven't confirmed myself.

borkdude13:09:01

Let's put it to the test.

$ time clj -J-Xmx5m -e "(+ 1 2 3)"
6
clj -J-Xmx5m -e "(+ 1 2 3)"   2.88s  user 0.17s system 232% cpu 1.309 total
avg shared (code):         0 KB
avg unshared (data/stack): 0 KB
total (sum):               0 KB
max memory:                108348 MB
$ time bb -Xmx5m -e "(+ 1 2 3)"
6
bb -Xmx5m -e "(+ 1 2 3)"   0.03s  user 0.01s system 93% cpu 0.049 total
avg shared (code):         0 KB
avg unshared (data/stack): 0 KB
total (sum):               0 KB
max memory:                33488 MB

borkdude13:09:02

YMMV with longer running apps that do more stuff. Benchmark needed.

dominicm13:09:44

I think the comment I saw did not figure out how to set max memory on a graal binary. I'd love to test this myself.

gibb14:09:54

This is really intriguing for me because it's a path to sneak in some clj at the dayjob without risking much for the company.

gibb14:09:25

And I really, really dislike coding PoCs in Go with wildly changing requirements.

gibb14:09:03

What would Xmx translate to for a native binary?

gibb14:09:52

In my situation we run everything as some kind of container workload either via docker or some other cgroupy thing - wonder if I could get graal to respect that in some way?

borkdude14:09:29

@gbson Just put -Xmx10m as a command line arg

gibb14:09:44

Wow, do you know how that works? 😄

gibb14:09:49

that's pretty cool

borkdude14:09:35

yes, graalvm native images respect this: e.g.:

$ bb -Dmy.prop=123 -e '(System/getProperty "my.prop")'
"123"

borkdude14:09:03

(bb = babashka)

gibb14:09:18

yes I am familiar with your borking great work!

gibb14:09:26

I loev it

gibb14:09:16

I wonder if this stuff works for Graal: -XX:+UseCGroupMemoryLimitForHeap

borkdude14:09:08

$ bb -XX:+UseCGroupMemoryLimitForHeap
error: Could not find option 'UseCGroupMemoryLimitForHeap'. Use -XX:PrintFlags= to list all available options.

dominicm06:09:06

I think native can go smaller because the program isn't allocated to memory by java anymore, so there's a little extra space

dominicm06:09:24

Would be interesting to see if it could be measured as a whole

dominicm06:09:58

Then again, java binary is probably larger anyway.

borkdude06:09:48

If you want to know how GraalVM wins in terms of memory, there’s a podcast with the main author

dominicm07:09:59

Ooh, that sounds interesting. Where is that?

dominicm07:09:09

Graal or native image, just to clarify?

borkdude07:09:11

actually I listened to this one: https://airhacks.fm/#episode_78 but the above one might also be interesting

borkdude07:09:30

it's about both native and JVM

dominicm14:09:04

Preliminary testing: openjdk is the fastest when you have a healthy amount of heap (eg 128M). At lower quantities (8/9/10) native dominates, even when using a fallback binary. However, I'm yet to determine if the cost gets eaten into the binary, so the overall memory usage is potentially the same.

dominicm14:09:48

Fallback native image is 340M memory with 5M heap, so I'll have to dig in I think :)

borkdude15:09:41

fallback isn't really native, it's just a thing with a jvm in it

borkdude15:09:48

best to disable that

borkdude15:09:32

@U09LZR36F Recommended flags:

"-H:+ReportExceptionStackTraces"
       "-J-Dclojure.spec.skip-macros=true"
       "-J-Dclojure.compiler.direct-linking=true"
       "-H:ReflectionConfigurationFiles=reflection.json"
       "--initialize-at-run-time=java.lang.Math\$RandomNumberGeneratorHolder"
       "--initialize-at-build-time"
       "-H:Log=registerResource:"
       "-H:EnableURLProtocols=http,https,jar"
       "--enable-all-security-services"
       "-H:+JNI"
       "--verbose"
       "--no-fallback"
       "--no-server"
       "--report-unsupported-elements-at-runtime"

dominicm15:09:14

Thanks, I'll admit I'm struggling right now 😅

dominicm15:09:22

I'll probably stop by #graalvm

dominicm16:09:47

Reporting back, the RSS is now closer to ~15M, now that I've solved the fallback issue.

dominicm16:09:49

Memory in RAM as reported by Linux.

dominicm17:09:47

On low amounts of memory, native image without fallback completely falls apart. 133 req/s with http-kit under 10M of memory for native, but under OpenJDK that's 8708 req/s. However, the fact is that it depends on how you're hosting. On a shared java host, you might prefer OpenJDK as the heap is more important than the libraries that are brought in. On a VM (ec2, openvz, kvm, etc.) you're more interested in overall memory, so you can give native-image an extra 20M and be fine.

dominicm19:09:30

Having said that, Linux only virtually loads the whole java binary, so in theory you might get away with it (albeit with a performance hit on actual low-memory machines where you end up in swap or back on disk).

gibb03:09:37

I only need to serve a few requests per second and the IO is mostly waiting for other service calls. The payloads are tiny

dpsutton04:09:50

Really tiny for a clojure app might be 128 mb. What is really tiny for your uses?

gibb04:09:51

The competing idea is writing it in go

gibb04:09:06

128 mb should be OK

seancorfield05:09:29

I guess the other possibility would be ClojureScript, compile to JS and target Node perhaps?

andy.fingerhut06:09:01

There is also a #graalvm channel where folks work on compiling Clojure to native binaries that I believe have smaller requirements than running on the JVM, but I don't know how they compare to ClojureScript. Worth asking there.

Chris McCormick06:09:09

@gbson you could make an exceptionally small single-file runtime using clojurescript on node with e.g. shadow-cljs and then compiling to a single binary with https://github.com/zeit/ncc - the one requirement would be that the end user has a node binary on their system. if that's not a blocker for you, i have done similar things before so let me know if you want any tips on setting it up.

👍 3
Chris McCormick06:09:44

and if you don't want to require a node binary and you know the target architecture you can pkg to bundle your script + node together: https://medium.com/@tech_girl/deploy-your-node-js-application-as-a-single-executable-4103a2508dd7

Josef Richter10:09:29

liveview - wondering if there’s something like that in clojure, please? seems like this project has stalled https://github.com/prepor/liveview-clj

delaguardo10:09:24

it is not staled. we are using it in production

Josef Richter10:09:20

@U04V4KLKC looking at the examples, it seems like it’s sending the whole page html thru the websocket on every event. does that work for you the same way in production?

delaguardo10:09:18

yes, it was designed to send entire page

Josef Richter10:09:19

@U04V4KLKC hmm is there a reason for that? isn’t one of the main points of live view that it’s sending just the diff?

delaguardo10:09:25

on client side it is using simple morphdom library to rerender dom nodes that needs to change

delaguardo10:09:41

diff is tricky. you have to take in account the time between send action from client + render + send diff back. during that range client’s state can be changed bu user.

delaguardo10:09:33

that could make this diff obsolete

delaguardo10:09:55

that was the main reason to send entire page as a snapshot of current state

Josef Richter10:09:08

hmm. liveview actually designates dynamic “slots” in the template and then is sending just the values for these slots basically. not full html.

Josef Richter10:09:07

it’s also using morphdom library, just minimizing the payload to almost nothing

delaguardo10:09:59

yeah, I know about difference between phoenix’s liveview. the goal for that library was simplicity and predictability not minimization of payload. Also it was written in few days )

👍 3
delaguardo10:09:00

also you could check my PR — https://github.com/prepor/liveview-clj/pull/1/files it adds ability to register event handlers on the client to react on changes coming from backend

👍 3
delaguardo11:09:42

this “feature” theoretically should give you ability to send diffs but without any helpers. If you will need something — let me know, will try to help

mpenet10:09:26

@gbson you could also consider fennel-lang, a single binary with the whole lua interpreter + fennel bundled would hover around 400Kb + size of your code (once "compiled" to lua)

👍 3
mpenet10:09:08

not even sure you can go that low with hello world in golang

mpenet10:09:22

then sure, you'll need to add some dependencies to that, and it's definitely not clojure, even if it has some ressemblance

borkdude11:09:36

@gbson Someone is experimenting with adding httpkit server + reitit to babashka here: https://github.com/kloimhardt/bb-web It can run with low memory (like 5-30mb). bb supports JVM flags like -Xmx Not sure if this will be in bb mainline, but it could be added under feature flags, or it could appear as its own bb spin-off. Either way, in that repo there are some downloads available.

👍 3
borkdude11:09:22

One thing I am considering is adding httpkit server to babashka for the next release, but not sure yet about a routing lib.

borkdude11:09:06

There's a build of babashka with httpkit server available here: https://github.com/borkdude/babashka/issues/556. You'd have to do your own routing with this. Please leave a note in this issue if you're interested.

wombawomba12:09:47

So I have a CLI app that I’m running as an uberjar. Is there anything I can do, apart from :aot :all, to make it start faster?

wombawomba12:09:32

FWIW my long-term plan is to compile it using GraalVM, but I’m looking for a simpler solution that I can use for the time being

borkdude12:09:53

@U15RYEQPJ Does your CLI use any special libs beyond clojure itself?

wombawomba12:09:14

Well, not really… I sometimes run other tasks in the same project, and those do have a ton of dependencies, which do end up in my uberjar. But the parts I use for the CLI only depend on some libraries I’ve written myself, which in turn have no essential dependencies apart from Clojure itself

borkdude12:09:16

@U15RYEQPJ Then I think babashka will be a great fit

borkdude12:09:20

It can even run uberjars, but also from source

borkdude12:09:34

Startup is milliseconds

wombawomba12:09:22

of course you would say that 😉

wombawomba12:09:28

I’ll give it a shot, thanks!

borkdude12:09:40

Yeah, well sorry, this is exactly the use case for it :)

wombawomba12:09:15

@U04V15CAJ any suggestions on how to generate the --classpath argument for a lein project?

borkdude12:09:48

export BABASHKA_CLASSPATH=$(lein --classpath)

borkdude12:09:57

sorry, without the hyphens, so lein classpath

borkdude12:09:14

but if you just want your source on the classpath then -cp src suffices

wombawomba12:09:03

yeah, I tried that but it turns out I have a few dependencies that do need to be loaded (e.g. clojure.match)

wombawomba12:09:25

anyway, now that I’ve build an uberjar, I’m getting:

----- Error --------------------------------------------------------------------
Type:     clojure.lang.ExceptionInfo
Message:  Could not resolve symbol: definterface
Location: clojure/core/match/protocols.clj:39:2
Phase:    analysis

----- Context ------------------------------------------------------------------
35:   (syntax-tag [this]))
36:
37: ;; markers
38:
39: (definterface IExistentialPattern)
     ^--- Could not resolve symbol: definterface
40:
41: (definterface IPseudoPattern)

borkdude12:09:31

ok, core.match isn't in bb.

borkdude12:09:15

(yet, there could be support for it, but I haven't heard many people ask about it)

wombawomba12:09:50

so I’m not really using core.match, I just happen to require an ns that requires it (for a fn I don’t use) — is there a way to work around this issue?

borkdude12:09:52

@U15RYEQPJ one way: bb supports .cljc + :bb so you can exclude parts using reader conditionals.

wombawomba12:09:27

ok sweet, I’ll give that a shot

mikeb17:09:14

I think direct linking helps with startup time. Also I have been playing around with openJ9 and shared classes and it significantly helps startup times.

dharrigan13:09:56

that looks interesting, bb + httpkit + reitit