Advertisement
Wyvern67

How Helioss.co manages its backups

Apr 29th, 2020
438
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 4.66 KB | None | 0 0
  1. -- Tree view of our folders: https://i.imgur.com/lY8TLAf.png
  2.  
  3. ---------------------------------
  4. --! @file clear-backups.lua
  5. --! @brief Manages the backups for the Helioss.co backups server
  6. --! @detail
  7. -- Our backup management is simple,
  8. -- The older they are, the less we keep.
  9. -- It is logarithmic, meaning we keep a backup from 1 hour ago, then 2h,
  10. -- then 4h, 8h, 16h, 32h, 64h, ...
  11. --
  12. -- To determine these timepoints we look at the oldest backup's age in seconds
  13. -- and successively divide it by 2 until we reach under 3600 seconds (1h)
  14. ---------------------------------
  15.  
  16. local lfs = require("lfs")
  17.  
  18.  
  19. --! @brief Given a number N, and an array of numbers A, returns the closest
  20. -- number to N in A
  21. --! @param number
  22. --! @param array
  23. --! @return array[match] the closest number to N in A, its index, the delta between N and A[index]
  24. --! @return match its index
  25. --! @return minDelta the difference between N and array[match]
  26. local function closestNumber(number, array)
  27.     local match = nil
  28.     local minDelta = math.huge
  29.     for i,v in pairs(array) do
  30.         local delta = math.abs(number - v)
  31.         if delta < minDelta then
  32.             match = i
  33.             minDelta = delta
  34.         end
  35.     end
  36.  
  37.     return array[match], match, minDelta
  38. end
  39.  
  40.  
  41. --! @brief Find the biggest number in a table
  42. --! @param array Any table
  43. --! @return max The number
  44. --! @return maxI its index
  45. local function findMax(array)
  46.     local max, maxI = nil, nil
  47.     for i,v in pairs(array) do
  48.         if max == nil or v > max then
  49.             maxI, max = i, v
  50.         end
  51.     end
  52.  
  53.     return max, maxI
  54. end
  55.  
  56. --! @brief Successively divide n by 2 until minimumToReach is reached then
  57. --!     returns each iteration.
  58. --! @param n Starting point
  59. --! @param minimumToReach Number
  60. --! @param result Private, keep to nil
  61. --! @result result A table in the form {n, n/2, n/4, n/8, ...}
  62. local function succesiveDivBy2Results(n, minimumToReach, result)
  63.     if result == nil then result = {} end
  64.     table.insert(result, n)
  65.  
  66.     if (n <= minimumToReach) then
  67.         return result
  68.     end
  69.  
  70.     return succesiveDivBy2Results(math.floor(n/2), minimumToReach, result)
  71. end
  72.  
  73. --! @brief Returns a subset of `array` composed of the elements that are closest to the
  74. --!     numbers in `references`
  75. --! @param array A table of numbers
  76. --! @param references A table of numbers
  77. --! @result toKeep A subset of `array` composed of the elements that are closest to the
  78. --!     numbers in `references`
  79. --! @result toDelete The elements of `array` that didn't make it into `toKeep`
  80. --! @result fittingPoints For internal use
  81. --! @example {1,2,3,4,5}, {3,8} => {3,5}, {1,2,4} (in spirit)
  82. local function mostFittingPoints(array, references)
  83.     local bestDeltas = {}
  84.     local fittingPoints = {}
  85.  
  86.     for i,v in pairs(array) do
  87.         local match, matchI, delta = closestNumber(v, references)
  88.         if bestDeltas[match] == nil or bestDeltas[match] > delta then
  89.             bestDeltas[match] = delta
  90.             fittingPoints[matchI] = {i=i,v=v,r=match}
  91.         end
  92.     end
  93.  
  94.     local toKeep = {}
  95.     for _,v in pairs(fittingPoints) do
  96.         toKeep[v.i] = v.v
  97.     end
  98.  
  99.     local toDelete = {}
  100.     for i,v in pairs(array) do
  101.         if toKeep[i] == nil then
  102.             toDelete[i] = v
  103.         end
  104.     end
  105.  
  106.     return toKeep, toDelete, fittingPoints
  107. end
  108.  
  109.  
  110. for server in lfs.dir("backups") do
  111.     if server == "." or server == ".." then
  112.         -- nothing
  113.     else
  114.         print(server .. ": ")
  115.  
  116.         -- Gather all the backups for each server, with their age
  117.         local files = {}
  118.         local now = os.time()
  119.         for backup in lfs.dir("backups/" .. server) do
  120.             if backup == "." or backup == ".." then
  121.                 -- nothing
  122.             else
  123.                 local file = "backups/" .. server .. "/" .. backup
  124.                 local attr = {}
  125.                 local result = lfs.attributes(file)
  126.                 local lapse = math.abs(now - result.modification)
  127.  
  128.                 files[file] = lapse
  129.             end
  130.         end
  131.  
  132.         -- not important
  133.         local success, err = lfs.mkdir("archived/backups/" .. server)
  134.         if not success and err ~= "File exists" then
  135.             print(err)
  136.             return
  137.         end
  138.  
  139.         -- Separates the backups we should keep from the backups we can delete
  140.         local oldest = findMax(files)
  141.         if oldest ~= nil then
  142.             local ref = succesiveDivBy2Results(oldest, 3600)
  143.             -- {n, n/2, n/4, n/8, ..., ~3600}
  144.             local keep, delete, _debug = mostFittingPoints(files, ref)
  145.             -- {
  146.             --  fileName=ageInSeconds,
  147.             --  fileName=ageInSeconds,
  148.             --  ...
  149.             -- }
  150.  
  151.             print("", "Keep:")
  152.             for i,v in pairs(keep)   do print("", "", i, v) end
  153.  
  154.             print("", "Delete:")
  155.             for i,v in pairs(delete) do
  156.                 print("", "", i, v)
  157.                 -- Move to ~/archived/backups/{server}/{file}
  158.                 local success, err = os.rename(i, "archived/"..i)
  159.                 if not success then
  160.                     print(i, err)
  161.                 end
  162.             end
  163.             --print("[debug]")
  164.             --for i,v in pairs(_debug) do print("", v.i, v.v, v.r) end
  165.         end
  166.     end
  167. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement