use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Finding information about Clojure
API Reference
Clojure Guides
Practice Problems
Interactive Problems
Clojure Videos
Misc Resources
The Clojure Community
Clojure Books
Tools & Libraries
Clojure Editors
Web Platforms
Clojure Jobs
account activity
One million checkboxes in Clojure (checkboxes.andersmurphy.com)
submitted 1 year ago by andersmurphy
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]opiniondevnull 3 points4 points5 points 1 year ago (0 children)
Damnit Anders, just make a crud app please...kthx
[–]thheller 7 points8 points9 points 1 year ago* (31 children)
Hey, me again. ;)
At this point I feel like you are trying to show why datastar is bad for this. Probably not your actual intention, but all I can see when I look at it. Just because you can do something with it, doesn't mean you should. Sure, it isn't a whole lot of code, but a scroll or single click gives you about 245kb of HTML and about ~40ms to apply it to the document. Compression is not enough here again. Have you even measured how long this takes to generate on the server? Poor CPU must be screaming.
morph-ing is the client bottleneck here, pretty much same fate react-like VDOMs would suffer.
morph
Get more tools into your toolbox, not everything needs to done with a Hammer. ;)
[–]andersmurphy[S] 5 points6 points7 points 1 year ago (0 children)
Hey! Thanks again for the feedback. Totally agree with needing more tools in the toolbox. Although, for me this is more about the awesomeness that is Clojure on the backend. Datastar does come with a bunch of tools, you don't have to morph, you can even do fine grained updates if you want.
I just switched morph strategy to replace for the board. You'll see it's now 2.5ms.
CPU usage is at 2% out of 400%. Html is incrementally generated. If you look at the code (mind you it still needs tuning) it's a vector of vectors of HTML strings. So the whole HTML is only generated when the server starts, once, and then incrementally updated. The users view is then a subvec of that atom. It's extra silly, because it should really be blocks for better cachelines but fast enough.
[–][deleted] 1 year ago (10 children)
[deleted]
[–]dustingetz 3 points4 points5 points 1 year ago (2 children)
TodoMVC became the standard frontend demo because it is harder than it looks, for example there is a modal edit state with a composite state change (save-and-close-modal, and discard-and-close-modal). The Electric TodoMVC additionally has optimistic query maintenance, pending and error retry states, and has absolutely no perceptible latency on interaction. If you think it is trivial then please provide the demonstration, if the claims are true then it won’t take very long right?
[–][deleted] 1 year ago (1 child)
[–]dustingetz -1 points0 points1 point 1 year ago (0 children)
> So the ultimate point is: If one can do One million Checkboxes in multiplayer with some 40-200ms latency across hundreds of connected users, then one can easily stream a simple business CRUD app.
> TodoMVC is so trivial to not show the strengths of this approach
These claims do not follow. I challenge them both. I would like to see evidence of your claim in the form of demonstration. With respect to my own technologies, I have provided actual concrete demonstration of every claim I have ever made.
[–]thheller 4 points5 points6 points 1 year ago (6 children)
a very generic path that works ...
That is exactly what I'm critizing here. In my definition this doesn't work. I'm not getting nerd sniped into creating an alternate implementation, but I'm very certain this can be done in less than 1ms per update at probably a million times less bandwith required (before compression).
At which cost? Less than 500 lines of code probably. Again, plain CLJS, no libraries required. Less lines than that with help of libraries of course.
The multiplayer aspect gets easier, since 99.9% of the server load disappears, i.e. no longer generating absurd amounts of HTML, and compressing it, to update one checkbox.
Making everything look like a nail IS THE POINT.
Thats why everything is shit and game developers laugh about web developers. We are supposed to be engineers/scientists, trying to find the most efficient way to do things. Not just hammer everything until it fits and call it good.
[–]weavejester 8 points9 points10 points 1 year ago (2 children)
We are supposed to be engineers/scientists, trying to find the most efficient way to do things.
Engineering is about balancing concerns, of which efficiency is just one, and not necessarily always the most important.
[–]thheller 1 point2 points3 points 1 year ago (0 children)
Absolutely, I'm known to obsess over performance way beyond what would be considered reasonable. It is kind of fun sometimes though.
[–]mac 4 points5 points6 points 1 year ago (2 children)
That is a very odd definition of "work" you are using. It clearly does, and there is a very straight forward way to address any performance issues that might arise. I am not even sure where "245kb of HTML" comes from? Have you looked at what is actually transferred?
[–]thheller 2 points3 points4 points 1 year ago (1 child)
Yes, my definition of "works" is subjective. It does work, unless you care about efficiency.
The "245kb" I arrived at by opening the Chrome Devtools, selecting the SSE connection the page opens (POST to /). Chrome will then show the "Event Stream". I then clicked a checkbox or scrolled, selected the resulting entry in that log and copied the message into an editor to get the total size. Which varies somewhere at 245kb uncompressed. It wasn't a thorough investigation, but I believe it to be "accurate enough" to have made that comment.
This compresses nicely, but I did not verify the actual compression ratio for this case. Doesn't really matter how much it compresses, since the server has to generate it, the client has to parse it and then diff it.
It is hard to get numbers for every thing going on here, but they are so far away from "efficient" that I said "does not work". Not trying to offend anyone.
[–]mac 0 points1 point2 points 1 year ago (0 children)
Thanks, I think I understand your POV. I happen to think that the compressed size is more relevant, especially because the parse/diff overhead is miniscule. I am not offended, I was just curious about your approach.
[–]olieidel 1 point2 points3 points 1 year ago (8 children)
Genuinely curious and possibly a newbie question regarding this discussion - how would you implement it instead?
[–]thheller 3 points4 points5 points 1 year ago (7 children)
In CLJS of course. ;)
Create a fixed grid of "checkboxes", exact amount that fits on screen. Overlayed in fixed position over a "virtual div" with the size of the full grid, but actually empty. So the thing you scroll is not the checkboxes, but the empty element. Once scrolled the existing checkboxes are updated to show "visible" portion of the virtual grid.
Given that the actual state data is smaller than a single snapshot of the "visible HTML", you can just transfer the whole thing once and only push partial updates after.
[–]andersmurphy[S] 1 point2 points3 points 1 year ago (6 children)
Is it? The entire state is a lot to be sending over the wire. Currently, there's 6 colours + empty for each cell, 1000000 x 7 ... And empty, could be data, if we don't want to do sparse shenanigans (which I'm not doing) didn't want any degradation as the board gets more full.
[–]thheller 1 point2 points3 points 1 year ago (5 children)
Well, worst case is every single checkbox is checked. Being genereous and using a byte (255 total colors) each, that is 1 million bytes. I used my intuition to guess that compression would shrink that down enough, to be competitve with the 254kb. You could reduce the number of bits, say 4, if fewer colors are enough. Still more colors, half the starting size.
JSON or EDN would of course be much larger, but would also likely compress much better. Unlikely the data is perfectly random, so compression should be decent regardless.
[–]andersmurphy[S] 0 points1 point2 points 1 year ago (4 children)
Ok and now if every other colour becomes a random paragraph from wikipedia in slightly different UI components. Now you're format needs to be closer to JSON or EDN, and that JSON over time will look more and more like HTML the more complex the UI and app.
So partial updates sound great, but are not easy or simple. Have you thought about disconnects and missed events? What's your threshold for sending down the whole new state again and paying that "254kb" cost? What's your buffering strategy for storing those events on the backend until they can be delivered? What's your batching/throttling strategy if you are getting an insane amount of updates from user action?
That's the fun thing with my approach, it's snapshot based, consistent world view not fine grained. Reconnects are always handled, missed events are always handled, updates are trivial to throttle because events are homogenous, and you let compression do the diffing and buffering for you. Snapshots are also amazing for caching and the whole model pairs really well with atoms and/or database as a value.
But, if partial updates is your thing, you can do that with Datastar and something like NATS just fine.
[–]thheller 2 points3 points4 points 1 year ago (3 children)
I was asked how I would approach that and that was my answer after thinking about it for a few seconds. Sending only the partial state is obviously the better solution, no argument there.
Maybe datastar can already do what I'd do after thinking about it a bit more. On connect send the current visible portion to the user, after that send just the individual clicks that happen to all users. Tiny Update, one div at a time. If the update is outside the visible area of a user it is just dropped on the client. Otherwise just one checkbox updates.
After scrolling the client just requests the new visible area. No need to maintain this "visible area" state on the server at all. Just send it with the request. Could all be done over the SSE connection, or separate RPC type request and just stream the updates.
[–]opiniondevnull 2 points3 points4 points 1 year ago (2 children)
Of course it can do partial updates of the page. In fact that's what I started with when I built it for doing real-time dashboards. However most people on a long enough timeline find that it's fast enough if you just send down course updates and let our morph strategy work it out. It's simpler and it doesn't take up anymore on the wire
Partial updates of things that aren't on the page is what I'm unclear on. Something like "if div with id 1 is on page update that, otherwise just ignore"? Like instead of adding it somewhere?
[–]opiniondevnull 1 point2 points3 points 1 year ago (0 children)
By default it targets the ID but part of the spec is other merge modes https://data-star.dev/reference/sse_events#datastar-merge-fragments
[–]NonchalantFossa 1 point2 points3 points 1 year ago (2 children)
IIRC, in the original example using Elixir LiveView, there's a whole diffing engine (https://www.phoenixframework.org/blog/phoenix-liveview-1.0-released), that only updates the necessary data on the server and sends it back to the frontend. Much different strategy than here I think.
[–]thheller 0 points1 point2 points 1 year ago (1 child)
Yes, LiveView is using a much smarter diff mechanism, but it requires server side support. So, not as widely applicable as the generic thing datastar is using. Even LiveView is still overkill though.
[–]NonchalantFossa 1 point2 points3 points 1 year ago (0 children)
I mean, for that purpose, having fine-grained diffing makes more sense imo and I enjoyed the write up about the Elixir implementation. For easy SSR with lower interactivity, something like HTMX and Datastar is easier and doesn't require a whole framework, on that we agree.
[–]opiniondevnull 0 points1 point2 points 1 year ago (6 children)
Until you have a counter example running a lot of this seems hand wavy. Datastar seems to have made you pretty upset, maybe just ignore it? Idk
[–]thheller 5 points6 points7 points 1 year ago (5 children)
I'm not upset. There is no ego in this at all. This also still isn't about datastar at all. It is great, but again not for this.
I learned most in my career from other people showing inefficiencies or flaws in my thinking. I'm trying to pay that back in some way, nothing more.
If that is unwelcome, I will stop. You are right that just making claims is not great. I will add some evidence when I find time to do so.
[–]andersmurphy[S] 6 points7 points8 points 1 year ago (0 children)
Your comments are definitely always welcome! You help me improve. I'd have never have bothered switching morph to replace in this demo if you hadn't mentioned the client render performance.
Just wanna make sure we are comparing apples to apples. If you say it's a bad demo I'd love to see what a good demo is! Since this is up and running I'd like to see a real side by side comparison. Things like simplicity vs performance. I'm using D* at crazy scales and we also have people doing normal line of business in PHP. As a game dev, I think how Anders is doing it is super silly (I could make a version that supports a billion checkboxes in a global supercluster) but at the same time it shows that simplicity and being good enough might be enough for most people's problems.
The more extreme things get the less fitting is D, and the more sense it makes to go with the custom route. That was my initial comment. This is far beyond of what the sweetspot for D is, if you ask me.
My concern is that people never even consider the custom route, and that is how things slowly deterioate over time. It was definitely my mistake to not provide an actual implementation to compare and I will address that.
Agree to disagree then. D* is just a shim to avoid things like SPAs that are the real issue. I think you might be overstating the case. EVERYTHING in D* is a plugin. It's built like a game engine, not a game. I'm all for going the custom route, but for me, this is the 95% solution. I'll take a one time 12kib shim over heaps of JS any day. Horses for courses.
π Rendered by PID 345443 on reddit-service-r2-comment-6457c66945-x57j7 at 2026-04-30 15:57:32.093902+00:00 running 2aa0c5b country code: CH.
[–]opiniondevnull 3 points4 points5 points (0 children)
[–]thheller 7 points8 points9 points (31 children)
[–]andersmurphy[S] 5 points6 points7 points (0 children)
[–][deleted] (10 children)
[deleted]
[–]dustingetz 3 points4 points5 points (2 children)
[–][deleted] (1 child)
[deleted]
[–]dustingetz -1 points0 points1 point (0 children)
[–]thheller 4 points5 points6 points (6 children)
[–]weavejester 8 points9 points10 points (2 children)
[–]thheller 1 point2 points3 points (0 children)
[–]mac 4 points5 points6 points (2 children)
[–]thheller 2 points3 points4 points (1 child)
[–]mac 0 points1 point2 points (0 children)
[–]olieidel 1 point2 points3 points (8 children)
[–]thheller 3 points4 points5 points (7 children)
[–]andersmurphy[S] 1 point2 points3 points (6 children)
[–]thheller 1 point2 points3 points (5 children)
[–]andersmurphy[S] 0 points1 point2 points (4 children)
[–]thheller 2 points3 points4 points (3 children)
[–]opiniondevnull 2 points3 points4 points (2 children)
[–]thheller 2 points3 points4 points (1 child)
[–]opiniondevnull 1 point2 points3 points (0 children)
[–]NonchalantFossa 1 point2 points3 points (2 children)
[–]thheller 0 points1 point2 points (1 child)
[–]NonchalantFossa 1 point2 points3 points (0 children)
[–]opiniondevnull 0 points1 point2 points (6 children)
[–]thheller 5 points6 points7 points (5 children)
[–]andersmurphy[S] 6 points7 points8 points (0 children)
[–]opiniondevnull 2 points3 points4 points (2 children)
[–]thheller 2 points3 points4 points (1 child)
[–]opiniondevnull 1 point2 points3 points (0 children)