Hacker news

  • Top
  • New
  • Past
  • Ask
  • Show
  • Jobs

Saving another 100TB of RAM (https://blog.cloudflare.com)

436 points by f311a about 24 hours ago | 93 comments | View on ycombinator

zer0x4d about 19 hours ago |

Incredibly happy to see this series of CF articles. I was always so proud of devs back in the days where RAM and processing were scarce and who had to get creative to fit even the most basic stuff in the budget. It seemed to me that after RAM and processing became abundant, most gave up on optimization and focused on shipping instead which meant now that even with several cores, a basic notepad or music player failed to work. In a way, RAM becoming more expensive has ushered in a new era of forced optimizations, which I'm really happy for

nopurpose about 3 hours ago |

Do I understand correctly, that they spent memory storing largish N hash values per server, so that request hash determines which server to send request to using closest higher value of all server hashes?

That in effect boils down to consistently selecting server S with probability P, where P is function of weight and total number of servers?

Surely there must be better way to select server with a given probability without storing a massive lookup table of hashes? Randevouz hashing of some sorts

dr_dshiv about 21 hours ago |

Cloudflare is truly amazing, they have made so much possible for my main side-project at a price and performance that I can’t really take credit for (http://sourcelibrary.org), I don’t care if their text was written with AI, I just wish I could get my own AI to sing so well about hashing… but wait.. today I noticed Claude trying to use hashing when a timestamp would honestly do, and now I’m really doubting myself, hmm…

vlovich123 about 15 hours ago |

I would get rid of consistent hashing and ketama for a better system which works save an additional 600TiB.

You use the first N bits of your key hash to pick the server partition so it’s a reasonable number (eg 128 servers per partition). Then use high quality precomputed hashes (first 64 bits of sha256) for the server name as N in H(K + N). Use wymum from wyhash as the H so that you do o(n) integer multiplications while retaining a result that’s still a good hash statistically.

Now you’re using a tournament hash, the small N means O(N) vs O(N log N) doesn’t matter, and also this O(N) is also going to be much less CPU than computing 160 hashes per key as they do now, so much less latency added per request.

Fordec about 19 hours ago |

This sort of thing makes me thing that we're about to enter an era where software development is going to be where most of the jobs fallout will be. You can't one-shot vibe code your way to this. But for proper Software Engineering, those jobs are safe where more and more problems are going to actually need solving by creatively using math because all the problems individuals deliver are just going to be larger. People are just mourning the loss of the low hanging fruit.

ricardobeat about 22 hours ago |

These optimizations are impressive, but it gets me thinking: at what point does a company become a collection of impenetrable siloes, where nothing really does what you expect? Maybe know with AI this is less of an issue as exploring a codebase is also much faster.

agosta about 22 hours ago |

Bang up article! As someone who doesn't get to do enough (almost any) calculus in my daily programming assignments, I thoroughly enjoyed reading about Kevin's dive into that derivation (linked in the supplemental article). All the people being negative here can swallow raisins

Sevii about 3 hours ago |

It's crazy to realize 100TB of RAM isn't that much anymore. 10TB server racks are already in production. Before long we'll have 100TB racks serving one instance of an LLM.

christina97 about 4 hours ago |

I’m not sure why folks are finding this so revolutionary. There are teams of scientists at big techs with PhDs working on all kinds of optimization across compute fleets. This seems cute but its exposition of math is more along the lines of “look at how cool I am that I could do a bit of calculus”, and that makes me question the technical depth at CF.

I found the motivation pretty lackluster: nowhere does it actually explain why you use consistent hashing (dividing the item space naively/regularly would actually cause much more than 1/n items to move, which is unintuitive) and how you actually use it.

That said, it got me to spend a few minutes studying this and got me to understand the key bit I was missing.

proc0 about 22 hours ago |

The only Rust section is the one on storage improvements about the struct that stores the hash, but do they really need that many hashes that 2 bytes makes that big of a difference? Article doesn't expand, but I guess it's a hash for every task on every computer, so maybe yes.

videocompressde about 10 hours ago |

I've hit the same thing at smaller scale — once you know the real cardinality, shaving a few bytes per entry beats the clever stuff that never got profiled.

cloudengineer94 about 7 hours ago |

Every single optimization stories I read such as these make me super happy

sroussey about 21 hours ago |

Someone really needed a few hundred TB to waste on inference and went looking under the rugs…

parallax_error about 20 hours ago |

I definitely enjoyed this writing style more than a lot of the recent cf blog posts. Cool article!

schobi about 11 hours ago |

I can imagine the other internal teams looking at this.. "100 TB gets you attention? Hold my beer.. we will try that as well!"

MisterMunchkin about 9 hours ago |

I like that they have a performance team that actually tries to improve their products over time.

I also really appreciate the fact that this is human-written and not just AI slop. It’s refreshing to actually read English instead of Claudelish.

jiggawatts about 16 hours ago |

I'm surprised to see no mention of hierarchical rendezvous hashing in either the article or the comments here.

It is purpose-designed for exactly this type of proxy/cache load-balancing scenario!

variety8675 about 17 hours ago |

It’s nice to see Cloudflare is letting humans write the blog posts again after all the fallout from their LLM slop blogs

sfink about 14 hours ago |

Um.

I read the article thinking it would make for a great brain puzzle, but I quickly decided there's something wrong with the question setup because the initial solution didn't make sense. I assumed it was just missing a constraint that would be revealed later, but I'm still not seeing it -- the article just kept patching up the flaws in the wrong solution, the one that is more complicated than the straightforward one.

I'm probably still missing something obvious? It's probably something to do with "...in a way that does not require large changes when servers are added or removed."

But let's start with the problem as initially posed: you have an infinite stream of tasks and you need to deterministically assign them to N servers. (Perhaps you have to shard the collections of servers, so not every load balancer knows about all of them? But no, that would break the solution in the article.) Ok, then hash the task request (I assume that you hash it, the article doesn't explicitly say, but that's how you'd get determinism) and take that hash mod N, that's your server index.

Why hash the servers too? If you roll 6 dice, and then another one to choose which die to use, you're not getting any more randomness. You're matching up two sides, the tasks on one side and the servers on the other; no need to randomize both.

Ooh, but that's not a perfect distribution? Ok, if the hash value is large enough to be in the at most N-1 slop values at the top of UINT_MAX, then roll again (compute another hash). But CF is happy with 8% unevenness, there should be no problem with this 0.1% or whatever.

Also, how do they find the nearest server hash to a task hash? Surely it's not a log(n) binary search through sorted server hashes, I hope?

Weights break this scheme. Now each server has some number of tickets. So you compute hash % T (where T=total tickets) and have to figure out what server that is. There's probably a more clever way, but you could make a big array of (2-byte!) server indexes, one per ticket, and just fill them in and look up at index hash % T.

That's 2 bytes per ticket, which feels uncomfortably wasteful if weights can be large. That's where things get more complicated for me: since the tasks are hashed, it doesn't matter what order a server's indexes come in relative to other servers', so sort them by descending weight. [I'm starting to suspect I'm making a fool of myself here by missing something obvious with the whole setup...] Now you can make an array of indexes for servers with the highest weight, then the next lower, then the next. Record the number of servers of each weight. Then you can take the hash % T and figure out which array it's in, then divide by the weight to give the index within that array.

To reduce the number of per-weight arrays, you can restrict the weights allowed. If you restrict weights to be powers of two, you can eliminate a division by using a shift. If you really want more flexible weights, you can allow servers to be in more than one of the arrays. Let the arrays be powers of two, and then add an entry to each array corresponding to 1 bits in the binary representation of the weights. That increases the total memory usage of the arrays, so you could somewhat restrict the allowed weights by rounding to the nearest number with, say, 2 or 3 "on" bits at most. With at most 2 bits, that means weights are 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 16, 17, .... The error really isn't bad.

And this should all be easily doable without any branches, I'm pretty sure. As long as you statically cap the max weight.

Anyway, that's just plowing through with the straightforward approach, and I still think I'm probably missing something major here. I imagine with large numbers of servers, some go down, so fast deletions are probably important. You can get by a little while by marking dead servers and if you "roll" one, just roll again. (Yes, deterministically, assuming other load balancers agree that the server is down.) But when more than some number of servers go down, you'd want to kick off a background task to rebuild a new set of tables -- so that's a factor 2 in size usage to have them both in memory during the rebuild.

Adding is trickier, you'd probably want to do a 2-level structure where first you use the hash to decide whether it's in the old set that the table is built for or the set of servers that hasn't been incorporated yet (you'd collect these over time, and empty them out on the next table rebuild.) It's a little weird, because the load balancers' outputs would only agree when the added and deleted sets agreed, but I don't see how to do better than that. (I think you could set up some kind of synchronization scheme so that the old sets would agree, which would make them usually agree on which of the old set of machines gets it.)

Somebody, feel free to tell me I'm being stupid! I'm sure there's a constraint that I'm missing, given that my understanding of the initial problem doesn't require any memory at all except for the servers' info.

(Or if not, I'll let you know where I'd like to receive shipment of 1% of the memory I've saved...)

kingleopold about 21 hours ago |

anyone remember 100tb hosting company?

opsnotes80 about 17 hours ago |

[flagged]

opsnotes80 about 17 hours ago |

[flagged]

jamesforestwest about 21 hours ago |

[dead]

goodpoint about 9 hours ago |

TLDR: the existing implementation was poorly designed. They packed 2 integers better and saved memory.

aaron695 about 18 hours ago |

[dead]

officialchicken about 22 hours ago |

[flagged]

johnnyApplePRNG about 23 hours ago |

[flagged]

go_elmo about 23 hours ago |

[flagged]

swe_dima about 20 hours ago |

does this mean RAM prices can go down now? Please?