Blog ·
Rack space is not capacity
How full is that rack? There are five answers, in five different shapes, and adding them up produces a number that is wrong in a way nobody can audit.
By Provibr
"How full is that rack" sounds like a question with a number for an answer. It is not. It is five questions wearing one coat, and every dashboard that answers it with a single figure has quietly picked which of the five it meant and thrown the other four away.
The five are genuinely different things. How many rack units are taken. How many of the machines in there are actually in service rather than staging or quarantine. How many of them could be sold to a customer tomorrow. How much power the machines in there are budgeted to draw against the budget of the rack. And how much power they are actually drawing.
You can be at ninety percent on the first and have nothing to sell, because everything in the rack is bound to a customer already. You can be at forty percent on the first and unable to add a machine, because the power budget is spent. Those are not edge cases. They are the normal state of a datacenter that has been running for a while.
This is how we built the answer as five separate readings, and, more importantly, how we made it hard for a future version of ourselves to average them.
Five axes, five different answers
| Axis | Shape of the answer | Where it comes from |
|---|---|---|
| Rack units | occupied, blocked and available per face, plus the largest contiguous free run | active claims plus the rack geometry |
| Asset status | a count per logistical state | the machines placed in the rack |
| Sellable dedicated | machines sellable, excluded, considered | the readiness verdict of each machine under the policy of the organisation |
| Configured power | reserved watts against the budgeted watts | reservations on the machines and the budget of the rack |
| Observed power | nothing, and the reason is that there is no sensor | nowhere |
Read that table as a list of return types rather than a list of features. The first axis returns a shape per face. The second returns a map from state to count. The third returns three counts of machines. The fourth returns two figures in watts. The fifth returns nothing at all. None of the five is a bare number, and that is not an accident of the design: it is the first of four things that stop a combined score from existing.
If every axis returned a percentage, an average would be one line of code. Nobody would review it as a decision, because it would not look like one. It would look like a convenience. Making the return shapes structurally incomparable means that anyone who wants a combined figure has to first write the conversion that flattens them, and a conversion is visible in a diff in a way that a division is not.
Unknown is per axis, and it carries a reason
The second closure is what happens when an axis does not know.
Three reasons exist today, and they are keys rather than sentences, because the panel renders them in nine languages. The rack has no power budget recorded. One or more of the machines in it does not know its own reserved draw. There is no sensor.
The difference matters because they are three different conversations. The first is a data entry gap in the rack record. The second is a data entry gap in a machine, and it says something specific: the sum you are about to read has a hole in it. The third is not a gap at all, it is the absence of hardware we have not built support for.
The power axis is where this bites. If the rack has no budget, there is nothing to measure against and the axis is unknown for that reason. If the rack has a budget but one placed machine has no reserved figure, the axis is unknown for a different reason, and this is the case that a naive implementation gets wrong. It is very tempting to treat a missing wattage as zero and carry on summing. That produces a rack that looks roomy and is full, which is the single most expensive wrong answer this system can give: somebody schedules a delivery against it.
The rule is that unknown on one side makes the whole axis unknown. A sum with a hole in it is not a smaller sum.
The axis that is always unknown
The fifth axis, observed power, returns unknown every single time. There is no power distribution unit, no feed, no outlet and no temperature sensor in this product. Not partially, not behind a flag: none.
The obvious thing to do is leave it out until it exists. We put it on the screen anyway, saying that there is no sensor, and the reason is what its absence would do to the reader. With four axes on the screen, one of them labelled power, a reasonable person concludes that the power figure is a measurement. It is not. It is a budget: what somebody typed into a form as the expected draw of a machine, added up.
Showing the fifth axis as explicitly unknown is what keeps the fourth one honest. A capacity claim assembled out of incomplete data is exactly the failure mode this whole section of the design is written against, and the cheapest defence against it is to name the thing you did not measure.
Making the score impossible rather than forbidden
None of the above survives contact with a busy quarter unless something enforces it. "We agreed not to combine these" is a comment, and a comment does not fail.
So there is a test that reads the source file with the TypeScript compiler and asserts three things about it.
No exported function in that file returns a bare number. There is therefore nothing in the module to average, not even by accident.
The function that assembles the five readings contains no arithmetic operator, no reduce and no Math. It maps over the register of axes and calls each reader. That is all it is allowed to do, and any combined figure would necessarily be born there, because that is the only place where all five readings are in scope at once. That is the third closure.
And the words themselves are banned: a scan rejects healthScore, rackHealth, overallScore, combinedScore and capacityScore in any casing. That last one is coarse and we kept it anyway, because the failure it guards against is somebody being helpful at half past four on a Friday.
The point of writing it this way is not that a rule cannot be broken. It is that breaking it requires deleting a test, and deleting a test is a line in a pull request that somebody will ask about. Adding a division between two numbers that were already there is not.
We proved the guard red before trusting it, in two separate ways: an arithmetic expression inside the assembling function, and an exported function returning a number. Both were taken out again. A guard that has never failed is a guard whose selectors you have not checked.
Adding an axis has to be a compile error
The five axes live in one register. The producers live in a record keyed by that register, so a sixth axis without a reader is a type error rather than a silently missing row. That is the fourth.
That covers the code. It does not cover the screen, and the screen is where a missing axis is most dangerous: it simply does not appear, and the table looks complete. So a second guard derives the axis list from the register and demands, for each one, a label and a hint in all nine catalogues, a rendering branch of its own in the component, and an unknown branch with a translated sentence for every reason in the reason register.
That guard earned itself immediately. It found that the observed power axis was named nowhere in the rendering component, which is exactly the state in which an axis produces an empty cell instead of an explanation, and an empty cell reads as zero. Writing tests per axis could not have found that, for the same reason that a test per case cannot find a missing case: you would have had to already know the axis existed.
The same reasoning applies to translation. A reason code with no sentence behind it is a blank space in a language nobody on the team reads, and nine catalogues is a lot of places for a blank space to hide.
What we would write down
- Decide what each answer is measured in before deciding what it looks like. Units that cannot be added are the cheapest defence you will ever build.
- Give unknown its own reason per axis, and never let a missing input become a zero on the way to a sum.
- Keep the axis you cannot measure on the screen, labelled as unmeasured, so the axis next to it is not mistaken for a measurement.
- Put the prohibition in a test that reads the source, not in a comment, and prove the test red once before you rely on it.
- Derive the guard from the register, so that adding a sixth axis is loud in code and on screen instead of quiet in both.
To be clear about where this stands: the datacenter logistics work sits behind a rollout flag that is off by default and behind a per organisation setting on top of it, so none of this is something you can go and buy today. It is the shape we would build again, and the five axes are the part we are most confident about. The related question of who is allowed to write into a rack at all, and what actually stops two people claiming the same slot, is in the only real limit on rack occupancy.
From the datacenter work on Provibr.