7 ms·
Serving a high-performance blog solely from memory, using Rust
- pradn 4y agoQuestion for the author: do you have numbers to share about performance relative to other static site servers? Great blog by the way :)
- xena 4y agoI had the numbers at one point, but I have lost them. I can try to recreate them, but I'd probably have to use my old mac pro again to be sure the results are consistent.
- mkl95 4y agoYou can build and deploy a blazingly fast blog within minutes with Django, Gunicorn and Nginx. This is cooler though.
- Thaxll 4y agoHow can it be faster than a static page that is already in memory, the bytes are there you just send them over a socket? Transforming some template to rust code back to string buffer is somehow faster?
- kuschku 4y agoWith a static page generator, the file is still on disk. You've got a read cache for the disk, but that's not entirely reliable.
- awercliyuna 4y agoJust like the Rust executable is still on disk. Sure, if it is running, it is memory mapped, but it can still be paged out. This is not theoretical. In practice, upon request, the probability of finding the static page in cache should be similar to the probability of the executable not being paged out. (That's true as long as the actual data is the same, and the differing factors, like the size of the web server executable, are small compared to the amount of free memory.)
- xena 4y agoAuthor here. That server doesn't have swap enabled. It can't be paged out.
- jmillikin 4y agoIf the executable is on disk, it can be paged out unless you've used mlock() to tell the kernel to keep it resident.
- xena 4y agoInteresting. I've never heard of that happening before. Can you link a reference to where I can find out more about that aspect of the linux memory subsystem?
- staticassertion 4y agoI believe that when a file is mmap'd a page table is created for it in the . As you perform read/write on the file a fault loads the actual entries into that page table. As pages can be mapped they so too can be unmapped under pressure, without that falling back to swap (since it is already a file backed map, you wouldn't swap a file backed map to a different file after all). There are a few relevant bits to this. You can MAP_POPULATE the file to prepopulate the entries and you can MAP_LOCKED to MAP_POPULATE + lock the pages in (unreliably). As mentioned in the man page for mmap MAP_LOCKED has some failure modes that you don't get with mlock. https://www.man7.org/linux/man-pages/man2/mmap.2.html https://www.man7.org/linux/man-pages/man2/mmap.2.html I also found this page: https://eklitzke.org/mlock-and-mlockall https://eklitzke.org/mlock-and-mlockall Oh, and this: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/using_mlock_to_avoid_page_io https://access.redhat.com/documentation/en-us/red_hat_enterp...
- jmillikin 4y agoThe mlock manual[0] has a "notes" section that provides a good brief summary. The GNU libc manual has more than anyone would ever want to read about memory management, including a section on memory locking[1]. On an intuitive level, think of swap as being a place the kernel can put memory the program has written. When you malloc(4096) and write some bytes into it, the kernel can't evict that page to disk unless there's some swap space to stick it in. However, executables are different because they're already on disk -- the in-memory version is just a cache (everything is cache (computers have too many caches)). The kernel is allowed to drop the copy of the program it has in memory, because it can always read it back from the original executable. [0] https://man7.org/linux/man-pages/man2/mlock.2.html https://man7.org/linux/man-pages/man2/mlock.2.html [1] https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_chapter/libc_3.html#SEC60 https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_chapter...
- marginalia_nu 4y agoOn the one hand, sure, you can probably squeeze some cycle or two out of buffering everything in memory. Even though your disk read is a memory read in all likelihood given how filesystem caching works, it's still an IO call, which isn't free. Keeping everything in user space buffers might just be faster. On the other hand, you're sending that sucker over network, and what you save doing this is most likely best counted in microseconds/request. It's piss in the ocean compared to the delay introduced even over a local network.
- tsimionescu 4y ago> Even though your disk read is a memory read in all likelihood given how filesystem caching works, it's still an IO call, which isn't free. I wonder if io_uring could be used to issue a single syscall that would read data from disk (actually using page cache) and send it on the network. Of course, you could use DPDK or similar technologies to do the opposite - read the data from disk once and keep it in user-space buffers, then write it directly to NIC memory without another syscall. That should still theoretically be faster, since there would be 0 syscalls per request, where the other approach would require 1 per request.
- Matthias247 4y ago> I wonder if io_uring could be used to issue a single syscall that would read data from disk (actually using page cache) and send it on the network. Only if you don't care about HTTP/2 and TLS. And if you don't care about those, you can as well do sendfile() from a thread.
- xena 4y agoMy site does serve itself out of a UNIX socket, so sendfile() may actually work. But most of the data is served with handler functions though.
- toast0 4y agoYou can do kernel TLS for sendfile at least, maybe for io_uring too? Probably not for HTTP/2, but I'm not convinced multiplexed tcp in tcp is a good protocol for the public internet anyway.
- Philip-J-Fry 4y agoI was thinking the same. They said a Go precompiled version was faster, but was 200MB. Which I don't understand. 200MB of pages and assets, sure. Code? No. If you compile it into the binary then the storage is no worse than having a small binary and all the resources separate. Taking a statically generated site and returning the raw bytes is 100% faster. The author said so themselves.
- xani_ 4y agoTechnically with go:embed you could trivially make a blog server that is just a 200MB binary with all of it embedded. If you did it that way, now all your content is basically mmaped into the memory which means (probably) less syscalls. Soo it might've shaved half a microsecond maybe ?
- dec0dedab0de 4y agoHow can it be faster than a static page that is already in memory, the bytes are there you just send them over a socket? Transforming some template to rust code back to string buffer is somehow faster? I don't think the author is claiming it is faster than a static site stored in memory, they're saying it is faster than a traditional static site that loads files from the disk. At least that's how I read it.
- codeflo 4y agoThat “traditional” site doesn’t actually load the data from disk, in practice. It does once, after a reboot, but that’s true for this solution’s executable file as well.
- dec0dedab0de 4y agoThat “traditional” site doesn’t actually load the data from disk, in practice. It does once, after a reboot, but that’s true for this solution’s executable file as well. Does Apache/Nginx/IIS load static files in memory ahead of time? I would assume no, unless someone went through and did some optimizations. Even so, there is always a point where memory runs out, and in that case a templating engine is essentially compression. I would assume if the author outputted his whole website as static files and stored them in memory it would be even faster, but that would require quite a bit more memory.
- marcosdumay 4y ago> Does Apache/Nginx/IIS load static files in memory ahead of time? Linux loads them on the first usage. If you have enough memory, they'll just stay there. It doesn't that much memory, most sites are pretty small. But the article's way doe use less memory, less system calls, and is completely optimized for that one site only. So yeah, it will surely be faster. Besides, his site appears to not be static.
- hinkley 4y ago> Linux loads them on the first usage. Yes, but. The problem with OS file caches has ever been that people look at a box, see that the programs aren't consuming all of the available memory, and argue that they should be able to cram more shit on the box because it's 'underutilized'. There are very reasonable and sane system architectures that let the OS handle caching, but you need a way to defend against these sorts of situations. The performance falloff for this failure mode is exponential, so people try it a few times, and not getting any negative feedback, they add it to their toolbox only to get lectured months later once the bad behavior has not only become standard for them but also spread to other people. It almost begs for a different system call that can earmark the memory usage by the app in a way that's easier for people to see.
- lijogdfljk 4y agoWell for one it's not static lol. I don't think they're claiming it's faster than a static website, are they?
- Matthias247 4y agoMy thoughts as CDN engineer: It can be a tiny amount more efficient since an async disk IO implementation might dispatch the file read() call to a thread pool, wait for the result, and then send the data back to the client. Makes 2 extra context switches compared to sending data from memory. Now if the user is super confident that the data is hot and in page cache then a synchronous disk read will fix the problem. Or trying a read with RWF_NOWAIT and only falling back to a thread pool if necessary. On the other hand rendering a template on each request also requires CPU, which might be either more or less expensive than doing a syscall. All in all the efficiency differences are likely negligible unless you run a CDN which does thousands of requests per seconnd. In terms of throughput to the end user it will make zero measurable difference unless the box ran out of CPU.
- Thaxll 4y agoThe file is most likely cached in memory ( OS ) even if there is a read I assume it's going to be faster vs running some code in Rust.
- robertlagrant 4y agoWithout reading: why do Rust folks think it's better if they memorise a website and serve it, instead of using a computer?
- HillRat 4y agoThe borrow checker is much less strict if the data only lives inside your skull. Much harder to mutably borrow.
- treffer 4y agoWebsite title: My Blog is Hilariously Overengineered to the Point People Think it's a Static Site Seeing the initial comments here I think it would be better to go with the original title.
- jmillikin 4y agoThere may be further opportunities for improvement. Chrome and Curl both report it takes about 1100ms to load the linked page's HTML, split about 50/50 between establishing a connection and fetching content. I'm not sure how the implementation works internally but that seems like a long time for a site served from memory and aiming to be "high-performance". The images bring the total time up to around 5.7s. As a point of comparison, my site (nginx serving static content, on the 0.25 CPU GCP instance) serves the index page in 250ms. Of that, ~140ms is connection setup (DNS, TCP, TLS). The whole page loads in < 1000ms. https://i.imgur.com/X4LDbWj.png https://i.imgur.com/X4LDbWj.png https://i.imgur.com/Ccwzmgz.png https://i.imgur.com/Ccwzmgz.png One thing to remember is that when a server like nginx serves static content, it's often serving it from the page cache (memory). The author of Varnish has written at some length about the benefits of using the OS page cache, for example <https://varnish-cache.org/docs/trunk/phk/notes.html https://varnish-cache.org/docs/trunk/phk/notes.html>. Some of the same principles can be applied even for servers that render dynamically (by caching expensive fragments).
- xena 4y agoAuthor here. I wrote that post before I axed the CDN for my blog site itself. It was true at the time of writing, but it is not true anymore because I need to redo the CDN for the blog itself. All the images are CDNed with XeDN though.
- georgyo 4y agoI'm trying to parse what you are saying here. You removed the CDN and the site got slower? How do you know your site was the one that was fast or just the CDN? IE, the CDN should have added a lot of extra hops and made things slower. To me, this implies the rust code is very poor at opening and closing connections, so the CDNs keep alive is pasting over that issue.
- xena 4y agoThe main thing the CDN provided was nodes on basically every continent that kept the site in cache. Without those servers on every continent keeping the site in cache, it takes longer to get to the netherlands to get the site loaded. The speed of light is only so fast.
- trh0awayman 4y agoI want to see this taken to the logical extreme. A real OS with actual drivers (no unikernel, no virtio) for a small set of hardware that only serves static pages. No need for virtual memory. Just hardcode the blog posts right into the OS and use the most minimal TCP stack you can make.
- filleokus 4y agoI guess Unikraft is that, kinda? https://github.com/unikraft/app-nginx https://github.com/unikraft/app-nginx
- shrubble 4y agoI think you could do that with redbean, or very nearly so.
- staticassertion 4y agoSeems like a case for a unikernel running on bare metal. No copying bytes across kernel/user, no context switching at all.
- xena 4y agoI almost wonder if I should try to get Shrine/TempleOS to do that. That could be a very interesting post.
- trh0awayman 4y agoI guess I didn't realize unikernels could run on bare metal. I've only seen them running on a hypervisor.
- whalesalad 4y agoI admire the OP's ability to use their blog as a rapid prototyping platform that is constantly growing and changing. Over engineering on a personal project like this is the whole point! Very cool. I am too much of an OCD perfectionist and don't have the guts to ship this often.
- xena 4y agoThe trick is to do lots of little changes that are easy to do in isolation. Then do bigger changes later after you learn what you messed up. I have CDO too but I work around it by sheer trolling with infrastructure, like my hacked up to hell CDN: https://xeiaso.net/blog/xedn https://xeiaso.net/blog/xedn
- hinkley 4y agoRelentless Refactoring is a great tool, but one that is often stymied by faddish behaviors like micro-services/modules. Small projects tend not to have that problem and so make a better petri dish. Of course then you have to take your knowledge out of the 'lab' and apply it in vivo... A lot of our (and in particular, my) best features come from of relocating the boundaries between things, to make space for features that weren't considered in the original design. With monolithic systems we see this late in the lifecycle in the form of Conway's Law. If you stick this problem in front of the CI/CD mirror, it's painful to face. CI/CD argues that if something is difficult we should do it all the time so that it's routine (or stop doing it entirely). However there's a conspicuous lack of tools and techniques to make that practical. The only one I really know of is service retirement (replace 2-3 services with 2 new, refactored services), and we don't have static analysis tools that can tell us deterministically when we can remove an API. We have to do it empirically, which is fundamentally on par with println debugging.
- whalesalad 4y agoI love the idea of "relentless refactoring"
- Jabbles 4y agoIt would be good if the post contained some data to justify its points, like a graph of loading times. Otherwise assertions like "So fast that it's faster than a static website." don't seem supportable. I would have liked to see the actual results from this comparison: "I compared my site to Nginx, openresty, tengine, Apache, Go's standard library, Warp in Rust, Axum in Rust, and finally a Go standard library HTTP server that had the site data compiled into ram."
- xena 4y agoI'm sorry but I have lost that data after some machines got reinstalled. I can attempt to recreate it, but that will have to wait for a future blogpost.
- llllllllllll9 4y ago
- mejutoco 4y agoThere is value in having unsafe parts of a program clearly annotated (not just with comments). It is similar to how in some languages you annotate pure functions and they do not compile unless they are pure.
- llllllllllll9 4y agobe careful and do your research. anytime you need to implement anything that shares references (just about any data structure worth its weight in implementation time) you need to use Rc<> and friends. i am not talking about https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html. fine, this escape hatch is needed. i am referring to https://doc.rust-lang.org/book/ch15-06-reference-cycles.html https://doc.rust-lang.org/book/ch15-06-reference-cycles.html.
- mejutoco 4y agoAh, I see now. That makes sense. I stand corrected.
- pornel 4y agoIt's not anytime. It's limited to recursive types with interior mutability. These two conditions are specific to mutable graphs, not just any shared ownership. There are plenty of uses of Rc that cannot possibly cause a cycle.
- NoraCodes 4y ago> enjoy programming in rust planning on it :)
- llllllllllll9 4y agozing! (or zig)
- vinay_ys 4y agoAfter going to the end of a long post, I'm disappointed to not find any latency or throughput efficiency metrics. Author seems to claim he has a very popular high-traffic blog and it is super fast, faster than all the popular web servers serving static pages. Where's the performance data to prove this? edit: web.dev measure gave this blog post url a performance score of 30/100 which is quite poor.
- xena 4y agoRipping out cloudflare made the metrics slower. I wrote this post before I ripped out cloudflare and it was accurate at the time of writing. It will be better once I can re-engineer things to be anycasted.
- deathanatos 4y agoweb.dev seems to give it a poor score primarily because of the YouTube embed … so perhaps Google should heed its own advice?
- kixiQu 4y agoAuthor isn't a man (https://github.com/Xe https://github.com/Xe)
- lionkor 4y ago> Nephelemancer, Kastermakfa, Hacker, Ordained Minister [...] Please call me (order of preference): Xe/xer, They/them or She/her please.
- deleted 4y ago[deleted]
- greenhearth 4y agoThe tech is cool, but some of the language is so cringy. For example, the statement "websites are social constructs" makes zero sense. You could say that websites are material objects of a symbolic network of computer languages, like physical paper money is a material, fetishized object of the social construct of money. Websites themselves are not constructed socially. Maybe the author means how websites are perceived, or conventions of web tech itself, is constructed socially?
- greenhearth 4y agoYou can downvote all you want; this is the truth
- deleted 4y ago[deleted]
- NoraCodes 4y agoI don't agree with this at all. What makes one set of frequency changes over a wire a website and another a voice call? A big pile of socially constructed concepts, from written language to Unicode to TCP and HTML. The electrical impulses are physically real; the website is a construct and makes sense only in the context of society.
- greenhearth 4y agoWow, NoraCodes! I just finished your Rust book! It's great and you're a hero! But no, a website is not a social construct because you don't have to have a society to have a website. I can have two machines connected and host an html file on one of them and stare at it on the other one all by myself and it will still be a website on a web! No contractual agreement is necessary! But anyway, it's amazing that you posted on my comment! I am a huge fan!
- xena 4y agoI don't have the time to get into a hardcore semiotics discussion at the moment, but basically I'm using words in the ways that normal people use words, which generally treats perception of the conventions of a thing as the thing itself. People do this mostly for convenience.
- 19h 4y agoYou don't need Rust for this -- you can do the same in Go, Node, etc. In 2012 my cheap VPS had a crappy HDD share but fairly acceptable memory, so I rendered the Markdown files and stored them in a little structure, returning them directly from memory. Everyone thought it was amazing even though it was just a dumb http server returning pages[req.path] :-) Latency was under 10ms which was pretty amazing for a 2012 KVM VPS.
- NoraCodes 4y agoI don't think OP was implying that Rust was a requirement, just what was actually used in this case. And, indeed, OP gives some reasons that Rust might be preferable: > And when I say fast, I mean that I have tried so hard to find some static file server that could beat what my site does. I tried really hard. I compared my site to Nginx, openresty, tengine, Apache, Go's standard library, Warp in Rust, Axum in Rust, and finally a Go standard library HTTP server that had the site data compiled into ram. None of them were faster, save the precompiled Go binary (which was like 200 MB and not viable for my needs). It was hilarious. I have accidentally created something so efficient that it's hard to really express how fast it is.
- xani_ 4y agoI did that in Go although it was "only" caching the markdown rendering - the page templates were written in Go (via some lib that gave tools to make that mangeable) and compiled with the app so the whole template building was blazingly fast.
- Existenceblinks 4y agoAh same as (precompiled + loaded into memory): https://dashbit.co/blog/welcome-to-our-blog-how-it-was-made https://dashbit.co/blog/welcome-to-our-blog-how-it-was-made
- manuelmoreale 4y agoI get the fun for a developer to set up something like this to experiment and learn new things. But I'm left with a question: why? Like, is there really a point aside for the aforementioned intrinsic dev fun? There has to be a point of diminishing return. And again, I'm not discarding the dev side of things but it seems a lot of extra tooling and complexity cor not much gain.
- xani_ 4y agoAnd then all the gains were entirely eaten by first hop to a network device. Speaking from experience as I did similar thing, although speed was not a concern, just perpetual annoyance with available tools for blogging.
- allan_s 4y agocppcms was (is?) using something similar , you write in a template language, and it get compiled into c++ code http://cppcms.com/wikipp/en/page/main http://cppcms.com/wikipp/en/page/main https://github.com/Tatoeba/tatowiki https://github.com/Tatoeba/tatowiki the wiki of tatoeba.org ( https://en.wiki.tatoeba.org/articles/show/main# https://en.wiki.tatoeba.org/articles/show/main# ) is written in it
- hit8run 4y agoCan you use more rust to serve the 7 readers of a blog? You know what: use caching or something that compiles to plain html (hugo, jekyll etc.). No need for hardcore memory optimization.
- AJRF 4y agoIs this the same author that made the talk about PAM recently? I really like his articles.
- xena 4y agoThanks! I'm not a guy, I'd prefer if you used they to refer to me, but she works too. The PAM one was a really fun talk to write. I need to finish that postmortem on how that talk went wrong.
- deleted 4y ago[deleted]
- epolanski 4y agoI'm confused about the they pronoun, isn't it plural? This pronoun thing doesn't exist in my native language.
- spullara 4y agoMeasuring the performance of a CDN isn't that interesting. This is about the fastest blog I have seen and it doesn't have a CDN in front of it: https://www.lukew.com https://www.lukew.com
- deleted 4y ago[deleted]
- deleted 4y ago[deleted]
- forchune3 4y ago
- apstats 4y agoThis loaded pretty slowly for me (2 seconds) and also has aggressive page layout changes. It’s almost like for 99% of software the most important part is UX not the low level programming language that is chosen