mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-13 16:21:07 +01:00
4675306615
Summary: Ref T13244. See D20080. Rather than randomly jittering service calls, we can give each host a "metronome" that ticks every 60 seconds to get load to spread out after one cycle. For example, web001 ticks (and makes a service call) when the second hand points at 0:17, web002 at 0:43, web003 at 0:04, etc. For now I'm just planning to seed the metronomes randomly based on hostname, but we could conceivably give each host an assigned offset some day if we want perfectly smooth service call rates. Test Plan: Ran unit tests. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13244 Differential Revision: https://secure.phabricator.com/D20087
92 lines
2.5 KiB
PHP
92 lines
2.5 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Tick at a given frequency with a specifiable offset.
|
|
*
|
|
* One use case for this is to flatten out load spikes caused by periodic
|
|
* service calls. Give each host a metronome that ticks at the same frequency,
|
|
* but with different offsets. Then, have hosts make service calls only after
|
|
* their metronome ticks. This spreads service calls out evenly more quickly
|
|
* and more predictably than adding random jitter.
|
|
*/
|
|
final class PhabricatorMetronome
|
|
extends Phobject {
|
|
|
|
private $offset = 0;
|
|
private $frequency;
|
|
|
|
public function setOffset($offset) {
|
|
if (!is_int($offset)) {
|
|
throw new Exception(pht('Metronome offset must be an integer.'));
|
|
}
|
|
|
|
if ($offset < 0) {
|
|
throw new Exception(pht('Metronome offset must be 0 or more.'));
|
|
}
|
|
|
|
// We're not requiring that the offset be smaller than the frequency. If
|
|
// the offset is larger, we'll just clamp it to the frequency before we
|
|
// use it. This allows the offset to be configured before the frequency
|
|
// is configured, which is useful for using a hostname as an offset seed.
|
|
|
|
$this->offset = $offset;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setFrequency($frequency) {
|
|
if (!is_int($frequency)) {
|
|
throw new Exception(pht('Metronome frequency must be an integer.'));
|
|
}
|
|
|
|
if ($frequency < 1) {
|
|
throw new Exception(pht('Metronome frequency must be 1 or more.'));
|
|
}
|
|
|
|
$this->frequency = $frequency;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setOffsetFromSeed($seed) {
|
|
$offset = PhabricatorHash::digestToRange($seed, 0, PHP_INT_MAX);
|
|
return $this->setOffset($offset);
|
|
}
|
|
|
|
public function getFrequency() {
|
|
if ($this->frequency === null) {
|
|
throw new PhutilInvalidStateException('setFrequency');
|
|
}
|
|
return $this->frequency;
|
|
}
|
|
|
|
public function getOffset() {
|
|
$frequency = $this->getFrequency();
|
|
return ($this->offset % $frequency);
|
|
}
|
|
|
|
public function getNextTickAfter($epoch) {
|
|
$frequency = $this->getFrequency();
|
|
$offset = $this->getOffset();
|
|
|
|
$remainder = ($epoch % $frequency);
|
|
|
|
if ($remainder < $offset) {
|
|
return ($epoch - $remainder) + $offset;
|
|
} else {
|
|
return ($epoch - $remainder) + $frequency + $offset;
|
|
}
|
|
}
|
|
|
|
public function didTickBetween($min, $max) {
|
|
if ($max < $min) {
|
|
throw new Exception(
|
|
pht(
|
|
'Maximum tick window must not be smaller than minimum tick window.'));
|
|
}
|
|
|
|
$next = $this->getNextTickAfter($min);
|
|
return ($next <= $max);
|
|
}
|
|
|
|
}
|