Fork me on GitHub
#clojure
<
2021-07-05
>
Adie12:07:27

I want to convert a clojure service to Go. What is the most optimal way to do this?

🙀 28
Ben Sless12:07:38

Why? Performance?

p-himik12:07:51

"Optimal" in what way?

vemv13:07:40

if the clojure service has integration tests, you might hack them so that they work over http so that you can exercise the Go version is in fact equivalent to the old one

Rupert (All Street)14:07:05

If this is a big (e.g. multi-week migration) project, you could do it gradually by creating some clojure<->go interop/bridge code then slowly replacing the service piece by piece.

borkdude14:07:06

It would be interesting to hear more about the why. If you want low latency (startup) and lower memory footprint, GraalVM native-image might offer you that with the code you have right now (or minor changes)

👍 5
vemv15:07:55

forthcoming GCs promise sub-ms latency as well, so if that's the reason you might do with GC tuning for the time being and no-setup GC with newer JDKs

borkdude15:07:27

Golang also uses GC, and probably less advanced or less options compared to Java

borkdude15:07:05

although typically for Clojure programs you would generate more garbage due to lazy seqs / FP style programming

👍 6
vemv15:07:33

GOGC=off that's Go's only option :)

Ben Sless15:07:57

From my experience, Clojure applications with performance issues have a metric ton of performance lying on the floor. There are lots of easy improvements without sacrificing readability or idiomatic code

3
niwinz16:07:30

ZGC and Shenandoah gc offers SUB ms pauses. Right now we have all of our services running with ZGC with JDK16, and the 99% of pauses (the percentile 0.99) is 0.03ms (analyzing the gc log of today).

💯 6
Ben Sless16:07:02

@U06B73YQJ how big is your heap, if you don't mind sharing?

niwinz16:07:11

the concrete log is for 2gb heap, but I have similar/identical situation for services that runs with 16gb, ZGC does not depends on heap size, in fact, it has better pauses and better behavior with bigger heaps.

Ben Sless16:07:18

That's what my question was aiming at. Since Clojure is more GC intensive than other langs I was wondering what kind of heaps you need to keep with ZGC for it to not keel over under the GC pressure

Ben Sless16:07:41

Or is it Shenandoah which keels over when the allocation rate gets too high?

vemv16:07:54

Probably relevant: > Pause times do not increase with the heap, live-set or root-set size https://wiki.openjdk.java.net/display/zgc/Main

niwinz16:07:57

It depends on the application, In my opinion, shenandoah behavior under high memory pressure is better than ZGC, but has a bit higher pauses (avg 1-2ms) (only tested under JDK15, so don't know if it is improved with jdk16 changes). With JDK16, ZGC improves behavior on small heaps and removes the scalability issue related to the root size (number of threads, etc...) so the GC pause was reduced from AVG 1ms to <1ms.

niwinz16:07:45

I'm pretty sure that right now shenandoah and zgc behavior will be very similar

niwinz16:07:10

In our case, we process large json/transit objects and serialize/deserialize them constantly, generating pretty big ammount of garbage on each request

Ben Sless16:07:15

Large maps or large sequences of maps?

noisesmith19:07:49

@U01P6LWQD1S clojure and go are different enough that I don't think the answer will be a code oriented one. If I were undertaking that translation I'd start with diagram (on paper or whiteboard) of the big pieces, and the relationships between them (what they share, how they transfer data to one another, which code crosses the boundaries between those pieces). often times with real world apps the answer is that the boundaries are crossed everywhere and you don't actually have separate pieces, but in that case the process of making the diagram helps you realize how you should have written it and can guide the new version. of course this is a clojure forum so most answers are going to be attempts to argue you don't need to switch

noisesmith19:07:07

also, logging can help a lot - put the same log calls in the same places, feed the same input, and look at differences in the log output

noisesmith19:07:58

similarly, it can be useful to use the existing codebase to generate data for example based tests (look at what the existing code returns, make a test in the other impl that expects the equivalent result)

noisesmith19:07:46

and once again, if your existing codebase doesn't play well with that kind of data-in / data-out testing, that's another indicator of something to fix in the reimplementation

noisesmith19:07:09

another trick I've used is to do a "bad rewrite" - code that is not idiomatic / readable in the new language but translates 1->1 with the old one. verify that the result works, then do the "readability / not being a pile of crap" edits as a second iteration

noisesmith19:07:59

because in particular here, a lot of clojure idioms are going to be terrible / unreadable / very poor in performance when directly copied to go

noisesmith19:07:44

lazy-seqs will probably become a queue consumer(?) - I think this is enough brain dump on my part now, I just find this kind of question interesting in general

vemv19:07:19

> another trick I've used is to do a "bad rewrite" - code that is not idiomatic / readable in the new language but translates 1->1 with the old one. one would end up with "aphyr interview code" :)

noisesmith19:07:11

right, but that's not code I'd ever publish (or at least I'd delete the branch as soon as possible) :D

noisesmith19:07:39

it's an intermediate step, but breaking into steps with limited roles can be helpful with big complex tasks

noisesmith19:07:55

"do one thing at a time" and all that

👌 4
Adie08:07:10

Thanks a tonn @U051SS2EU for the detailed answer. I now have more clarity on what should be the steps to follow in the transition. thanks3

nate sire13:07:26

@U01P6LWQD1S Just wondering... are you building a micro service? will you be running it inside a container?

nate sire13:07:26

@U04V15CAJ is right that GraalVM native images can give you performant containers... but when I started coding in both Clojure and Go... Go was missing a lot of features... e.g. need to write a function from scratch just to check if an array index exists. Go's philosophy of "keep it simple" leads to tons of boilerplate (Github CoPilot to the rescue)

nate sire13:07:54

And I wanted all the tooling of a VM for performance tuning... try to pick the right tool for the job. That is just my opinion for a current project I need. Go is a nice language. Language agnostic.

nate sire13:07:07

I am reading about how GraalVM native image can also apply to serverless :)

nate sire13:07:02

I like @U051SS2EU approach. Start at high level. Possibly start with your tests if you have endpoints to test.

Adie14:07:34

I am not building a microservice. Just a client.

nate sire15:07:32

sorry... I read the word "service" and immediately thought API, containers, micro services...

clj839419:07:31

general clojure question, what's the proper way to emulate a for loop with a conditional break statement in clojure?

seancorfield19:07:30

Without knowing a bit more context and specifics, it's hard to be precise, but generally a reduce that can return reduced for the conditional "break" should work.

👍 2
seancorfield19:07:15

And that sort of question is going to get you better answers in #beginners (because the folks there have opted in to helping folks learning Clojure in depth -- which is not the case in this channel).

phronmophobic20:07:59

depending on if you want a for loop for iteration or for side effects, the for macro also accepts a :while to "break" early:

phronmophobic20:07:08

> (for [i (range)
        :while (< i 5)]
   i)
(0 1 2 3 4)

phronmophobic20:07:56

for side effects:

> (doseq [i (range)
          :while (< i 5)]
    (println i))
0
1
2
3
4
nil

seancorfield20:07:15

(which is why we encourage this sort of question in #beginners because there's a lot of context needed to get the best answer for a given situation)

Joshua Suskalo19:07:20

reduce to replicate the loop, returning reduced values when you want to early exit.

2