clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
gibb 2020-09-16T03:56:05.453100Z

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

dominicm 2020-09-16T11:18:16.470800Z

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

dominicm 2020-09-16T11:19:51.471200Z

https://dominic.io/post/low-memory-hello-world/

dominicm 2020-09-16T11:20:03.471400Z

Everything fails around 8M

borkdude 2020-09-16T11:42:18.474100Z

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

dominicm 2020-09-16T13:01:17.481400Z

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

borkdude 2020-09-16T13:07:01.481600Z

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

borkdude 2020-09-16T13:12:02.481800Z

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

dominicm 2020-09-16T13:18:44.482100Z

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.

gibb 2020-09-16T14:31:54.483Z

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.

gibb 2020-09-16T14:32:25.483200Z

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

gibb 2020-09-16T14:35:03.483600Z

What would Xmx translate to for a native binary?

gibb 2020-09-16T14:35:52.483800Z

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?

borkdude 2020-09-16T14:36:29.484Z

@gbson Just put -Xmx10m as a command line arg

gibb 2020-09-16T14:36:44.484200Z

Wow, do you know how that works? 😄

gibb 2020-09-16T14:36:49.484400Z

that's pretty cool

borkdude 2020-09-16T14:37:35.484600Z

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

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

borkdude 2020-09-16T14:38:03.484800Z

(bb = babashka)

gibb 2020-09-16T14:38:18.485Z

yes I am familiar with your borking great work!

gibb 2020-09-16T14:38:26.485200Z

I loev it

gibb 2020-09-16T14:43:16.485500Z

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

borkdude 2020-09-16T14:46:08.485700Z

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

dominicm 2020-09-18T06:34:06.012200Z

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

dominicm 2020-09-18T06:34:24.012400Z

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

dominicm 2020-09-18T06:47:58.012600Z

Then again, java binary is probably larger anyway.

borkdude 2020-09-18T06:49:48.014500Z

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

dominicm 2020-09-18T07:27:59.014700Z

Ooh, that sounds interesting. Where is that?

dominicm 2020-09-18T07:28:09.014900Z

Graal or native image, just to clarify?

borkdude 2020-09-18T07:33:11.015600Z

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

borkdude 2020-09-18T07:33:30.015800Z

it's about both native and JVM

dominicm 2020-09-20T14:37:04.145800Z

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.

dominicm 2020-09-20T14:55:48.146Z

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

borkdude 2020-09-20T15:43:41.146400Z

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

borkdude 2020-09-20T15:43:48.146600Z

best to disable that

borkdude 2020-09-20T15:44:32.146800Z

@dominicm 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"

dominicm 2020-09-20T15:45:14.147100Z

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

dominicm 2020-09-20T15:45:22.147300Z

I'll probably stop by #graalvm

dominicm 2020-09-20T16:32:47.150700Z

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

borkdude 2020-09-20T16:33:15.150900Z

RSS?

dominicm 2020-09-20T16:45:49.154800Z

Memory in RAM as reported by Linux.

borkdude 2020-09-20T16:46:57.155100Z

ah

dominicm 2020-09-20T17:05:47.155900Z

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.

dominicm 2020-09-20T19:03:30.168100Z

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).

gibb 2020-09-16T03:58:37.454200Z

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

dpsutton 2020-09-16T04:02:50.455100Z

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

gibb 2020-09-16T04:05:51.455900Z

The competing idea is writing it in go

gibb 2020-09-16T04:06:06.456300Z

128 mb should be OK

seancorfield 2020-09-16T05:16:29.458100Z

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

2020-09-16T06:26:01.459600Z

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 McCormick 2020-09-16T06:34:09.461600Z

@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.

👍 1
Chris McCormick 2020-09-16T06:38:44.462500Z

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 Richter 2020-09-16T10:31:29.463900Z

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

flowthing 2020-09-16T10:37:43.464100Z

https://github.com/tatut/ripley (alpha, though)

Josef Richter 2020-09-16T10:41:01.464500Z

@flowthing nice, thank you!

2020-09-16T10:41:24.464700Z

it is not staled. we are using it in production

Josef Richter 2020-09-16T10:43:20.464900Z

@delaguardo 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?

2020-09-16T10:44:18.465100Z

yes, it was designed to send entire page

Josef Richter 2020-09-16T10:45:19.465300Z

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

2020-09-16T10:45:25.465500Z

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

2020-09-16T10:46:41.465700Z

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.

2020-09-16T10:47:33.466Z

that could make this diff obsolete

2020-09-16T10:47:55.466200Z

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

Josef Richter 2020-09-16T10:51:08.466900Z

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

Josef Richter 2020-09-16T10:52:07.467300Z

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

mpenet 2020-09-16T10:54:26.468500Z

@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)

👍 1
2020-09-16T10:54:59.468900Z

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 )

👍 1
mpenet 2020-09-16T10:55:08.469200Z

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

2020-09-16T10:56:00.469700Z

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

👍 1
mpenet 2020-09-16T10:56:22.470300Z

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

Josef Richter 2020-09-16T11:06:54.470600Z

@delaguardo nice!

2020-09-16T11:19:42.471Z

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

Josef Richter 2020-09-16T11:26:33.471600Z

@delaguardo thank you!

borkdude 2020-09-16T11:38:36.472300Z

@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.

👍 1
borkdude 2020-09-16T11:40:22.473300Z

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

borkdude 2020-09-16T11:41:06.473600Z

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.

wombawomba 2020-09-16T12:08:47.476Z

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?

wombawomba 2020-09-16T12:09:32.476100Z

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

borkdude 2020-09-16T12:13:53.476300Z

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

wombawomba 2020-09-16T12:19:14.476800Z

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

borkdude 2020-09-16T12:20:16.477Z

@wombawomba Then I think babashka will be a great fit

borkdude 2020-09-16T12:20:20.477200Z

It can even run uberjars, but also from source

borkdude 2020-09-16T12:20:34.477500Z

Startup is milliseconds

wombawomba 2020-09-16T12:21:22.477700Z

of course you would say that 😉

wombawomba 2020-09-16T12:21:28.477900Z

I’ll give it a shot, thanks!

borkdude 2020-09-16T12:21:40.478100Z

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

wombawomba 2020-09-16T12:32:15.478600Z

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

borkdude 2020-09-16T12:32:48.478800Z

export BABASHKA_CLASSPATH=$(lein --classpath)

wombawomba 2020-09-16T12:33:03.479Z

thx

borkdude 2020-09-16T12:33:57.479200Z

sorry, without the hyphens, so lein classpath

wombawomba 2020-09-16T12:34:07.479400Z

yup

borkdude 2020-09-16T12:34:14.479600Z

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

wombawomba 2020-09-16T12:36:03.479800Z

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

wombawomba 2020-09-16T12:36:25.480Z

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)

borkdude 2020-09-16T12:36:31.480200Z

ok, core.match isn't in bb.

wombawomba 2020-09-16T12:36:49.480400Z

ah, got it

borkdude 2020-09-16T12:37:15.480600Z

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

wombawomba 2020-09-16T12:38:50.480800Z

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?

borkdude 2020-09-16T12:39:52.481Z

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

wombawomba 2020-09-16T12:40:27.481200Z

ok sweet, I’ll give that a shot

dharrigan 2020-09-16T13:28:56.482500Z

that looks interesting, bb + httpkit + reitit

mikeb 2020-09-16T17:04:14.485900Z

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.