Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite in Rust #100

Draft
wants to merge 25 commits into
base: master
Choose a base branch
from
Draft

Rewrite in Rust #100

wants to merge 25 commits into from

Conversation

ngryman
Copy link
Owner

@ngryman ngryman commented Nov 9, 2021

tl;dr This PR is a complete rewrite in Rust. It shouldn't change anything for end-users.

👋 Could you help me try the new version?

Please try https://img.runbots.io instead of https://img.badgesize.io and let me know if it works fine. Take a look at the README for the API.

I'm using this temporary domain for testing purposes. I will shut it down when this new version is deployed. For the story, the new solution needs to sit behind a CDN for caching content. For this to work, the CDN needs to manage the domain. But the current badgesize.io domain is already managed by Vercel, so I had to use another temporary domain. I happened to have runbots.io (yet another unborn project) free 🤷‍♂️

Motivation

The first reason for this change is that I wanted to learn Rust. That's a good reason by itself 😀

But the main reason is, as often, pasta (💰). badgesize.io is directly serving 1.5+ million badges per month (~50k per day). Badges are cached for 1 hour. So the traffic is largely absorbed by Vercel's CDN and other caches like Camo when accessed via GitHub. In theory, it's serving dozens of millions of badges without having to generate them 👌

However, despite this cache, the current hosting on Vercel + Logflare cost me respectively $60 + $10 per month. This is not sustainable for me. I need to change of hosting solution to something cheaper and make sure to use every atom of available hardware to its best. Rust is the perfect candidate for that.

My end goal is to divide costs per 10, from$70 to $7. Spoiler, I got down to $6 😎

Breaking changes

There is only one major breaking change: PNG is officially dropped. It was broken in the current version anyway since Shields.io changed the subdomain for raster image formats (ie. png, jpg). I think it makes sense since SVG is superior in every aspect and from what I see in my logs, everybody is using SVG anyways.

That's good news for the wallet 💰 since it allows me to drastically change the implementation to something more efficient (cf. section below). SVG is a text-based image format that is cheap to generate compared to rater images.

New implementation

I mostly ported the existing code to Rust with some tweaks. The major change is that I don't depend on Shields.io anymore. The current version was redirecting the user to Shields.io for the badge generation itself. It happened to be brittle on some occasions and to slow things down.

The new implementation proudly generates SVG badges itself 💪 It was a fun and interesting task to do, not that hard in the end. The most challenging part was to handle kernings (ie. space between characters) to compute the final badge width.


Fixes #94, #97

@PaperStrike
Copy link

WOW! This is really awesome! Sincere thanks for your contributions!

I haven't learned Rust at all so I only took some simple tests. It seems the computed sizes are increased by the changes, e.g., the represented size of a sample file before, after.

It happens that `Write::flush` is not garanteed to be sync. In some occasions `count` was not up to date when we read it. Making sure we flush after each write seem to resolve the problem. I don't know if that's the most performant way of doing this though.
Apache defaults are: `q=5, lgwin=18`
Nginx defaults are: `q=6, lgwin=19 (512k)`
@ngryman
Copy link
Owner Author

ngryman commented Nov 10, 2021

@PaperStrike Thanks for testing this out!

I should have mentioned that but I changed Brotli's compression quality in the new version. It explains the differences.

Node.js defaults it to the maximum value (11). It produces a smaller file at the expense of much more CPU cycles. That's why I think servers out there have lower defaults. For example, Apache sets it to 5 and Nginx is 6 by default. The reason is that the additional compression time would likely be slower than transferring a slightly bigger file, which would defeat the purpose of speeding transfers.

If we take unpkg as an example. By inspecting the transferred size with Chrome DevTools, we can see that it takes 4.1kb and not 3.45kb as advertised by the older version. So it's likely unpkg uses something close to the configuration of the new badgesize version.

After your comment, I dug a little deeper, and I adjusted Brotli's options a little bit to try to match a reasonable default configuration (cf. c4a934d). The reported size of this new version is still above the older (eg. 3.9kb for your library), but I think it's for the best. I believe it's better to advertise something close to real-world sizes rather than optimal sizes.

@styfle
Copy link
Contributor

styfle commented Nov 10, 2021

@ngryman I realize you've already done a lot of work on the Rust implementation here but I wanted to reach out because the $60/month for Vercel doesn't sound right. The Pro plan is $20/month and should offer plenty of bandwidth and function execution without hitting any overages.

That being said, Vercel offers sponsorships to cover the cost of open source projects so you don't need to worry about billing at all 😃

Take a look at https://vercel.com/support/articles/can-vercel-sponsor-my-open-source-project

@ngryman
Copy link
Owner Author

ngryman commented Nov 10, 2021

Hey @styfle, thanks for the heads up, I appreciate it.

I already have the pro plan, but unfortunately, I'm reaching the Execution quota. I'm consuming 2 × 100 GB-Hours which costs me an additional $40.

For the sponsorship, I already asked last year but got the following response:

At the moment, we are sponsoring OSS packages that fit the criteria here.
Serverless Functions are indeed a premium offering in our platform and that is why we are not prepared
to sponsor projects heavily using this type of resource.
If you don't want to use Vercel to deploy this service, perhaps a serverful environment may suit your needs.

So I figured out I should use a different hosting solution for this.

@rauchg
Copy link

rauchg commented Nov 10, 2021

@ngryman definitely happy to help out out here

@PaperStrike
Copy link

PaperStrike commented Nov 11, 2021

@ngryman Yes, I agree real-world sizes are better.

The transferred size shown in Chrome DevTools contains everything in the response including the headers, which will increase the size by around 0.4 kB. You may take that into account to be more accurate.-)

@ngryman
Copy link
Owner Author

ngryman commented Nov 11, 2021

@PaperStrike Oh good catch for the headers. My example with unpkg is probably not the best then 😅
I'll keep the current settings for now and if folks complain about it, I'll revisit it.

@ngryman
Copy link
Owner Author

ngryman commented Nov 11, 2021

@rauchg Oh cool 🎉 I guess I'll ask directly, could this project be eligible for your OSS sponsorship? If so, I can port this PR to use the Vercel Rust runtime.

@styfle
Copy link
Contributor

styfle commented Nov 11, 2021

@ngryman Yes, I confirmed with @rauchg that Vercel will sponsor this project 👍

Once you add a logo and link to your project, contact support and we'll apply the sponsorship to your project! 🎉

I hope that helps you ship faster, thanks! 🤗

@ngryman
Copy link
Owner Author

ngryman commented Nov 12, 2021

Oh wow, thank you so much guys! That's just awesome 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Absolute url no longer supported?
4 participants