{
  "id": "P166",
  "slug": "replica-placement",
  "key": "P166-replica-placement",
  "title": "Replica placement",
  "summary": "Three replicas a shard on racks of 6, 3 and 3 nodes, by distinct nodes or by distinct racks: a check that counts nodes while failures arrive by rack, uneven loads, and a shared service no replica count moves.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P166 - Replica placement\n\nThree replicas a shard, two needed to serve, on racks of 6, 3 and 3\nnodes: placed on any three distinct nodes, or one in each of three racks.\n\n- `main.eml` - the menu, the cluster, the placements, a rack down, the\n  load per node, availability, and a cluster of your own\n- `placement.eml` - the racks, the node triples each rule allows, what a\n  rack's failure leaves, and the minutes a year a shard cannot serve\n\nHow each part works:\n\n- A shard is equally likely to land on any node triple its rule allows, so\n  every figure is an exact count over those triples.\n- As in the corpus case `the-quorum-was-three-and-two-shared-a-rack`, the\n  distinct-nodes rule allows 220 triples and its check passes on all of\n  them, and 166 (75.5%) put two or more replicas in one rack. With rack A\n  down, 50.0% of them cannot serve; rack B takes 12.7%. One replica in each\n  of three racks: no single rack takes a quorum.\n- As in `three-copies-and-two-places-to-lose-them`, three copies are three\n  chances only in three places, and the racks are not the same size:\n  spreading by rack puts 16.7% of the shards on each node of A and 33.3%\n  on each node of B and C, where distinct nodes put 25.0% on every node.\n- As in `the-redundancy-shared-a-dependency`, each rack is down 1 in 1000\n  of the time on its own, and every rack reads one configuration service,\n  down 5 in 1000. One replica in each rack cannot serve for 1.58 minutes a\n  year from the racks, 2,629.57 with the service; two in one rack, 525.60\n  and 3,150.97. The service's 2,628 minutes are not touched by any number\n  of replicas.\n\nWhat is checked: menu choices 1 to 7; a rack A, B or C (in capitals or\nnot); 2 to 6 rack sizes from 1 to 20, separated by spaces, at least 3\nnodes in all. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the cluster, the placements, racks A and B down, the load and the\n  availability;\n- clusters of 4 4 4 (an even load under rule 2) and 10 2 (no third rack).\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- racks: a cancel, D and x;\n- clusters: a cancel, one rack, seven racks, a rack of 0 and one of 21, 1 1,\n  6,3,3, a letter and 100 3;\n- the smallest cluster, 1 1 1.\n\nBuilt on the verified corpus cases `the-quorum-was-three-and-two-shared-a-rack`\n(three replicas on distinct nodes, two of them in one rack),\n`three-copies-and-two-places-to-lose-them` (replicas spread by node over\nracks of 12, 4 and 4 nodes) and `the-redundancy-shared-a-dependency` (three\nzones at 99.9 percent and one certificate authority they all use).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P166 replica placement: three replicas a shard on racks of 6, 3 and 3\n# nodes, placed by distinct nodes or by distinct racks - the placements, a\n# rack down, the load per node, availability, and a cluster of your own.\nimport placement\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef pad(s, n):\n    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\ndef commas(n):\n    str(n) => s\n    \"\" => out\n    0 => k\n    len(s) => i\n    while i > 0:\n        if k == 3:\n            \",\" + out => out\n            0 => k\n        s[i - 1] + out => out\n        k + 1 => k\n        i - 1 => i\n    return out\n\ndef pct(a, b):\n    # a of b as a percent to one place, half-up\n    a * 2000 + b => y\n    int((y - y % (2 * b)) / (2 * b)) => t\n    return str(int((t - t % 10) / 10)) + \".\" + str(t % 10) + \"%\"\n\ndef minutes(parts):\n    placement.minutes_hundredths(parts) => h\n    h % 100 => c\n    if c < 10:\n        return commas(int((h - c) / 100)) + \".0\" + str(c)\n    return commas(int((h - c) / 100)) + \".\" + str(c)\n\ndef names(rs):\n    \"\" => out\n    0 => i\n    while i < len(rs):\n        if i > 0 and i == len(rs) - 1:\n            out + \" and \" => out\n        elif i > 0:\n            out + \", \" => out\n        out + rs[i][0] => out\n        i + 1 => i\n    return out\n\ndef show_cluster():\n    placement.racks() => rs\n    (\"Racks A, B and C with 6, 3 and 3 nodes: \" + str(placement.nodes(rs)) + \" nodes. Every shard has 3 replicas and needs 2 to serve.\") ^0\n    \"Rule 1, distinct nodes: no two replicas of a shard on one node. Rule 2, distinct racks: one replica in each of three racks.\" ^0\n    \"A shard is equally likely to land on any node triple its rule allows.\" ^0\n\ndef show_placements():\n    placement.racks() => rs\n    placement.node_triples(rs) => t\n    placement.two_in_one(rs) => e\n    \"\" => each\n    0 => x\n    while x < len(rs):\n        placement.lose_nodes(rs, x) => l\n        if each != \"\":\n            each + \", \" => each\n        each + str(l[1] + l[2]) + \" in \" + rs[x][0] => each\n        x + 1 => x\n    (\"Rule 1 allows \" + str(t) + \" node triples, and the placement check passes on every one.\") ^0\n    (\"  \" + str(e) + \" of them (\" + pct(e, t) + \") put two or more replicas in one rack: \" + each + \".\") ^0\n    (\"Rule 2 allows \" + str(placement.rack_triples(rs)) + \" triples, and none puts two in one rack.\") ^0\n    \"The check counts nodes; failures arrive by rack.\" ^0\n\ndef rack_down(rs, x):\n    placement.node_triples(rs) => t\n    placement.lose_nodes(rs, x) => l\n    t - l[0] - l[1] - l[2] => untouched\n    (\"  rule 1: of \" + str(t) + \" triples, \" + str(untouched) + \" untouched, \" + str(l[0]) + \" left with 2, \" + str(l[1]) + \" left with 1 and no quorum, \" + str(l[2]) + \" left with none: \" + pct(l[1] + l[2], t) + \" cannot serve.\") ^0\n    if len(rs) >= 3:\n        placement.rack_triples(rs) => r\n        placement.lose_racks(rs, x) => k\n        (\"  rule 2: of \" + str(r) + \" triples, \" + str(r - k) + \" untouched and \" + str(k) + \" left with 2: every shard still serves.\") ^0\n\ndef show_rack():\n    placement.racks() => rs\n    plain(input(\"which rack (A, B or C)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    0 - 1 => x\n    0 => i\n    for r in rs:\n        if t == r[0] or t == [\"a\", \"b\", \"c\"][i]:\n            i => x\n        i + 1 => i\n    if x < 0:\n        \"Type A, B or C.\" ^0\n        return\n    (\"Rack \" + rs[x][0] + \" down, \" + str(rs[x][1]) + \" nodes:\") ^0\n    rack_down(rs, x)\n\ndef loads(rs):\n    # [least, most] share of shards a node holds under rule 2, as [triples, of]\n    placement.rack_triples(rs) => r\n    0 - 1 => lo\n    0 => hi\n    0 => x\n    while x < len(rs):\n        placement.per_node_racks(rs, x) => k\n        if lo < 0 or k < lo:\n            k => lo\n        if k > hi:\n            k => hi\n        x + 1 => x\n    return [lo, hi, r]\n\ndef show_load():\n    placement.racks() => rs\n    placement.nodes(rs) => n\n    (\"Replicas a node holds, as a share of the shards: rule 1, every node \" + pct(3, n) + \" (3 of \" + str(n) + \").\") ^0\n    \"\" => each\n    0 => x\n    while x < len(rs):\n        if each != \"\":\n            each + \", \" => each\n        each + \"in \" + rs[x][0] + \" \" + pct(placement.per_node_racks(rs, x), placement.rack_triples(rs)) => each\n        x + 1 => x\n    (\"Rule 2: a node \" + each + \". The small racks' nodes carry twice as much: every shard puts one replica in each rack, and 3 nodes share what 6 share in A.\") ^0\n\ndef show_availability():\n    \"Each rack is down 1 in 1000 of the time, on its own; every rack reads its configuration from one service, down 5 in 1000.\" ^0\n    (\"  \" + pad(\"minutes a year a shard cannot serve\", 40) + lpad(\"racks alone\", 12) + lpad(\"with the service\", 18)) ^0\n    (\"  \" + pad(\"one replica in each rack\", 40) + lpad(minutes(placement.down_parts(False, False)), 12) + lpad(minutes(placement.down_parts(False, True)), 18)) ^0\n    (\"  \" + pad(\"two of the replicas in one rack\", 40) + lpad(minutes(placement.down_parts(True, False)), 12) + lpad(minutes(placement.down_parts(True, True)), 18)) ^0\n    \"A replica in a third rack cuts the rack term from 525.60 minutes to 1.58. The service is not a rack, and no number of replicas moves its 2,628 minutes.\" ^0\n\ndef sizes(t):\n    # the numbers in t, separated by spaces; [] if anything else is there\n    [] => out\n    \"\" => cur\n    for c in t + \" \":\n        if c == \" \":\n            if cur != \"\":\n                if len(cur) > 2:\n                    return []\n                out + [int(cur)] => out\n                \"\" => cur\n        elif c < \"0\" or c > \"9\":\n            return []\n        else:\n            cur + c => cur\n    return out\n\ndef own():\n    plain(input(\"nodes in each rack, e.g. 6 3 3> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    sizes(t) => ns\n    True => ok\n    if len(ns) < 2 or len(ns) > 6:\n        False => ok\n    0 => total\n    for v in ns:\n        total + v => total\n        if v < 1 or v > 20:\n            False => ok\n    if not ok or total < 3:\n        \"Type 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\" ^0\n        return\n    [] => rs\n    0 => i\n    for v in ns:\n        rs + [[[\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"][i], v]] => rs\n        i + 1 => i\n    placement.node_triples(rs) => tr\n    placement.two_in_one(rs) => e\n    0 => worst\n    0 => x\n    while x < len(rs):\n        placement.lose_nodes(rs, x) => l\n        placement.lose_nodes(rs, worst) => w\n        if l[1] + l[2] > w[1] + w[2]:\n            x => worst\n        x + 1 => x\n    placement.lose_nodes(rs, worst) => w\n    \" triples\" => word\n    if tr == 1:\n        \" triple\" => word\n    (str(total) + \" nodes in racks \" + names(rs) + \": rule 1 allows \" + commas(tr) + word + \", \" + commas(e) + \" (\" + pct(e, tr) + \") with two or more in one rack; rack \" + rs[worst][0] + \" down leaves \" + commas(w[1] + w[2]) + \" (\" + pct(w[1] + w[2], tr) + \") without quorum.\") ^0\n    if len(rs) < 3:\n        \"Rule 2 needs three racks.\" ^0\n        return\n    loads(rs) => ld\n    if ld[0] == ld[1]:\n        (\"Rule 2 allows \" + commas(ld[2]) + \", and one rack down never takes a quorum; every node holds \" + pct(ld[0], ld[2]) + \" of the shards.\") ^0\n    else:\n        (\"Rule 2 allows \" + commas(ld[2]) + \", and one rack down never takes a quorum; a node holds \" + pct(ld[0], ld[2]) + \" to \" + pct(ld[1], ld[2]) + \" of the shards.\") ^0\n\n\"== Replica placement ==\" ^0\n\"Three replicas a shard, two needed to serve, on racks of different sizes.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_cluster()\n    elif choice == \"2\":\n        show_placements()\n    elif choice == \"3\":\n        show_rack()\n    elif choice == \"4\":\n        show_load()\n    elif choice == \"5\":\n        show_availability()\n    elif choice == \"6\":\n        own()\n    elif choice == \"7\":\n        False => running\n    else:\n        \"Pick a number from 1 to 7.\" ^0\n\"Bye.\" ^0\n",
      "python": "import placement\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef pad(s, n):\n    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\ndef commas(n):\n    s = str(n)\n    out = \"\"\n    k = 0\n    i = len(s)\n    while i > 0:\n        if k == 3:\n            out = \",\" + out\n            k = 0\n        out = s[i - 1] + out\n        k = k + 1\n        i = i - 1\n    return out\n\ndef pct(a, b):\n    y = a * 2000 + b\n    t = int((y - y % (2 * b)) / (2 * b))\n    return str(int((t - t % 10) / 10)) + \".\" + str(t % 10) + \"%\"\n\ndef minutes(parts):\n    h = placement.minutes_hundredths(parts)\n    c = h % 100\n    if c < 10:\n        return commas(int((h - c) / 100)) + \".0\" + str(c)\n    return commas(int((h - c) / 100)) + \".\" + str(c)\n\ndef names(rs):\n    out = \"\"\n    i = 0\n    while i < len(rs):\n        if i > 0 and i == len(rs) - 1:\n            out = out + \" and \"\n        elif i > 0:\n            out = out + \", \"\n        out = out + rs[i][0]\n        i = i + 1\n    return out\n\ndef show_cluster():\n    rs = placement.racks()\n    print(\"Racks A, B and C with 6, 3 and 3 nodes: \" + str(placement.nodes(rs)) + \" nodes. Every shard has 3 replicas and needs 2 to serve.\")\n    print(\"Rule 1, distinct nodes: no two replicas of a shard on one node. Rule 2, distinct racks: one replica in each of three racks.\")\n    print(\"A shard is equally likely to land on any node triple its rule allows.\")\n\ndef show_placements():\n    rs = placement.racks()\n    t = placement.node_triples(rs)\n    e = placement.two_in_one(rs)\n    each = \"\"\n    x = 0\n    while x < len(rs):\n        l = placement.lose_nodes(rs, x)\n        if each != \"\":\n            each = each + \", \"\n        each = each + str(l[1] + l[2]) + \" in \" + rs[x][0]\n        x = x + 1\n    print(\"Rule 1 allows \" + str(t) + \" node triples, and the placement check passes on every one.\")\n    print(\"  \" + str(e) + \" of them (\" + pct(e, t) + \") put two or more replicas in one rack: \" + each + \".\")\n    print(\"Rule 2 allows \" + str(placement.rack_triples(rs)) + \" triples, and none puts two in one rack.\")\n    print(\"The check counts nodes; failures arrive by rack.\")\n\ndef rack_down(rs, x):\n    t = placement.node_triples(rs)\n    l = placement.lose_nodes(rs, x)\n    untouched = t - l[0] - l[1] - l[2]\n    print(\"  rule 1: of \" + str(t) + \" triples, \" + str(untouched) + \" untouched, \" + str(l[0]) + \" left with 2, \" + str(l[1]) + \" left with 1 and no quorum, \" + str(l[2]) + \" left with none: \" + pct(l[1] + l[2], t) + \" cannot serve.\")\n    if len(rs) >= 3:\n        r = placement.rack_triples(rs)\n        k = placement.lose_racks(rs, x)\n        print(\"  rule 2: of \" + str(r) + \" triples, \" + str(r - k) + \" untouched and \" + str(k) + \" left with 2: every shard still serves.\")\n\ndef show_rack():\n    rs = placement.racks()\n    t = plain(input(\"which rack (A, B or C)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    x = 0 - 1\n    i = 0\n    for r in rs:\n        if t == r[0] or t == [\"a\", \"b\", \"c\"][i]:\n            x = i\n        i = i + 1\n    if x < 0:\n        print(\"Type A, B or C.\")\n        return\n    print(\"Rack \" + rs[x][0] + \" down, \" + str(rs[x][1]) + \" nodes:\")\n    rack_down(rs, x)\n\ndef loads(rs):\n    r = placement.rack_triples(rs)\n    lo = 0 - 1\n    hi = 0\n    x = 0\n    while x < len(rs):\n        k = placement.per_node_racks(rs, x)\n        if lo < 0 or k < lo:\n            lo = k\n        if k > hi:\n            hi = k\n        x = x + 1\n    return [lo, hi, r]\n\ndef show_load():\n    rs = placement.racks()\n    n = placement.nodes(rs)\n    print(\"Replicas a node holds, as a share of the shards: rule 1, every node \" + pct(3, n) + \" (3 of \" + str(n) + \").\")\n    each = \"\"\n    x = 0\n    while x < len(rs):\n        if each != \"\":\n            each = each + \", \"\n        each = each + \"in \" + rs[x][0] + \" \" + pct(placement.per_node_racks(rs, x), placement.rack_triples(rs))\n        x = x + 1\n    print(\"Rule 2: a node \" + each + \". The small racks' nodes carry twice as much: every shard puts one replica in each rack, and 3 nodes share what 6 share in A.\")\n\ndef show_availability():\n    print(\"Each rack is down 1 in 1000 of the time, on its own; every rack reads its configuration from one service, down 5 in 1000.\")\n    print(\"  \" + pad(\"minutes a year a shard cannot serve\", 40) + lpad(\"racks alone\", 12) + lpad(\"with the service\", 18))\n    print(\"  \" + pad(\"one replica in each rack\", 40) + lpad(minutes(placement.down_parts(False, False)), 12) + lpad(minutes(placement.down_parts(False, True)), 18))\n    print(\"  \" + pad(\"two of the replicas in one rack\", 40) + lpad(minutes(placement.down_parts(True, False)), 12) + lpad(minutes(placement.down_parts(True, True)), 18))\n    print(\"A replica in a third rack cuts the rack term from 525.60 minutes to 1.58. The service is not a rack, and no number of replicas moves its 2,628 minutes.\")\n\ndef sizes(t):\n    out = []\n    cur = \"\"\n    for c in t + \" \":\n        if c == \" \":\n            if cur != \"\":\n                if len(cur) > 2:\n                    return []\n                out = out + [int(cur)]\n                cur = \"\"\n        elif c < \"0\" or c > \"9\":\n            return []\n        else:\n            cur = cur + c\n    return out\n\ndef own():\n    t = plain(input(\"nodes in each rack, e.g. 6 3 3> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    ns = sizes(t)\n    ok = True\n    if len(ns) < 2 or len(ns) > 6:\n        ok = False\n    total = 0\n    for v in ns:\n        total = total + v\n        if v < 1 or v > 20:\n            ok = False\n    if not ok or total < 3:\n        print(\"Type 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\")\n        return\n    rs = []\n    i = 0\n    for v in ns:\n        rs = rs + [[[\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"][i], v]]\n        i = i + 1\n    tr = placement.node_triples(rs)\n    e = placement.two_in_one(rs)\n    worst = 0\n    x = 0\n    while x < len(rs):\n        l = placement.lose_nodes(rs, x)\n        w = placement.lose_nodes(rs, worst)\n        if l[1] + l[2] > w[1] + w[2]:\n            worst = x\n        x = x + 1\n    w = placement.lose_nodes(rs, worst)\n    word = \" triples\"\n    if tr == 1:\n        word = \" triple\"\n    print(str(total) + \" nodes in racks \" + names(rs) + \": rule 1 allows \" + commas(tr) + word + \", \" + commas(e) + \" (\" + pct(e, tr) + \") with two or more in one rack; rack \" + rs[worst][0] + \" down leaves \" + commas(w[1] + w[2]) + \" (\" + pct(w[1] + w[2], tr) + \") without quorum.\")\n    if len(rs) < 3:\n        print(\"Rule 2 needs three racks.\")\n        return\n    ld = loads(rs)\n    if ld[0] == ld[1]:\n        print(\"Rule 2 allows \" + commas(ld[2]) + \", and one rack down never takes a quorum; every node holds \" + pct(ld[0], ld[2]) + \" of the shards.\")\n    else:\n        print(\"Rule 2 allows \" + commas(ld[2]) + \", and one rack down never takes a quorum; a node holds \" + pct(ld[0], ld[2]) + \" to \" + pct(ld[1], ld[2]) + \" of the shards.\")\n\nprint(\"== Replica placement ==\")\nprint(\"Three replicas a shard, two needed to serve, on racks of different sizes.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_cluster()\n    elif choice == \"2\":\n        show_placements()\n    elif choice == \"3\":\n        show_rack()\n    elif choice == \"4\":\n        show_load()\n    elif choice == \"5\":\n        show_availability()\n    elif choice == \"6\":\n        own()\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 7.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "placement.eml",
      "eml": "# P166 replica placement - three replicas a shard, two needed to serve, on\n# racks of different sizes, under two rules: any three distinct nodes, or\n# one replica in each of three racks. Every count is over all the node\n# triples a rule allows, each equally likely. As in the corpus case\n# the-quorum-was-three-and-two-shared-a-rack, the distinct-nodes rule passes\n# its check while failures arrive by rack. As in\n# three-copies-and-two-places-to-lose-them, three copies are three chances\n# only when they are in three places, and spreading by rack loads the small\n# racks' nodes harder. And as in the-redundancy-shared-a-dependency,\n# independent racks multiply, and a service they all share does not.\n\ndef racks():\n    # [name, nodes]\n    return [[\"A\", 6], [\"B\", 3], [\"C\", 3]]\n\ndef choose(n, k):\n    if k < 0 or k > n:\n        return 0\n    1 => r\n    1 => i\n    while i <= k:\n        int(r * (n - k + i) / i) => r\n        i + 1 => i\n    return r\n\ndef nodes(rs):\n    0 => t\n    for r in rs:\n        t + r[1] => t\n    return t\n\ndef node_triples(rs):\n    return choose(nodes(rs), 3)\n\ndef rack_triples(rs):\n    # node triples with one node in each of three racks\n    0 => t\n    len(rs) => k\n    0 => a\n    while a < k:\n        a + 1 => b\n        while b < k:\n            b + 1 => c\n            while c < k:\n                t + rs[a][1] * rs[b][1] * rs[c][1] => t\n                c + 1 => c\n            b + 1 => b\n        a + 1 => a\n    return t\n\ndef others(rs, x):\n    # pairs of nodes from two different racks, neither of them rack x\n    0 => t\n    len(rs) => k\n    0 => a\n    while a < k:\n        a + 1 => b\n        while b < k:\n            if a != x and b != x:\n                t + rs[a][1] * rs[b][1] => t\n            b + 1 => b\n        a + 1 => a\n    return t\n\ndef lose_nodes(rs, x):\n    # node triples, under any three distinct nodes, that rack x's failure\n    # leaves with [2 replicas, 1 (no quorum), 0]\n    rs[x][1] => n\n    nodes(rs) - n => rest\n    return [n * choose(rest, 2), choose(n, 2) * rest, choose(n, 3)]\n\ndef lose_racks(rs, x):\n    # one-in-each-rack triples that rack x's failure leaves with 2 replicas\n    return rs[x][1] * others(rs, x)\n\ndef two_in_one(rs):\n    # node triples with two or three replicas in one rack\n    0 => t\n    0 => x\n    while x < len(rs):\n        lose_nodes(rs, x) => l\n        t + l[1] + l[2] => t\n        x + 1 => x\n    return t\n\ndef per_node_racks(rs, x):\n    # one-in-each-rack triples that hold a given node of rack x\n    return others(rs, x)\n\ndef down_parts(two_in_one, shared):\n    # per 10^12, the chance a shard cannot serve, each rack down 1 in 1000\n    # independently. One replica a rack: two of the three racks down. Two\n    # in one rack: that rack down. With shared, the configuration service\n    # every rack reads, down 5 in 1000, as well.\n    3 * 999 + 1 => p\n    if two_in_one:\n        1000000 => p\n    if not shared:\n        return p * 1000\n    return 1000000000000 - (1000000000 - p) * 995\n\ndef minutes_hundredths(parts):\n    # minutes a year for a chance of parts per 10^12, in hundredths, half-up\n    parts * 5256 + 50000000 => y\n    return int((y - y % 100000000) / 100000000)\n",
      "python": "def racks():\n    return [[\"A\", 6], [\"B\", 3], [\"C\", 3]]\n\ndef choose(n, k):\n    if k < 0 or k > n:\n        return 0\n    r = 1\n    i = 1\n    while i <= k:\n        r = int(r * (n - k + i) / i)\n        i = i + 1\n    return r\n\ndef nodes(rs):\n    t = 0\n    for r in rs:\n        t = t + r[1]\n    return t\n\ndef node_triples(rs):\n    return choose(nodes(rs), 3)\n\ndef rack_triples(rs):\n    t = 0\n    k = len(rs)\n    a = 0\n    while a < k:\n        b = a + 1\n        while b < k:\n            c = b + 1\n            while c < k:\n                t = t + rs[a][1] * rs[b][1] * rs[c][1]\n                c = c + 1\n            b = b + 1\n        a = a + 1\n    return t\n\ndef others(rs, x):\n    t = 0\n    k = len(rs)\n    a = 0\n    while a < k:\n        b = a + 1\n        while b < k:\n            if a != x and b != x:\n                t = t + rs[a][1] * rs[b][1]\n            b = b + 1\n        a = a + 1\n    return t\n\ndef lose_nodes(rs, x):\n    n = rs[x][1]\n    rest = nodes(rs) - n\n    return [n * choose(rest, 2), choose(n, 2) * rest, choose(n, 3)]\n\ndef lose_racks(rs, x):\n    return rs[x][1] * others(rs, x)\n\ndef two_in_one(rs):\n    t = 0\n    x = 0\n    while x < len(rs):\n        l = lose_nodes(rs, x)\n        t = t + l[1] + l[2]\n        x = x + 1\n    return t\n\ndef per_node_racks(rs, x):\n    return others(rs, x)\n\ndef down_parts(two_in_one, shared):\n    p = 3 * 999 + 1\n    if two_in_one:\n        p = 1000000\n    if not shared:\n        return p * 1000\n    return 1000000000000 - (1000000000 - p) * 995\n\ndef minutes_hundredths(parts):\n    y = parts * 5256 + 50000000\n    return int((y - y % 100000000) / 100000000)\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n3\n\n3\nD\n3\nx\n6\n\n6\n6\n6\n6 3 3 3 3 3 3\n6\n0 3 3\n6\n21 3\n6\n1 1\n6\n6,3,3\n6\n6 3 x\n6\n100 3\n6\n1 1 1\n7\n",
      "screen": "== Replica placement ==\nThree replicas a shard, two needed to serve, on racks of different sizes.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 0\nPick a number from 1 to 7.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> x\nPick a number from 1 to 7.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 3\nwhich rack (A, B or C)> \nCancelled.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 3\nwhich rack (A, B or C)> D\nType A, B or C.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 3\nwhich rack (A, B or C)> x\nType A, B or C.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> \nCancelled.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 6\nType 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 6 3 3 3 3 3 3\nType 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 0 3 3\nType 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 21 3\nType 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 1 1\nType 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 6,3,3\nType 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 6 3 x\nType 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 100 3\nType 2 to 6 rack sizes from 1 to 20, at least 3 nodes in all.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 1 1 1\n3 nodes in racks A, B and C: rule 1 allows 1 triple, 0 (0.0%) with two or more in one rack; rack A down leaves 0 (0.0%) without quorum.\nRule 2 allows 1, and one rack down never takes a quorum; every node holds 100.0% of the shards.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\nA\n3\nb\n4\n5\n6\n4 4 4\n6\n10 2\n7\n",
      "screen": "== Replica placement ==\nThree replicas a shard, two needed to serve, on racks of different sizes.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 1\nRacks A, B and C with 6, 3 and 3 nodes: 12 nodes. Every shard has 3 replicas and needs 2 to serve.\nRule 1, distinct nodes: no two replicas of a shard on one node. Rule 2, distinct racks: one replica in each of three racks.\nA shard is equally likely to land on any node triple its rule allows.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 2\nRule 1 allows 220 node triples, and the placement check passes on every one.\n  166 of them (75.5%) put two or more replicas in one rack: 110 in A, 28 in B, 28 in C.\nRule 2 allows 54 triples, and none puts two in one rack.\nThe check counts nodes; failures arrive by rack.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 3\nwhich rack (A, B or C)> A\nRack A down, 6 nodes:\n  rule 1: of 220 triples, 20 untouched, 90 left with 2, 90 left with 1 and no quorum, 20 left with none: 50.0% cannot serve.\n  rule 2: of 54 triples, 0 untouched and 54 left with 2: every shard still serves.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 3\nwhich rack (A, B or C)> b\nRack B down, 3 nodes:\n  rule 1: of 220 triples, 84 untouched, 108 left with 2, 27 left with 1 and no quorum, 1 left with none: 12.7% cannot serve.\n  rule 2: of 54 triples, 0 untouched and 54 left with 2: every shard still serves.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 4\nReplicas a node holds, as a share of the shards: rule 1, every node 25.0% (3 of 12).\nRule 2: a node in A 16.7%, in B 33.3%, in C 33.3%. The small racks' nodes carry twice as much: every shard puts one replica in each rack, and 3 nodes share what 6 share in A.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 5\nEach rack is down 1 in 1000 of the time, on its own; every rack reads its configuration from one service, down 5 in 1000.\n  minutes a year a shard cannot serve      racks alone  with the service\n  one replica in each rack                        1.58          2,629.57\n  two of the replicas in one rack               525.60          3,150.97\nA replica in a third rack cuts the rack term from 525.60 minutes to 1.58. The service is not a rack, and no number of replicas moves its 2,628 minutes.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 4 4 4\n12 nodes in racks A, B and C: rule 1 allows 220 triples, 156 (70.9%) with two or more in one rack; rack A down leaves 52 (23.6%) without quorum.\nRule 2 allows 64, and one rack down never takes a quorum; every node holds 25.0% of the shards.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 6\nnodes in each rack, e.g. 6 3 3> 10 2\n12 nodes in racks A and B: rule 1 allows 220 triples, 220 (100.0%) with two or more in one rack; rack A down leaves 210 (95.5%) without quorum.\nRule 2 needs three racks.\n\n1) the cluster  2) placements  3) a rack down  4) load per node  5) availability  6) a cluster of your own  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "the-quorum-was-three-and-two-shared-a-rack",
      "caseId": "621-the-quorum-was-three-and-two-shared-a-rack",
      "title": "The quorum was three and two shared a rack"
    },
    {
      "slug": "three-copies-and-two-places-to-lose-them",
      "caseId": "551-three-copies-and-two-places-to-lose-them",
      "title": "Three copies and two places to lose them"
    },
    {
      "slug": "the-redundancy-shared-a-dependency",
      "caseId": "577-the-redundancy-shared-a-dependency",
      "title": "The redundancy shared a dependency"
    }
  ],
  "updated": "2026-10-19"
}
