{
  "id": "P167",
  "slug": "backup-planner",
  "key": "P167-backup-planner",
  "title": "Backup planner",
  "summary": "An order system's changes hour by hour against a one-hour recovery point objective: a nightly backup that has never failed meets it for 1 hour of 24, and every archive carries the key that encrypts it.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P167 - Backup planner\n\nAn order system's changes hour by hour, a recovery point objective of one\nhour, three backup schedules, and what one archive holds.\n\n- `main.eml` - the menu, the system, a failure at each hour, what an\n  archive holds, and a schedule of your own\n- `backup.eml` - the changes in each hour, the objective, the schedules,\n  the age of the last backup before a failure and what it loses, and the\n  archive's contents\n\nHow each part works:\n\n- 300 changes an hour at night, 4,200 in office hours, 51,100 a day. A\n  failure at h:00 loses everything written since the last backup before\n  it; the objective is to lose at most one hour of work.\n- As in the corpus case `the-backup-ran-nightly-and-the-change-was-hourly`,\n  a nightly backup at 02:00 that has succeeded 400 nights in a row is\n  within the objective for 1 of the 24 failure hours, and a failure just\n  before it loses the whole day, 51,100 changes. Every 6 hours meets it\n  for 4 hours of 24 and loses up to 25,200; only an incremental every hour\n  meets it for all 24. The success count says the archive was made; the\n  schedule alone says how old it is when it is needed.\n- As in `the-backup-was-encrypted-and-the-key-was-in-the-backup`, the\n  archive is a snapshot of the whole host, and the key it is encrypted\n  with is a file on that host: with 90 archives and 3 copies of each off\n  site, the key is in 270 places, each next to the data it protects.\n\nWhat is checked: menu choices 1 to 5; a first backup hour from 0 to 23 and\na backup every 1 to 24 hours. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the system, a failure at each hour, and the archive;\n- your own schedules: from 02:00 every 5 hours, from 00:00 every hour,\n  and once a day at 09:00.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- first hours: a cancel, 24 and x; then intervals with a cancel, 0, 25\n  and x;\n- from 23:00 every 7 hours, and from 03:00 every 12.\n\nBuilt on the verified corpus cases `the-backup-ran-nightly-and-the-change-was-hourly`\n(a nightly backup, 400 successes, and a one-hour recovery point objective)\nand `the-backup-was-encrypted-and-the-key-was-in-the-backup` (encrypted host\nsnapshots that hold the key material the host needs to start).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P167 backup planner: an order system's changes hour by hour against a\n# one-hour recovery point objective - the system, a failure at each hour\n# under three schedules, what one archive holds, and a schedule of your own.\nimport backup\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 clock(h):\n    if h < 10:\n        return \"0\" + str(h) + \":00\"\n    return str(h) + \":00\"\n\ndef clocks(hours):\n    \"\" => out\n    0 => i\n    while i < len(hours):\n        if i > 0 and i == len(hours) - 1:\n            out + \" and \" => out\n        elif i > 0:\n            out + \", \" => out\n        out + clock(hours[i]) => out\n        i + 1 => i\n    return out\n\ndef hours_word(n):\n    if n == 1:\n        return \"1 hour\"\n    return str(n) + \" hours\"\n\ndef show_system():\n    (\"An order system, hour by hour: 300 changes an hour from 00:00 to 07:00, 2,000 to 09:00, 4,200 to 18:00, 1,500 to 22:00 and 600 to midnight - \" + commas(backup.per_day()) + \" a day.\") ^0\n    (\"The recovery point objective: lose at most \" + hours_word(backup.rpo()) + \" of work.\") ^0\n    \"The backups: nightly at 02:00, which has succeeded 400 nights in a row; every 6 hours; and nightly with an incremental every hour.\" ^0\n\ndef summary(sc):\n    # [failure hours within the objective, the worst hour, its loss, its age]\n    0 => within\n    0 => worst\n    0 => h\n    while h < 24:\n        if backup.age(sc, h) <= backup.rpo():\n            within + 1 => within\n        if backup.lost(sc, h) > backup.lost(sc, worst):\n            h => worst\n        h + 1 => h\n    return [within, worst, backup.lost(sc, worst), backup.age(sc, worst)]\n\ndef verdict(name, sc):\n    summary(sc) => s\n    if s[0] == 24:\n        return name + \": every failure hour within the objective; the most a failure loses is \" + commas(s[2]) + \" changes.\"\n    return name + \": within the objective for \" + str(s[0]) + \" of 24 failure hours; the worst, at \" + clock(s[1]) + \", loses \" + commas(s[2]) + \" changes, \" + hours_word(s[3]) + \" of work.\"\n\ndef cell(sc, h):\n    backup.age(sc, h) => a\n    lpad(commas(backup.lost(sc, h)), 7) + lpad(str(a) + \"h\", 4) => out\n    if a <= backup.rpo():\n        out + \" ok\" => out\n    else:\n        out + \"   \" => out\n    return out\n\ndef show_failures():\n    \"A failure at each hour, the changes lost since the last backup, and their age (ok: within the objective):\" ^0\n    backup.schedules() => scs\n    (\"  \" + pad(\"failure\", 9) + pad(scs[0][0], 19) + pad(scs[1][0], 19) + scs[2][0]) ^0\n    0 => h\n    while h < 24:\n        (\"  \" + pad(clock(h), 9) + pad(cell(scs[0][1], h), 19) + pad(cell(scs[1][1], h), 19) + cell(scs[2][1], h)) ^0\n        h + 1 => h\n    for sc in scs:\n        verdict(sc[0], sc[1]) ^0\n    \"400 nights of success say the archive was made. How old it is when it is needed is set by the schedule alone.\" ^0\n\ndef show_archive():\n    \"What one archive holds - a snapshot of the whole application host:\" ^0\n    for f in backup.archive():\n        (\"  \" + pad(f[0], 24) + pad(f[1], 40) + lpad(commas(f[2]) + \" MB\", 10)) ^0\n    backup.kept() => k\n    (str(k[0]) + \" archives are kept, each with \" + str(k[1]) + \" copies off site: the key is in \" + str(k[0] * k[1]) + \" places, each next to the data it protects. Anyone who holds one archive can open it.\") ^0\n    \"With /etc/orders/backup.key left out of the snapshot, a restore asks the key service for the key: no copy in any archive, and one more step in the drill.\" ^0\n\ndef own():\n    plain(input(\"first backup hour (0 to 23)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(t) > 2 or not digits(t) or int(t) > 23:\n        \"Type a whole number from 0 to 23.\" ^0\n        return\n    plain(input(\"every how many hours (1 to 24)> \")) => n\n    if n == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(n) > 2 or not digits(n) or int(n) < 1 or int(n) > 24:\n        \"Type a whole number from 1 to 24.\" ^0\n        return\n    backup.every(int(t), int(n)) => sc\n    (\"Backups at \" + clocks(sc) + \".\") ^0\n    verdict(\"This schedule\", sc) ^0\n\n\"== Backup planner ==\" ^0\n\"Backups that succeed every night, and what a failure still loses.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_system()\n    elif choice == \"2\":\n        show_failures()\n    elif choice == \"3\":\n        show_archive()\n    elif choice == \"4\":\n        own()\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import backup\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 clock(h):\n    if h < 10:\n        return \"0\" + str(h) + \":00\"\n    return str(h) + \":00\"\n\ndef clocks(hours):\n    out = \"\"\n    i = 0\n    while i < len(hours):\n        if i > 0 and i == len(hours) - 1:\n            out = out + \" and \"\n        elif i > 0:\n            out = out + \", \"\n        out = out + clock(hours[i])\n        i = i + 1\n    return out\n\ndef hours_word(n):\n    if n == 1:\n        return \"1 hour\"\n    return str(n) + \" hours\"\n\ndef show_system():\n    print(\"An order system, hour by hour: 300 changes an hour from 00:00 to 07:00, 2,000 to 09:00, 4,200 to 18:00, 1,500 to 22:00 and 600 to midnight - \" + commas(backup.per_day()) + \" a day.\")\n    print(\"The recovery point objective: lose at most \" + hours_word(backup.rpo()) + \" of work.\")\n    print(\"The backups: nightly at 02:00, which has succeeded 400 nights in a row; every 6 hours; and nightly with an incremental every hour.\")\n\ndef summary(sc):\n    within = 0\n    worst = 0\n    h = 0\n    while h < 24:\n        if backup.age(sc, h) <= backup.rpo():\n            within = within + 1\n        if backup.lost(sc, h) > backup.lost(sc, worst):\n            worst = h\n        h = h + 1\n    return [within, worst, backup.lost(sc, worst), backup.age(sc, worst)]\n\ndef verdict(name, sc):\n    s = summary(sc)\n    if s[0] == 24:\n        return name + \": every failure hour within the objective; the most a failure loses is \" + commas(s[2]) + \" changes.\"\n    return name + \": within the objective for \" + str(s[0]) + \" of 24 failure hours; the worst, at \" + clock(s[1]) + \", loses \" + commas(s[2]) + \" changes, \" + hours_word(s[3]) + \" of work.\"\n\ndef cell(sc, h):\n    a = backup.age(sc, h)\n    out = lpad(commas(backup.lost(sc, h)), 7) + lpad(str(a) + \"h\", 4)\n    if a <= backup.rpo():\n        out = out + \" ok\"\n    else:\n        out = out + \"   \"\n    return out\n\ndef show_failures():\n    print(\"A failure at each hour, the changes lost since the last backup, and their age (ok: within the objective):\")\n    scs = backup.schedules()\n    print(\"  \" + pad(\"failure\", 9) + pad(scs[0][0], 19) + pad(scs[1][0], 19) + scs[2][0])\n    h = 0\n    while h < 24:\n        print(\"  \" + pad(clock(h), 9) + pad(cell(scs[0][1], h), 19) + pad(cell(scs[1][1], h), 19) + cell(scs[2][1], h))\n        h = h + 1\n    for sc in scs:\n        print(verdict(sc[0], sc[1]))\n    print(\"400 nights of success say the archive was made. How old it is when it is needed is set by the schedule alone.\")\n\ndef show_archive():\n    print(\"What one archive holds - a snapshot of the whole application host:\")\n    for f in backup.archive():\n        print(\"  \" + pad(f[0], 24) + pad(f[1], 40) + lpad(commas(f[2]) + \" MB\", 10))\n    k = backup.kept()\n    print(str(k[0]) + \" archives are kept, each with \" + str(k[1]) + \" copies off site: the key is in \" + str(k[0] * k[1]) + \" places, each next to the data it protects. Anyone who holds one archive can open it.\")\n    print(\"With /etc/orders/backup.key left out of the snapshot, a restore asks the key service for the key: no copy in any archive, and one more step in the drill.\")\n\ndef own():\n    t = plain(input(\"first backup hour (0 to 23)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(t) > 2 or not digits(t) or int(t) > 23:\n        print(\"Type a whole number from 0 to 23.\")\n        return\n    n = plain(input(\"every how many hours (1 to 24)> \"))\n    if n == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(n) > 2 or not digits(n) or int(n) < 1 or int(n) > 24:\n        print(\"Type a whole number from 1 to 24.\")\n        return\n    sc = backup.every(int(t), int(n))\n    print(\"Backups at \" + clocks(sc) + \".\")\n    print(verdict(\"This schedule\", sc))\n\nprint(\"== Backup planner ==\")\nprint(\"Backups that succeed every night, and what a failure still loses.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_system()\n    elif choice == \"2\":\n        show_failures()\n    elif choice == \"3\":\n        show_archive()\n    elif choice == \"4\":\n        own()\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "backup.eml",
      "eml": "# P167 backup planner - an order system's changes hour by hour, a recovery\n# point objective of one hour, and three backup schedules. As in the corpus\n# case the-backup-ran-nightly-and-the-change-was-hourly, a backup that has\n# succeeded every night says the artifact was made; how old it is when it is\n# needed is set by the schedule alone. And as in\n# the-backup-was-encrypted-and-the-key-was-in-the-backup, an archive that is a\n# snapshot of the whole host carries the key that encrypts it.\n\ndef rpo():\n    # hours of work the business can afford to lose\n    return 1\n\ndef changes(h):\n    # orders and updates written in hour h (0 to 23)\n    if h < 7:\n        return 300\n    if h < 9:\n        return 2000\n    if h < 18:\n        return 4200\n    if h < 22:\n        return 1500\n    return 600\n\ndef schedules():\n    # [name, the hours a backup runs]\n    return [[\"nightly at 02:00\", [2]],\n            [\"every 6 hours\", [0, 6, 12, 18]],\n            [\"nightly + hourly\", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]]]\n\ndef every(start, n):\n    # backups every n hours from start, the same hours each day\n    [] => out\n    0 => k\n    while k < 24:\n        (start + k) % 24 => h\n        False => dup\n        for x in out:\n            if x == h:\n                True => dup\n        if not dup:\n            out + [h] => out\n        k + n => k\n    return out\n\ndef age(hours, h):\n    # hours since the last backup before a failure at h:00 (1 to 24)\n    1 => a\n    while a <= 24:\n        (h - a + 24) % 24 => b\n        for x in hours:\n            if x == b:\n                return a\n        a + 1 => a\n    return 24\n\ndef lost(hours, h):\n    # changes written since that backup\n    age(hours, h) => a\n    0 => t\n    1 => k\n    while k <= a:\n        t + changes((h - k + 24) % 24) => t\n        k + 1 => k\n    return t\n\ndef per_day():\n    0 => t\n    0 => h\n    while h < 24:\n        t + changes(h) => t\n        h + 1 => h\n    return t\n\ndef archive():\n    # [path, what it is, size in MB] in a snapshot of the application host\n    return [[\"/srv/orders/db\", \"the order database\", 48000],\n            [\"/etc/orders/app.conf\", \"the application's settings\", 1],\n            [\"/etc/orders/backup.key\", \"the key the archive is encrypted with\", 1]]\n\ndef kept():\n    # archives kept, and the copies of each held off site\n    return [90, 3]\n",
      "python": "def rpo():\n    return 1\n\ndef changes(h):\n    if h < 7:\n        return 300\n    if h < 9:\n        return 2000\n    if h < 18:\n        return 4200\n    if h < 22:\n        return 1500\n    return 600\n\ndef schedules():\n    return [[\"nightly at 02:00\", [2]], [\"every 6 hours\", [0, 6, 12, 18]], [\"nightly + hourly\", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]]]\n\ndef every(start, n):\n    out = []\n    k = 0\n    while k < 24:\n        h = (start + k) % 24\n        dup = False\n        for x in out:\n            if x == h:\n                dup = True\n        if not dup:\n            out = out + [h]\n        k = k + n\n    return out\n\ndef age(hours, h):\n    a = 1\n    while a <= 24:\n        b = (h - a + 24) % 24\n        for x in hours:\n            if x == b:\n                return a\n        a = a + 1\n    return 24\n\ndef lost(hours, h):\n    a = age(hours, h)\n    t = 0\n    k = 1\n    while k <= a:\n        t = t + changes((h - k + 24) % 24)\n        k = k + 1\n    return t\n\ndef per_day():\n    t = 0\n    h = 0\n    while h < 24:\n        t = t + changes(h)\n        h = h + 1\n    return t\n\ndef archive():\n    return [[\"/srv/orders/db\", \"the order database\", 48000], [\"/etc/orders/app.conf\", \"the application's settings\", 1], [\"/etc/orders/backup.key\", \"the key the archive is encrypted with\", 1]]\n\ndef kept():\n    return [90, 3]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n4\n\n4\n24\n4\nx\n4\n3\n\n4\n3\n0\n4\n3\n25\n4\n3\nx\n4\n23\n7\n4\n3\n12\n5\n",
      "screen": "== Backup planner ==\nBackups that succeed every night, and what a failure still loses.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> \nCancelled.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 24\nType a whole number from 0 to 23.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> x\nType a whole number from 0 to 23.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 3\nevery how many hours (1 to 24)> \nCancelled.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 3\nevery how many hours (1 to 24)> 0\nType a whole number from 1 to 24.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 3\nevery how many hours (1 to 24)> 25\nType a whole number from 1 to 24.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 3\nevery how many hours (1 to 24)> x\nType a whole number from 1 to 24.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 23\nevery how many hours (1 to 24)> 7\nBackups at 23:00, 06:00, 13:00 and 20:00.\nThis schedule: within the objective for 4 of 24 failure hours; the worst, at 20:00, loses 24,000 changes, 7 hours of work.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 3\nevery how many hours (1 to 24)> 12\nBackups at 03:00 and 15:00.\nThis schedule: within the objective for 2 of 24 failure hours; the worst, at 15:00, loses 30,400 changes, 12 hours of work.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n4\n2\n5\n4\n0\n1\n4\n9\n24\n5\n",
      "screen": "== Backup planner ==\nBackups that succeed every night, and what a failure still loses.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 1\nAn order system, hour by hour: 300 changes an hour from 00:00 to 07:00, 2,000 to 09:00, 4,200 to 18:00, 1,500 to 22:00 and 600 to midnight - 51,100 a day.\nThe recovery point objective: lose at most 1 hour of work.\nThe backups: nightly at 02:00, which has succeeded 400 nights in a row; every 6 hours; and nightly with an incremental every hour.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 2\nA failure at each hour, the changes lost since the last backup, and their age (ok: within the objective):\n  failure  nightly at 02:00   every 6 hours      nightly + hourly\n  00:00     50,500 22h          7,200  6h            600  1h ok\n  01:00     50,800 23h            300  1h ok         300  1h ok\n  02:00     51,100 24h            600  2h            300  1h ok\n  03:00        300  1h ok         900  3h            300  1h ok\n  04:00        600  2h          1,200  4h            300  1h ok\n  05:00        900  3h          1,500  5h            300  1h ok\n  06:00      1,200  4h          1,800  6h            300  1h ok\n  07:00      1,500  5h            300  1h ok         300  1h ok\n  08:00      3,500  6h          2,300  2h          2,000  1h ok\n  09:00      5,500  7h          4,300  3h          2,000  1h ok\n  10:00      9,700  8h          8,500  4h          4,200  1h ok\n  11:00     13,900  9h         12,700  5h          4,200  1h ok\n  12:00     18,100 10h         16,900  6h          4,200  1h ok\n  13:00     22,300 11h          4,200  1h ok       4,200  1h ok\n  14:00     26,500 12h          8,400  2h          4,200  1h ok\n  15:00     30,700 13h         12,600  3h          4,200  1h ok\n  16:00     34,900 14h         16,800  4h          4,200  1h ok\n  17:00     39,100 15h         21,000  5h          4,200  1h ok\n  18:00     43,300 16h         25,200  6h          4,200  1h ok\n  19:00     44,800 17h          1,500  1h ok       1,500  1h ok\n  20:00     46,300 18h          3,000  2h          1,500  1h ok\n  21:00     47,800 19h          4,500  3h          1,500  1h ok\n  22:00     49,300 20h          6,000  4h          1,500  1h ok\n  23:00     49,900 21h          6,600  5h            600  1h ok\nnightly at 02:00: within the objective for 1 of 24 failure hours; the worst, at 02:00, loses 51,100 changes, 24 hours of work.\nevery 6 hours: within the objective for 4 of 24 failure hours; the worst, at 18:00, loses 25,200 changes, 6 hours of work.\nnightly + hourly: every failure hour within the objective; the most a failure loses is 4,200 changes.\n400 nights of success say the archive was made. How old it is when it is needed is set by the schedule alone.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 3\nWhat one archive holds - a snapshot of the whole application host:\n  /srv/orders/db          the order database                       48,000 MB\n  /etc/orders/app.conf    the application's settings                    1 MB\n  /etc/orders/backup.key  the key the archive is encrypted with         1 MB\n90 archives are kept, each with 3 copies off site: the key is in 270 places, each next to the data it protects. Anyone who holds one archive can open it.\nWith /etc/orders/backup.key left out of the snapshot, a restore asks the key service for the key: no copy in any archive, and one more step in the drill.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 2\nevery how many hours (1 to 24)> 5\nBackups at 02:00, 07:00, 12:00, 17:00 and 22:00.\nThis schedule: within the objective for 5 of 24 failure hours; the worst, at 17:00, loses 21,000 changes, 5 hours of work.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 0\nevery how many hours (1 to 24)> 1\nBackups at 00:00, 01:00, 02:00, 03:00, 04:00, 05:00, 06:00, 07:00, 08:00, 09:00, 10:00, 11:00, 12:00, 13:00, 14:00, 15:00, 16:00, 17:00, 18:00, 19:00, 20:00, 21:00, 22:00 and 23:00.\nThis schedule: every failure hour within the objective; the most a failure loses is 4,200 changes.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 4\nfirst backup hour (0 to 23)> 9\nevery how many hours (1 to 24)> 24\nBackups at 09:00.\nThis schedule: within the objective for 1 of 24 failure hours; the worst, at 09:00, loses 51,100 changes, 24 hours of work.\n\n1) the system  2) a failure at each hour  3) what an archive holds  4) a schedule of your own  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "the-backup-ran-nightly-and-the-change-was-hourly",
      "caseId": "583-the-backup-ran-nightly-and-the-change-was-hourly",
      "title": "The backup ran nightly and the change was hourly"
    },
    {
      "slug": "the-backup-was-encrypted-and-the-key-was-in-the-backup",
      "caseId": "657-the-backup-was-encrypted-and-the-key-was-in-the-backup",
      "title": "The backup was encrypted and the key was in the backup"
    }
  ],
  "updated": "2026-10-19"
}
