18 ms·
This partially might be more of a what's old is new again, but here's what I use: - 100% server side rendered - Progressively enhanced (fully works without JS
by kouteiheika 6y ago
This partially might be more of a what's old is new again, but here's what I use:
- 100% server side rendered
- Progressively enhanced (fully works without JS, but having JS enabled makes it nicer)
- In select places where I need SPA-like snappiness I use a ~100 line long DOM diffing algorithm (I request full HTML through AJAX and diff it into the current page; if the user has no JS enabled then the page just refreshes normally)
- Running on a single, cheap VPS; no Docker, no Kubernetes, no serverless
- No SQL database; I just keep everything in memory in native data structures and flush to disk periodically; I have a big swap file set up so that I can keep a working set bigger than the RAM
- Written in Rust, so it's fast; I can support tens of thousands of connections per second and thousands concurrent users on my cheap VPS
- marshmellman 6y agoWhat are “native data structures”?
- blackoil 6y agoNot OP, but we had a similar project long back. All data is in maps and structs. We didn't had free text search, all filters had a corresponding Map of key to []ids. A primary map from id to object. Needs lots of RAM, but that is the cheapest thing we can add
- unixhero 6y agoWhatever data types are available in the programming language of choice here; Rust.
- kouteiheika 6y agoNormal structs, `Vec`s, `HashMap`s, `HashSet`s, etc. Basically whatever is natively available in Rust.
- deleted 6y ago[deleted]
- deleted 6y ago[deleted]
- boyter 6y agoI do this a lot these days with prototypes and poc’s but usually with Go since I’m more familiar with it. Sure it only works on one machine, but it’s usually so fast scaling means it was a success and now you have a war chest. I’d add adding s3 to backup things though.
- jasfi 6y agoNo SQL database is the one thing I disagree with. What if you want to write a SQL query/report? Do you have transactions? As the app gets bigger you might want that.
- onion2k 6y agoAdding in a service like a SQL database because "you might want that" in the future is not a good idea. Apps should do what they need to do and no more. "Future proofing" is almost always a waste of time - even if you definitely want the feature in the future it's usually better to add it in when it's needed than adding it early and have to support something that adds no value yet. As for reports and transactions specifically, noSQL databases support both.
- d1sxeyes 6y agoOP isn't talking about noSQL databases, but no database at all (if I understand correctly - OP says '- No SQL database; I just keep everything in memory in native data structures'. In this case, I don't think OP is even thinking about noSQL databases.
- kouteiheika 6y agoCorrect. Sorry, I should have worded that better; unfortunately I cannot edit the post anymore.
- arkitaip 6y agoUnless, of course, switching to SQL turns out to be a major headache because it requires major rewrites, organizational changes, etc.
- zepolen 6y ago> "Future proofing" is almost always a waste of time Not really, unless you lack the experience to know what to future proof and what not to future proof. I've saved days/weeks of development by future proofing certain things, I've also saved days/weeks of development by taking pragmatic YAGNI in the right places. Obviously this will only surface on projects that last longer than a couple months ;)
- y2bd 6y agoWhat kinds of things have you made with your setup?
- ubertoop 6y ago> I just keep everything in memory in native data structures and flush to disk periodically; Can you expand on what you mean by this? I am interpreting that to mean that you are actually writing to a file and reading from a file rather than using SQL?
- emteycz 6y agoThey're storing their data in native Rust objects and read or write them in server application RAM. In case data becomes larger than available RAM, they have set up a large swap, which is a file or a disk partition that the OS uses when no more real RAM is available, while acting like nothing is happening (and everything gets 1000x slower). They periodically serialize these in-memory objects to a file on disk, and presumably (they didn't specify, but I feel it's implied) they're reading this file and deserialize it during initialization/startup phase.
- kouteiheika 6y agoCorrect. Although I don't read everything on startup. Some of the data (e.g. per user data) is only loaded once it's needed. Also, the swap doesn't make everything slower as long as my active working set is smaller than the available RAM. That is, I have more data loaded into "memory" than I have RAM, but I'm not using all of it all the time, so things are still fast. It's basically an OS managed database. (If I were using a normal SQL database I'd get a similar behavior where actively used data would sit in RAM and less used data would be read from the disk.)
- pastage 6y agoThe obvious difference is that swap files are not as optimized for disk io. There is a serious performance issue if you ever have to hit disk often. It all depends on how you structure your in memory/on disk data. But it might never be an issue, have scaled to tenish million user in memory there is so much else that can go wrong first.
- iKevinShah 6y ago> I use a ~100 line long DOM diffing algorithm Since this is not vDOM diffing (which most JS frameworks nowadays provide) can you help share the algorithm if its already out in the open / non-proprietary?
- kouteiheika 6y agoIt's really nothing special. I wrote the whole thing in, like, a single afternoon. If you're interested, here it is: https://pastebin.com/V7UiWj1e https://pastebin.com/V7UiWj1e It's not really a general purpose diffing algorithm. It probably doesn't handle every corner case as it only does what I need it to do in the few places I actually need it.
- jhgb 6y agoWould you even need diffing if, for example, the relevant nodes had an ID functionally derived from their contents?
- kouteiheika 6y agoSorry, I'm not sure I understand your question? Basically, I use diffing because it's simple. I don't want to mess around with manually updating whatever content I want to update, and I don't want to be forced to generate a specially crafted piece of HTML so that it's updatable. This way I only have to have only one function that updates the current page in-place by diffing, and I can use it everywhere I need to.
- jhgb 6y agoI meant that the old tree and the new tree both originate from the same source (i.e., your code), and if both were annotated with some kind of hashes of their contents, you wouldn't need any diffing to find the changes. You could simply compare the annotations on corresponding pairs of nodes and if they're the same, you don't need to descend any further and compare their contents - you already know than anything below that node is going to be identical. Also, have you compared the performance of whatever you're doing with a simple PJAX-like approach? Considering that browsers are very good at parsing HTML, I wonder how fast that would be compared to doing quite a lot of DOM calls, which you seem to be doing (?). I'd definitely be interested in that comparison.
- dejawu 6y agoThis is really neat. What's the site? I'd love to see it. And could you tell us more about the DOM diffing algorithm?
- kouteiheika 6y ago> What's the site? I'd love to see it. One of my projects where I'm using this approach is this one: https://jpdb.io https://jpdb.io > And could you tell us more about the DOM diffing algorithm? I'll paste what I wrote in another reply to a post asking the same question: It's really nothing special. I wrote the whole thing in, like, a single afternoon. If you're interested, here it is: https://pastebin.com/V7UiWj1e https://pastebin.com/V7UiWj1e It's not really a general purpose diffing algorithm. It probably doesn't handle every corner case as it only does what I need it to do in the few places I actually need it.
- monsieurbanana 6y agoTo be expected from your previous comments, but your website is snappy. Navigating it feels great.
- dygd 6y agoThe search works great. Having furigana next to kanji is very useful. Fantastic work! While playing around, I got the play button to work only once and then got this error: Uncaught TypeError: this.previousElementSibling.play is not a function onclick https://jpdb.io/search?q=ごめんあさい#a:1
- kouteiheika 6y agoThat's interesting! It works for me, both on Firefox and Chromium. May I ask what web browser and OS are you using?
- dygd 6y agoFirefox 87.0 (64-bit) on Win10. It is reproducible. Disabling uBlock Origin didn't make a difference. Let me know if I can help debug!
- redis_mlc 6y agoI'm doing the same old skool thing (first 4 points) for a large app, except the last 2 points are: - using MySQL and Redis - using Perl and the awesome CGI::FormBuilder module
- em-bee 6y agohow do you control for swapping to much?
- kouteiheika 6y agoI don't. Linux's memory management subsystem is really good. It's almost magic. And modern SSDs are really fast. So whatever swapping is going on in the background I really don't feel it. It's basically an OS managed database. Instead of the SQL server doing the swapping to-and-from the disk, Linux does it for me.
- AlchemistCamp 6y agoThis speaks to my soul! How cheap of a VPS?
- kouteiheika 6y agoCurrently 4.29 EUR/month VPS on Hetzner. (Although I have some fun features planned and might need some more processing power so I might upgrade in the future.) They have great AMD-based VMs, 2x perf/$ over Intel. (I've benchmarked.)
- mysterydip 6y agowhat benchmarks do you use? I'm curious how my VPSes compare.
- kouteiheika 6y agoIt was quite some time ago so I don't remember exactly, but I've basically used one of the usual benchmarks that everyone uses to measure the number of requests per second that a server can sustain. (I've rented another VPS in the same data center and ran the benchmark from there.)
- pknerd 6y agoHow much traffic per month does it cover in this amount? I want to launch some toy projects and confuses whether go for AWS or vps.
- nnurmanov 6y agoI am currently using a bunch of AWS technologies, e.g. AWS EBS, RDS etc. I am also thinking to consolidate all projects to a single AWS LightSail, which offers pretty cheap instances, e.g. 512 Mb, 1vCPU, 20Gb SSD for 3.5$/month.
- kouteiheika 6y ago20 TB. I would definitely recommend going with a VPS over AWS in your case, unless you want to play with AWS-specific tech to build up your resume.
- globular-toast 6y ago> Progressively enhanced (fully works without JS, but having JS enabled makes it nicer) This is something I've struggled to get developers to understand forever. It doesn't help that no browser really allows users to switch off js. The number of developers who think doing client-side validation on forms is enough is far too high!
- jaywalk 6y agoRegardless of JS vs no JS, only validating forms on the client side is just an awful practice begging for trouble.
- polote 6y agoThe post was about 'novel' tools. I really dont see what is novel about your approach
- kouteiheika 6y agoLet me quote the OP: > I'd like to hear about different approaches others are using to write web sites or apps today. What languages, frameworks, or libraries are you using? So the question as I understood it was not "what truly novel and unique approach you're using that no one else has before?" but more of a "what non-mainstream approach you're using?". And what I'm doing is definitely non-mainstream.
- wccrawford 6y agoActually, the title of the post specifically says "novel tools". That apparently didn't make it into the body of the post, but almost everyone is primed by the title to expect novel solutions in the answers. That said, I don't think there's any harm in reminding people that there are plenty of non-novel solutions already that they might not have fully considered.
- caffeine 6y agoHe is using a novel templating library, novel HTTP server, novel JS diffing algorithm ... and this approach has generated more questions than any other on this thread so clearly it is unfamiliar to much of the audience.
- gitgud 6y agoHow do you deploy changes? git hooks or something like ftp?
- kouteiheika 6y agoBasically it's more-or-less this: 1. Compile the binary. 2. SCP it to the server. 3. Restart the running process.
- gitgud 6y agoBrilliant, I feel that would simplify much of my life
- wpietri 6y agoYes! I am a big fan of "keep data hot" approaches to things. I first came across this approach with the Prevayler library years ago and I love it. People who haven't tried it will be excited to discover how much faster it is. The only trick is to store your data in a format where you can write the changes easily. E.g., logging changes and then playing back the log on startup.
- onebot 6y agoWhat Rust framework/crate are you using for webserver?
- kouteiheika 6y agoMy own. (: In general I was a little frustrated with the state of Rust's web server ecosystem since it has, what I personally call, the NPM syndrome. Pulling a single crate will often pull along with it hundreds of dependencies, and murder your compile times. I know that those dependencies are often necessary, but during development I do not need all of that. So I made my own. But of course it's generally a bad idea to run your own HTTP stack in production. So my framework works like this - it can be compiled either in development mode, or in production mode. In development mode it uses my own crappy HTTP stack and my own crappy async executor and has *zero* dependencies, and compiles super fast. In production mode it uses `hyper` and `tokio`. In both cases the API is exactly the same. So I can have my cake and eat it too.
- nextaccountic 6y agoAny chance any of this can be made open source? Sounds like a killer feature
- kouteiheika 6y agoPossibly. I will make it open source eventually. It's just that's extra work that I don't really have much time for.
- onebot 6y agoFascinating. Would you like a full-time Rust job? :)
- kouteiheika 6y ago...are you offering one? (: If so I would definitely be interested.
- gwbas1c 6y ago> flush to disk periodically How do you protect yourself against incomplete writes? What about when there's a critical update that you don't want lost? I've done similar stuff in the past, but I handled things slightly differently. For tiny data structures I just spat out a short XML file. For larger data structures I synchronized all changes with a SQLite database.
- kouteiheika 6y ago> How do you protect yourself against incomplete writes? I don't update files on disk in-place. I write to a new file, and once it's done I do an atomic move and replace the old file with the new one. > What about when there's a critical update that you don't want lost? I immediately write it to the disk? (: I don't think it's possible to completely avoid this problem. In a traditional database if you suddenly lose power you'll also lose that critical update, if it hasn't been yet committed to the disk.
- krab 6y agoI like the approach of tailoring the app to the use case, so I'm not arguing you should do something differently. The database has commit for exactly this reason. If you lose power, you will not acknowledge the commit to the client, so the client may retry. For example, the end user may get an error page. This is different from a case when the app would show modified values, but the next day, the modifications would be gone. With the database, you still have the problem, that after a user sees an error, the transaction might have suceeded. Eg if connection drops at the moment when the DB receives the commit command.
- gwbas1c 6y agoHow big are the files? The old version of the application that was written before my version always wrote the entire file like you do. But when the file got very large it was too slow, which was why we used SQLite in newer versions.
- atomicnumber3 6y agoMy "old is new" setup is similar in spirit but quite different in effect. I use rails, postgres, and docker. The only weird thing in the stack is that I use a pretty big base image - the ubuntu base image. Where I need SPA-like performance, I use react-rails to just do that little bit in React. Rails has so many batteries included, I just can't imagine being as productive in anything less mature.
- skrtskrt 6y agoWhich Rust templating tools are you using for server side HTML rendering?
- bccdee 6y agoI've had a good time with Tera, but I haven't used it extensively.
- kouteiheika 6y agoI have my own crate for template rendering. I tried various existing ones but I ended up not liking any of them for one reason or another, so I've made my own. Some of the features are: * Fully static. It's compiled into Rust code instead of being dynamic. (I wanted to be able to use normal Rust code in my templates instead of a special template-only language, and I wanted the extra speed.) * Built-in automatic HTML whitespace minification at compile time. * Built-in XSS-safe string interpolation. You have to explicitly use the unsafe raw interpolation. * It can interpolate any Rust value which implements the `Display` trait. * Doesn't use Jinja's syntax. * Doesn't have any extra unnecessary dependencies. * Doesn't use inheritance for template composition, instead it uses simple textual inclusion plus late binding to achieve composition. Since it's hard to explain let me give a simple example. Let's assume I have a skeleton.html which looks like this: <html> <head> <title><~ @paste title ~></title> </head> <body> ~~ @paste body </body> </html> And now I have index.html which is the actual template I render, and it looks like this: ~~ @include "skeleton.html" ~~ @snippet title Index ~~ @end ~~ @snippet body <h1>Hello world!</h1> ~~ @end As you can see I first include the skeleton.html, and then I define the "title" and the "body" snippets after the skeleton has already pasted them. Normally this wouldn't work, but since the `@paste` directive is lazy it only gets resolved once the whole template is processed. This makes it really simple to customize the templates, since you can just add `@paste`s wherever you'd like to have a customization point, and you don't have to think about doing things in the right order since the templating engine will take care of it for you. (If you forget to define a snippet that was `@paste`d you'll get a compile time error; I also have other directives that allow you to set a default if there was no snippet defined.)
- 6y ago
- janjones 6y ago> 100% server side rendered > Progressively enhanced > request full HTML through AJAX and diff it into the current page These first three points are actually starting to be "in" again. They're called HTML-over-the-wire[1, 2]. And I am currently looking into implementing them for my site (which uses ASP.NET Core Blazor), because I think this approach is awesome. [1] https://hotwire.dev/ https://hotwire.dev/ [2] https://alistapart.com/article/the-future-of-web-software-is-html-over-websockets/ https://alistapart.com/article/the-future-of-web-software-is...
- rogers18445 6y agoWhy not use something like lmdb instead of writing raw files? It's a performant k/v store and memory mapped. It beats file access syscalls most of the time.
- kouteiheika 6y agoI'm well aware of lmdb and I did consider using it. It is a reasonable option, yes. It's just mostly unnecessary in my particular case with my architecture, so I went for the simpler option.
- hpen 6y agoWhat are the requirements that need it to have no database?
- kouteiheika 6y agoIt's the other way around. I have no requirements that need it to have a normal database. The biggest two advantages of not having a database are simplicity (I don't have to query the database to access the data, as it's already in memory) and speed (my average response times are well under 1ms, and that is with compressing every reply, having more data in memory than I have RAM, running on a cheap dual core VPS, and being flooded with requests from linking to my site on HN).
- SomeHacker44 6y agoHow does progressively enhanced work without Javascript please?
- caffeine 6y agoI'm really impressed at how many people have come back with critiques or suggestions about your architecture without actually even knowing what your app does! So, what does it do?
- kouteiheika 6y agoWell, to me it's more of a "no one I know develops like this; tell me more" kind of a thing rather than a normal critique of my architecture. (: I've posted this link in another comment already, but this is one of my projects where I'm using this approach: https://jpdb.io https://jpdb.io
- A-Train 6y agoI do the same thing with "no SQL db". The only thing I do differently is not make snapshot of the state but rather replay events to achieve the same state as before (or different t if we migrate to different logic). Also all the changes that are possible are typed events. It wont be a problem to save the state like you do but right now our application changes so often that we have to make sure we can replicate the state even if the logic changes. Would love to talk to you about this.