The index that replaces the text

Refitting when the escape is wrong

A growing directory's document array, coded by a Huffman code over its files' lengths and refitted each time the files double, loses at best 0.356 bits a symbol for 5.65 final arrays of rewriting, and even an escape weighted by the growth still to come loses 0.183. A clock read off the structure — refit when the share of characters named through the escape passes twice the share its code word stands for — was predicted to refit about six times and halve the loss. It refits 25 times, eleven in the first twelve versions, and rewrites 10.6 arrays for a loss of 0.042. Held back until the characters have grown by a quarter since the last refit, it refits 11 times and loses 0.098 bits a symbol for 4.52 arrays: under every doubling schedule on both counts, including the one that reads the future. It sees anything only if the escape starts small: weighted as half the coding space, the escape's code word is one bit, nothing behind it can ever pass twice a half, and the clock never fires.

One doubling late tried to repair the weakest part of a growing index’s document array. The array names, for every character of a collection, the document it belongs to, and the array is the length distribution found that the best code for it is a Huffman code over the documents’ lengths. A collection that grows needs a way to name documents added after the Huffman code was fitted, and the Huffman code has one: an escape, a code word that says “a document added since the fit”, followed by a small code over the added documents. Replayed through a source directory’s history, from twenty files to 222 over 47 versions, the escape was where the loss went. Refitting each time the files doubled and rebuilding only the escape’s subtree in between was the best schedule measured, and the escape’s weight at each refit decided the rest. Weighted by the share of characters that arrived since the previous refit, it lost 0.518 bits a symbol; weighted as half the coding space from the start, 0.389; weighted by the growth since a half start, 0.356, the best a store could compute; weighted by the growth still to come, which no store can know, 0.183. Every schedule rewrote the array 5.65 times over.

That page’s conclusion was that the rewriting belongs to the schedule. Its closing section proposed a different clock. At every version the store knows how many characters its escape is naming, because the escape’s subtree is rebuilt at every version from the added files’ lengths. The escape’s code word, ℓ\ell bits, stands for a share 2−ℓ2^{-\ell}. When the share actually behind the escape passes that by more than a factor of two, the escape’s code word is wrong by a bit, and a refit would correct it. The proposal predicted the clock would refit about six times over the history instead of four, at different versions, for a mean loss under a quarter of a bit and about the same rewriting. It named the failure: the first stretch, where the share behind the escape climbs from nothing to two thirds in nine versions and would cross a power of two four times.

The clock, and the hold

The history, the Huffman code and the charges are the earlier page’s. The array at each version is coded by the Huffman code fitted at the last refit — and the comparison that matters is always against the Huffman code refitted at that version, not against the plain array, the trap a saving quoted without its collection found — every document added since named through the escape and then a Huffman subtree over the added documents’ current lengths; the loss at a version is its bits a symbol beyond the Huffman code refitted at that version; the rewriting is the bytes every refit and every subtree rebuild reads, counted in final arrays.

The clock refits at the first version, and afterwards at any version where the share of characters behind the escape exceeds a factor times 2−ℓ2^{-\ell}. Factors of 1.5, 2, 4 and 8 were run. The clock fires only upward: just after a refit nothing is behind the escape, its share is nought, and a clock that also fired on a share too small would refit at every version. A second rule was added after the first was measured: a hold, under which the clock may not fire until the collection’s characters have grown by a factor since the last refit — a quarter or a half. The escape is weighted at every refit as the earlier pages weighted it, and the clock is reported first with the escape weighted as one average document, which gives it a long code word to watch.

Better than every doubling, once held

A clock read off the escape, held until the characters have grown by a quarter, loses 0.098 bits a symbol for 4.52 final arrays read — under every doubling schedule on both counts, the best a store can compute at 0.356 for 5.65 and even the weight that reads the future at 0.183; unheld it loses 0.042 and reads 10.59Each schedule's mean loss against the Huffman code refitted at every version, over every version, against the bytes its refits and escape rebuilds read, in final arrays. Doubling schedules (refit when the files double): escape as one document 0.622 for 5.65, escape as half the coding space 0.389 for 5.65, escape by the growth seen 0.518 for 5.65, escape growth seen, half at first 0.356 for 5.65, escape by the growth to come 0.183 for 5.65. Clocks with the escape weighted as one document, refitting when the escape's share exceeds a factor of 1.5, 2, 4, 8 times its code word's: held to growth of 1: 1.5 0.042 for 10.59 (25 refits), 2 0.042 for 10.59 (25 refits), 4 0.056 for 8.53 (18 refits), 8 0.128 for 5.95 (11 refits); held to growth of 1.25: 1.5 0.098 for 4.52 (11 refits), 2 0.098 for 4.52 (11 refits), 4 0.098 for 4.52 (11 refits), 8 0.175 for 4.13 (8 refits); held to growth of 1.5: 1.5 0.189 for 3.84 (7 refits), 2 0.189 for 3.84 (7 refits), 4 0.189 for 3.84 (7 refits), 8 0.284 for 4.15 (6 refits).00.2000.4000.60002.5057.5010bytes read by refits and rebuilds, in final arraysmean loss, bits a symbolas one documentas half the coding spaceby the growth seengrowth seen, half at firstby the growth to comeclock, held to 1.5×clock, held to 1.25×clock, no holdlower and left is better47 versions, 20 to 222 files
Fig. 1 Mean loss against the bytes read by refits and rebuilds, in final arrays. Every doubling schedule reads 5.65 arrays: the escape as one document 0.622, as half the coding space 0.389, by the growth seen 0.518, growth seen from half 0.356, by the growth to come 0.183. The clock with no hold, at factors of 1.5 to 8: 0.042 to 0.128 for 10.59 to 5.95. Held to a quarter’s growth: 0.098 for 4.52. Held to a half: 0.189 for 3.84.

A clock read off the escape, held until the characters have grown by a quarter, loses 0.098 bits a symbol for 4.52 final arrays read — under every doubling schedule on both counts: the best a store can compute loses 0.356 for 5.65, and even the escape weighted by the growth to come loses 0.183. The clock with no hold loses less still, 0.042 at a factor of two, and pays for it by reading 10.59 arrays, nearly twice any doubling schedule. Held to a half, it loses 0.189 for 3.84.

The prediction is right about the loss and wrong about almost everything else. It expected about six refits; the clock with no hold makes twenty-five. It expected about the doubling schedule’s rewriting; the clock with no hold rewrites nearly twice as much. It expected a loss just under a quarter of a bit; the clock reaches a sixth of that. The proposal had the direction right — watching the escape is a better clock than counting the files — and the scale wrong, and the hold is what turns the right direction into a schedule worth running.

Where the reading goes

The frontier plate counts every byte a schedule reads, and splitting that count shows why the clock can refit more often than the doubling schedule and still read less. Every schedule reads two things: the whole array at each refit, and at every other version the escape’s subtree, rebuilt over the documents added since the last refit. The doubling schedule reads 1.60 final arrays in its four refits and 4.06 in rebuilding the escape’s subtree, because between refits that are twenty versions apart the subtree comes to cover most of the collection and is rebuilt at every one of those versions. The held clock reads 3.52 arrays in its eleven refits and 1.00 in rebuilds. The clock with no hold reads 10.59 in refits and almost nothing in rebuilds, since it refits nearly every time anything arrives.

So the doubling schedule’s rewriting was mostly not refitting at all. It was the price of an escape left to grow: every version between refits rebuilds a subtree over everything added since, and a schedule that refits rarely rebuilds a large subtree many times. The collections the saving was quoted for found that what a compressed document array saves depends on the collection it is measured on; what it costs to keep compressed depends, in the same way, on how its growth is spread over its history, and a schedule that ignores that spreading pays for it in subtree rebuilds. A refit costs more than a rebuild, but it empties the escape, and the clock’s refits are timed to empty it when its share has just grown enough to matter.

What the clock watches

The clock watches one number: the share of characters named through the escape, which climbs after each refit until it passes 2 times what the escape's code word assumes; held until the characters have grown by a quarter, it refits 11 times in 47 versions, at 0, 2, 3, 6, 9, 11, 13, 21, 25, 37, 43At every version, under the clock with the escape weighted as one document, a factor of 2 and a hold of 1.25: the share of the collection's characters in documents named through the escape, and the share its code word stands for, 2 to the minus its length, on a log₂ scale; a share of nought is drawn at the floor. v0: 0.000 against 0.0625 (refit), v1: 0.000 against 0.0625, v2: 0.000 against 0.0625 (refit), v3: 0.000 against 0.0313 (refit), v4: 0.107 against 0.0313, v5: 0.110 against 0.0313, v6: 0.000 against 0.0313 (refit), v7: 0.118 against 0.0313, v8: 0.118 against 0.0313, v9: 0.000 against 0.0313 (refit), v10: 0.098 against 0.0313, v11: 0.000 against 0.0313 (refit), v12: 0.109 against 0.0313, v13: 0.000 against 0.0156 (refit), v14: 0.000 against 0.0156, v15: 0.072 against 0.0156, v16: 0.123 against 0.0156, v17: 0.123 against 0.0156, v18: 0.123 against 0.0156, v19: 0.123 against 0.0156, v20: 0.179 against 0.0156, v21: 0.000 against 0.0156 (refit), v22: 0.057 against 0.0156, v23: 0.057 against 0.0156, v24: 0.138 against 0.0156, v25: 0.000 against 0.0078 (refit), v26: 0.000 against 0.0078, v27: 0.000 against 0.0078, v28: 0.000 against 0.0078, v29: 0.000 against 0.0078, v30: 0.000 against 0.0078, v31: 0.000 against 0.0078, v32: 0.000 against 0.0078, v33: 0.087 against 0.0078, v34: 0.087 against 0.0078, v35: 0.083 against 0.0078, v36: 0.078 against 0.0078, v37: 0.000 against 0.0078 (refit), v38: 0.000 against 0.0078, v39: 0.000 against 0.0078, v40: 0.008 against 0.0078, v41: 0.022 against 0.0078, v42: 0.051 against 0.0078, v43: 0.000 against 0.0078 (refit), v44: 0.044 against 0.0078, v45: 0.094 against 0.0078, v46: 0.123 against 0.0078.-12-9-6-30010203040versionshare of characters, log₂share behind the escapeshare its code word assumesvertical lines: refitslog₂ shares
Fig. 2 At every version, under the clock held to a quarter’s growth: the share of characters named through the escape and the share its code word stands for, on a log scale. The clock refits 11 times, at versions 0, 2, 3, 6, 9, 11, 13, 21, 25, 37 and 43.

The clock watches one number, the share of characters named through the escape, which climbs after each refit until it passes twice what the escape’s code word assumes; held until the characters have grown by a quarter, the clock refits eleven times in 47 versions. After each refit the escape’s share starts from nothing and grows as files are added; the share the escape’s code word assumes is fixed until the next refit, and is small, because an escape weighted as one document among dozens gets a code word of four to seven bits. The two lines cross, the clock fires, the new code gives the escape a new code word, and the climb starts again.

Seven of the eleven refits are in the first fourteen versions, where twenty files become fifty and the share behind the escape climbs fastest. That is the stretch the proposal expected to be expensive, and it is: the array is small there, so those refits read little, but the clock with no hold made thirteen refits in the first sixteen versions. The hold stops the clock from refitting twice inside one quarter’s growth, which in the first stretch means not refitting at consecutive versions.

Where each schedule refits

Doubling the files refits 4 times, at versions 0, 10, 22, 44; the clock with no hold refits 25 times, 13 of them in the first sixteen versions where twenty files become 60; held to a quarter's growth it refits 11 times and held to a half 7The versions at which each schedule refits, of 47: doubling the files: 0, 10, 22, 44; clock, no hold: 0, 2, 3, 4, 6, 7, 9, 10, 11, 12, 13, 15, 16, 20, 21, 22, 24, 25, 33, 41, 42, 43, 44, 45, 46; clock, held to 1.25×: 0, 2, 3, 6, 9, 11, 13, 21, 25, 37, 43; clock, held to 1.5×: 0, 3, 7, 11, 16, 25, 41. Files at every tenth version: v0 20, v10 40, v20 66, v30 106, v40 123.doubling the filesclock, no holdclock, held to 1.25×clock, held to 1.5×v0v10v20v30v40v46versiondots: refits47 versions
Fig. 3 The versions at which each schedule refits: doubling the files at 0, 10, 22 and 44; the clock with no hold 25 times, 13 of them in the first sixteen versions; held to a quarter 11 times; held to a half 7 times.

Doubling the files refits four times, at versions 0, 10, 22 and 44; the clock with no hold refits 25 times, thirteen of them in the first sixteen versions, where twenty files become sixty; held to a quarter’s growth it refits eleven times, and held to a half seven. The doubling schedule’s refits are spaced by the file count, which in this history grows fast early and slowly late, so its last two refits are twenty-two versions apart. The clock’s refits follow the characters behind the escape, and late in the history, when a few large files arrive, the share behind the escape climbs quickly even though the file count barely moves. The held clock refits at versions 37 and 43, where the doubling schedule’s next refit would have waited until 44.

That is the earlier page’s finding seen from the clock’s side. One doubling late said the growth seen is the growth to come one doubling late, because this directory’s growth slowed at every doubling. A clock that counts files measures the wrong growth. A clock that reads the escape measures the growth that costs bits, the characters named through a code word that was not fitted to them, and does not care how many files carry them.

The loss at every version

Version by version the held clock's loss is under a tenth of a bit at 30 of 47 versions and peaks at 0.418; the best doubling schedule a store can compute is under a tenth at 10, over half a bit at 11, and peaks at 1.000 — the full bit an escape weighted as half the coding space costs just after a refit, when nothing is yet behind itThe loss at every version — bits a symbol beyond the Huffman code refitted at that version — for the doubling schedule with the escape weighted by the growth seen and half at first, the doubling schedule with the weight read from the future, and the clock held to a quarter's growth. Means: doubling, growth seen, half at first 0.356, doubling, growth to come 0.183, clock, held to 1.25× 0.098.00.2500.5000.7501010203040versionloss, bits a symbolclock, held to 1.25×doubling, growth seen, half at firstdoubling, growth to come47 versions, 20 to 222 filesloss at each version
Fig. 4 The loss at every version for the doubling schedule weighted by the growth seen from a half start, the doubling schedule weighted by the growth to come, and the clock held to a quarter’s growth. The held clock is under a tenth of a bit at 30 versions of 47 and peaks at 0.418; the doubling schedule is under a tenth at 10, over half a bit at 11, and peaks at 1.000.

The held clock’s loss is under a tenth of a bit at 30 of the 47 versions and peaks at 0.418; the best doubling schedule a store can compute is under a tenth at 10, over half a bit at 11, and peaks at a full bit. The doubling schedule’s full bit is its escape weighted as half the coding space: just after a refit nothing is behind the escape, and every document’s code word is one bit longer than it needs to be for nothing. Its half-bit stretches are the long gaps between late refits, where the escape names more and more characters through a code word fitted to fewer.

The clock’s loss has a different shape. It rises after each refit as the escape fills, and is cut off by the next refit before it rises far. Its highest point, 0.418, comes at version 20, inside a stretch where the hold has kept it from the refits the clock with no hold made at versions 15, 16 and 20; the clock with no hold peaks at 0.231. The hold is a trade between that peak and the rewriting, and a quarter’s growth is the point on the frontier plate where it costs least. A mean over 47 versions hides which versions pay, and here the versions that pay most under doubling are the late ones, where the array is largest and every bit a symbol is the most characters’ worth.

An escape the clock can see

The clock can only see an escape whose code word is long: weighted as one document the escape starts at 4 bits and the clock refits 11 times for a mean loss of 0.098; weighted as half the coding space it starts at one bit, the share behind it never passes twice a half, and the clock refits only at the startThe held clock (a factor of 2, held to a quarter's growth) and the doubling schedule under each escape weight: escape as one document: clock 0.098 for 4.52 with 11 refits, its first code word 4 bits; doubling 0.622 for 5.65. escape as half the coding space: clock 0.384 for 17.04 with 1 refits, its first code word 1 bits; doubling 0.389 for 5.65. escape growth seen, half at first: clock 0.384 for 17.04 with 1 refits, its first code word 1 bits; doubling 0.356 for 5.65.escape as one document0.098 · 11 refits0.622escape as half the coding space0.384 · 1 refits0.389escape growth seen, half at first0.384 · 1 refits0.356upper bar: the held clock · lower bar: doubling the filesmean loss, bits a symbolfactor 2, held to 1.25×mean loss
Fig. 5 The held clock and the doubling schedule under three escape weights. As one document: the clock 0.098 with 11 refits, its first code word 4 bits; doubling 0.622. As half the coding space: the clock 0.384 with one refit, its first code word 1 bit; doubling 0.389. Growth seen from half: the same as half for the clock, 0.356 for doubling.

The clock can only see an escape whose code word is long: weighted as one document the escape starts at four bits and the clock refits eleven times for a mean loss of 0.098; weighted as half the coding space it starts at one bit, the share behind it never passes twice a half, and the clock refits only at the start. With one refit, the escape weighted as half the coding space loses 0.384 bits a symbol, about what doubling loses under the same weight, and the clock reads 17 arrays rebuilding an escape subtree that grows to most of the collection.

This reverses the earlier page’s ranking of the weights. Under doubling, the escape weighted as one document was the worst, 0.622, because between refits it names a growing share of the characters through a code word sized for one document. Under the clock, that weakness is exactly what makes the escape a good instrument: a code word that assumes a small share is wrong as soon as the share grows, and the clock reads the wrongness and corrects it. A weight that hedges, half the coding space, is never far wrong and so is never corrected.

A clock read off the structure

The day a filter cannot grow rebuilt a filter from its store when its own count of keys said its false-positive rate had passed a threshold, a clock read off the structure rather than off the calendar, and found it better than rebuilding on a schedule. The escape clock is the same idea for a code. The structure carries, at every moment, a measure of how wrong it is — the share behind the escape against the share the escape was sized for — and a refit made when that measure crosses a threshold is made when it is needed.

The hold is the part the proposal did not have, and the part that makes it a schedule. A threshold on wrongness alone refits whenever the Huffman code is wrong by a bit, which in a small and fast-growing collection is nearly always; choosing a growth factor is the classical reminder that a structure which resizes whenever it is full resizes too often unless it resizes by a factor. The hold is that factor: the Huffman code may be wrong, but it is refitted only once the collection’s characters have grown enough for the refit to be worth its reading. Together the two read the right growth and refit at a pace the rewriting can bear.

What one history cannot settle

One directory. Every number is one source directory’s history, whose growth was fast early, slow in the middle and lumpy late — a real history rather than a generated one, the reason a corpus that was not generated gave for using it. A collection that grows evenly would give the doubling schedule less to lose and the clock less to catch.

One shape of code. The array is coded by a Huffman code over lengths with one escape. A code with several escapes — one for each era of additions — would let a refit touch only part of the Huffman code, and a code word is at least one bit is the reminder that an escape weighted as half the coding space cannot be made shorter than the one bit it already is.

Factors on a coarse grid. The clock was run at factors of 1.5, 2, 4 and 8 and holds of nothing, a quarter and a half. A hold of a third, or one that grows with the collection, was not run, and the frontier between 3.84 and 4.52 arrays read is drawn from two points.

Still open: a hold set by what the refit will cost

The hold is a fixed share of growth, and the plates say its right value depends on where in the history the collection is: early, a refit reads a small array and a quarter’s growth is a few versions; late, a refit reads a large one and a quarter’s growth is many. A hold could instead be set by price. The rewriting a refit costs is the array’s current size; the loss it removes is the bits a symbol the escape is wasting times the characters that will be coded before the next refit. A clock could refit when the waste accumulated since the last refit, in bits, passes the bytes a refit would read.

The measurement that follows gives the clock that rule — refit when the bits lost since the last refit exceed the array’s size times a constant, swept — and charges it as here. The prediction is that it lands on the frontier at least as well as the best fixed hold, at about 0.08 bits a symbol for 4.5 arrays, because it refits early in the history when refits are cheap and late only when the waste has grown large. It could fail on the late history: the waste accumulates slowly there and the rule would wait, as the doubling schedule did, while a few large files sit behind a code word fitted before they arrived. The question is whether a refit can be priced in the same units as the loss it removes, or whether the bytes a refit reads and the bits a code wastes are costs no single exchange rate makes comparable.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

The objects this essay names

Each one links to every other essay that touches it.

AmortisationDocument arrayDocument collectionEntropyHuffman codingIndex sizePrediction