https://github.com/weavejester/capra 0.1.0 is an experimental new Ring adapter that's written entirely in Clojure (with the small exception of some shim classes to avoid using proxy). It currently supports HTTP/1.1 with WebSocket support being planned soon.
Interesting project! I was surprised to see jetty score so low and http-kit so well on the benchmarks, do you know if anyone has done more comprehensive benchmarking of clojure http servers?
Love the name π
http-kit itself does quite a few benchmarks, but between Ring Jetty and itself. I don't believe it benchmarks other Ring adapters. There were also a couple of Ring adapters I could still add, but they don't have releases on Clojars which makes it more difficult. While developing Capra I benchmarked frequently against http-kit, and was surprised by just how quick it is. It does take some shortcuts that Capra doesn't; but overall its a very efficient codebase.
We've used http-kit for years, it's pretty good. But I'm very keen to follow this project π
The problem with jetty is log level, default debug, they're all rendered but not emitted, takes about 95% off your throughput Found by profiling because I wondered why jetty was slow
I'll try that and see if it affects Jetty's performance in the benchmarks. Thanks for your insight, Ben.
that is a MVP level top tip right there, thanks @ben.sless. I've always stuck with jetty cause it's just so rock solid.
why did you want to avoid proxy?
I do think it's a bit irresponsible of jetty to set this log level by default on the assumption users will configure the entire mess of Java logging
I've added a logback.xml file to the base src directory:
capra.benchmark=> (require '[ :as io])
nil
capra.benchmark=> (slurp (io/resource "logback.xml"))
"<root level='ERROR'></root>\n"
But this makes no difference to the benchmarks.I'm not sure this is the complete xml
At work, we use Jetty because New Relic supports it, but does not support http-kit. We tried http-kit but lost a lot of valuable information for monitoring so we switched back. I suspect the same would apply to Capra, since New Relic wouldn't know how to instrument the code to enable observability.
(and I see another announcement thread has turned to performance! π )
Point taken though
I spent a lot of time testing performance of Capra, so this thread feels appropriate.
I was just echoing Rich's frustration with some of the recent threads. Ben knows I'm just teasing him.
I tried this logback.xml next:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="error">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Still no difference in the benchmarks, though.Regarding the Jetty benchmarks, I didn't find them too surprising. A lot of adapters based on Java web servers hover around the 30K req/s mark, and the Ring Jetty adapter is somewhat less efficient because it passes through Ring to Servlets to Jetty. rj9a also uses Jetty 12, but is faster because it doesn't use Jetty's Servlet compatibility layer.
Though of course, there's other reasons for using Jetty, as @seancorfield pointed out. And once you throw in a handler that actually does work, the difference between adapters is a lot smaller.
I think it's very appealing to have more pure Clojure alternatives, so I applaud the effort.
lol hey @weavejester why did you want to avoid proxy?
It's slower than reify. So I use reify to create a custom IInputStream instance, and then a custom class ProxyInputStream to turn it into a full InputStream.
coolio, thanks
Managed to find the logback.xml from the original thread
<?xml version='1.0' encoding='UTF-8'?>
<configuration scanPeriod='5 seconds' scan='true'>
<appender class='ch.qos.logback.core.ConsoleAppender' name='STDOUT'>
<filter class='ch.qos.logback.classic.filter.ThresholdFilter'>
<level>
ERROR
</level>
</filter>
<encoder>
<pattern>
%date %highlight(%-5level) %logger{36}: %msg%n
</pattern>
<charset>
UTF-8
</charset>
</encoder>
</appender>
<root level='ERROR'>
</root>
<logger name='hello'>
<appender-ref ref='STDOUT'>
</appender-ref>
</logger>
<logger name='user'>
<appender-ref ref='STDOUT'>
</appender-ref>
</logger>
</configuration>
I put it under src/logback.xml
I added the logback.xml you posted to the classpath, but it hasn't improved the Jetty benchmark significantly. It may be that the logging only becomes a significant factor when there's more simultaneous connections, or perhaps later versions of Jetty fix the default behaviour.
I'll do some investigating and follow up in #performance
The Capra benchmarks are all in the repository, so feel free to use them if that's useful.
New release 0.31.0 of ClojureCUDA, a Clojure library for parallell GPU computing https://clojurecuda.uncomplicate.org Several improvements in the last version, including the support for primary context. In the past few years, I released many versions, but somehow didn't regularly posted announcements, so maybe I'd better start doing that again, since GPU computing is hotter than ever, and not everyone knows that we have had elegant AND powerful support for that in Clojure for many years.
Would it be a good idea to make it a drop in replacement? I.e using exactly the same names and semantics. Then you could just (:require malli.core) in .cljc files and it would work everywhere :^)
https://github.com/willcohen/worker-router 0.0.1 β a http://github.com/squint-cljs/squint-based worker pool via http://github.com/GoogleChromeLabs/comlink, works on both browser and node It gives single-threaded JS throughput in the spirit of parallel, multithreaded use of one or more thread-safe native libraries: load N instances of a module (like a WASM module) across N workers and route each call to the least-busy one.
https://github.com/replikativ/pretrained-rstr β run pretrained HuggingFace models natively on the JVM: text embeddings, speech-to-text, and decoder LLMs. No Python, no native runtime. It loads weights directly from safetensors, quantizes to int8/int4, and runs on CPU or Intel GPU.
(require '[pretrained.embed :as emb] '[pretrained.asr :as asr] '[pretrained.lm :as lm])
(emb/embed-texts (emb/load-embedder :qwen3-embedding-0.6b) ["a durable Datalog database"])
(asr/transcribe (asr/load-asr :moonshine-streaming-medium) "voice-note.oga")
(lm/generate-text (lm/load-lm :gemma-3-270m-it) "The capital of France is" 20) ; +{:gpu? true}
(load-X :key) auto-downloads from HF; models run on CPU and, where wired, resident Intel-GPU (Level Zero/OpenCL). Validated against the reference impls: token-exact LLM decode, character-exact Moonshine transcripts (WER 1.62% == HF torch), cos β₯ 0.999 embeddings vs torch f32.
It's built on https://github.com/replikativ/raster, our typed-dispatch numerical compiler. The JVM has no efficient path for quantized (int8/int4) kernels β no VNNI/dp4a intrinsics, no primitive-float story, so raster got a native CPU-C backend that emits C for the quantized operators and compiles them natively. CPU decode matches llama.cpp, all inside one Clojure compiler pipeline that also targets OpenCL, JVM and wasm.
Planned:
β’ CUDA + HIP (AMD) GPU backends in raster
β’ LoRA/QLoRA fine-tuning (finetune-rstr) β gradients flow through the frozen quantized base via raster's own reverse-mode AD
It's experimental, the API is still moving, which is why feedback would be helpful:
β’ Interface β is (load-X :key) β task-verb good? What would you change?
β’ Models β which models/tasks do you need? (more embedders, multilingual, larger LLMs, rerankers, visionβ¦?)
β’ Backends β beyond CUDA/HIP, what hardware/targets matter to you?
Join #simmis to discuss, where we also use it in our agentic simulation engine. It feeds directly into https://github.com/replikativ/proximum/ (vector DB) and https://github.com/replikativ/umap-rstr (2-D layouts) for example.I don't know if there is such a thing as common practice for basilisp libraries π
I just think it's useful for libraries to work cross-host :^) Basilisp supports cljc, e.g: https://github.com/olavfosse/context/blob/main/src/no/olavfosse/context.cljc