From 3e3719dda1beae705ba7353e97de0cce604180a2 Mon Sep 17 00:00:00 2001
From: GeorgyKirichenko <kirichenkoga@gmail.com>
Date: Wed, 3 Feb 2016 15:40:23 +0300
Subject: [PATCH] Randomize snapshot start time. Fixed #732

---
 src/box/lua/snapshot_daemon.lua   | 45 +++++++++++--------------------
 test/box/snapshot_daemon.result   | 43 +++++++++++++++++++++++++++++
 test/box/snapshot_daemon.test.lua | 23 ++++++++++++++++
 3 files changed, 81 insertions(+), 30 deletions(-)

diff --git a/src/box/lua/snapshot_daemon.lua b/src/box/lua/snapshot_daemon.lua
index 81b7bc6c4f..1db09b81d3 100644
--- a/src/box/lua/snapshot_daemon.lua
+++ b/src/box/lua/snapshot_daemon.lua
@@ -6,11 +6,14 @@ do
     local fio = require 'fio'
     local yaml = require 'yaml'
     local errno = require 'errno'
+    local digest = require 'digest'
+    local pickle = require 'pickle'
 
     local PREFIX = 'snapshot_daemon'
 
     local daemon = {
         snapshot_period = 0;
+        snapshot_period_bias = 0;
         snapshot_count = 6;
     }
 
@@ -145,7 +148,7 @@ do
 
 
     local function next_snap_interval(self)
-        
+
         -- don't do anything in hot_standby mode
         if box.info.status ~= 'running' or
             self.snapshot_period == nil or
@@ -153,34 +156,8 @@ do
             return nil
         end
 
-        local interval = self.snapshot_period / 10
-
-        local time = fiber.time()
-        local snaps = fio.glob(fio.pathjoin(box.cfg.snap_dir, '*.snap'))
-        if snaps == nil or #snaps == 0 then
-            return interval
-        end
-
-        local last_snap = snaps[ #snaps ]
-        local stat = fio.stat(last_snap)
-
-        if stat == nil then
-            return interval
-        end
-
-
-        -- there is no activity in xlogs
-        if self.snapshot_period * 2 + stat.mtime < time then
-            return interval
-        end
-
-        local time_left = self.snapshot_period + stat.mtime - time
-        if time_left > 0 then
-            return time_left
-        end
-
-        return interval
-
+        return self.snapshot_period -
+            (fiber.time() + self.snapshot_period_bias) % self.snapshot_period
     end
 
     local function daemon_fiber(self)
@@ -221,6 +198,12 @@ do
         __index = {
             set_snapshot_period = function()
                 daemon.snapshot_period = box.cfg.snapshot_period
+                if daemon.snapshot_period > 0 then
+                    local rnd = pickle.unpack('i', digest.urandom(4))
+                    daemon.snapshot_period_bias = rnd % daemon.snapshot_period
+                else
+                    daemon.snapshot_period_bias = 0
+                end
                 reload(daemon)
                 return
             end,
@@ -232,7 +215,9 @@ do
                 end
                 daemon.snapshot_count = box.cfg.snapshot_count
                 reload(daemon)
-           end
+            end,
+
+            next_snap_interval = next_snap_interval
         }
     })
 
diff --git a/test/box/snapshot_daemon.result b/test/box/snapshot_daemon.result
index 169e7c2fb0..0cf8bedb7e 100644
--- a/test/box/snapshot_daemon.result
+++ b/test/box/snapshot_daemon.result
@@ -132,3 +132,46 @@ daemon.snapshot_count = 20
 box.cfg{ snapshot_count = 0 }
 ---
 ...
+-- first start daemon
+box.cfg{ snapshot_count = 2, snapshot_period = 3600}
+---
+...
+period_bias = daemon.snapshot_period_bias
+---
+...
+next_snap_interval = daemon.next_snap_interval(daemon)
+---
+...
+test_snap_interval = 3600 - (fiber.time() + period_bias) % 3600
+---
+...
+(next_snap_interval - test_snap_interval + 3600) % 3600 < 2
+---
+- true
+...
+biases = {}
+---
+...
+max_equals = 0
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+for i = 1, 20 do
+    box.cfg{ snapshot_period = 0}
+    box.cfg{ snapshot_period = 3600}
+    biases[daemon.snapshot_period_bias] = (biases[daemon.snapshot_period_bias] or 0) + 1
+    if biases[daemon.snapshot_period_bias] > max_equals then
+        max_equals = biases[daemon.snapshot_period_bias]
+    end
+end
+
+test_run:cmd("setopt delimiter ''");
+---
+...
+max_equals < 4
+---
+- true
+...
diff --git a/test/box/snapshot_daemon.test.lua b/test/box/snapshot_daemon.test.lua
index cf98a93d9e..8acaed3877 100644
--- a/test/box/snapshot_daemon.test.lua
+++ b/test/box/snapshot_daemon.test.lua
@@ -72,3 +72,26 @@ daemon.snapshot_count = 20
 
 -- stop daemon
 box.cfg{ snapshot_count = 0 }
+
+-- first start daemon
+box.cfg{ snapshot_count = 2, snapshot_period = 3600}
+period_bias = daemon.snapshot_period_bias
+next_snap_interval = daemon.next_snap_interval(daemon)
+test_snap_interval = 3600 - (fiber.time() + period_bias) % 3600
+(next_snap_interval - test_snap_interval + 3600) % 3600 < 2
+
+biases = {}
+max_equals = 0
+test_run:cmd("setopt delimiter ';'")
+
+for i = 1, 20 do
+    box.cfg{ snapshot_period = 0}
+    box.cfg{ snapshot_period = 3600}
+    biases[daemon.snapshot_period_bias] = (biases[daemon.snapshot_period_bias] or 0) + 1
+    if biases[daemon.snapshot_period_bias] > max_equals then
+        max_equals = biases[daemon.snapshot_period_bias]
+    end
+end
+
+test_run:cmd("setopt delimiter ''");
+max_equals < 4
-- 
GitLab