public class InstanceStartupDelayManager {
/**
- * Applies a startup delay based on the host's name to avoid race conditions during schema migration.
-
- * In environments with multiple instances (e.g., Docker Compose, Kubernetes),
- * this delay helps avoid simultaneous Liquibase executions that may result in conflicts.
-
+ * Applies a consistent hash-based startup delay based on the host's name
+ * to avoid race conditions during schema migration.
+ * This method is useful in environments with multiple instances
+ * (e.g., Docker Compose, Kubernetes), where simultaneous Liquibase executions
+ * might result in conflicts.
* Delay logic:
- * - If the last character of the hostname is a digit, delay = digit * 1000 ms.
- * - Otherwise, a hash-based fallback delay up to 3000 ms is applied.
+ * - A hash of the hostname is calculated.
+ * - The result is used to derive a delay up to 5000 milliseconds.
+ * - This provides a reasonably distributed delay across instances.
*/
public void applyHostnameBasedStartupDelay() {
try {
final String hostname = getHostName();
- final char lastCharacterOfHostName = hostname.charAt(hostname.length() - 1);
- final long startupDelayInMillis;
- if (Character.isDigit(lastCharacterOfHostName)) {
- startupDelayInMillis = Character.getNumericValue(lastCharacterOfHostName) * 1_000L;
- } else {
- startupDelayInMillis = Math.abs(hostname.hashCode() % 3_000L);
- }
+ final long startupDelayInMillis = Math.abs(hostname.hashCode() % 5_000L);
log.info("Startup delay applied for Hostname: {} | Delay: {} ms", hostname, startupDelayInMillis);
haveALittleSleepInMs(startupDelayInMillis);
} catch (final InterruptedException e) {
def objectUnderTest = Spy(InstanceStartupDelayManager)
def 'Startup delay with real hostname.'() {
- when: 'start up delay is called'
- objectUnderTest.applyHostnameBasedStartupDelay()
- then: 'the system will sleep for some time'
- 1 * objectUnderTest.haveALittleSleepInMs(_ as Long) >> { /* don't really sleep */ }
- }
-
- def 'Startup delay for hostname that ends with digit.'() {
- given: 'a hostname with a digit at the end'
- objectUnderTest.getHostName() >> 'host' + lastDigit
- and: 'the expected delay is based on last digit'
- def expectedDelay = lastDigit * 1_000
- when: 'startup delay is called'
- objectUnderTest.applyHostnameBasedStartupDelay()
- then: 'the system will sleep for expected time'
- 1 * objectUnderTest.haveALittleSleepInMs(expectedDelay)
- where: 'following last digits are used'
- lastDigit << [0, 1]
- }
-
- def 'Startup delay for hostname that does not end with digit.'() {
- given: 'a hostname with a non-digit at the end'
+ given: 'a hostname is resolved'
objectUnderTest.getHostName() >> 'hostX'
- and: 'the expected delay is based on hash code with max of 3,000 ms'
- def expectedDelay = Math.abs('hostX'.hashCode() % 3_000)
+ and: 'the expected delay is based on hash code with max of 5,000 ms'
+ def expectedDelay = Math.abs('hostX'.hashCode() % 5_000)
when: 'startup delay is called'
objectUnderTest.applyHostnameBasedStartupDelay()
then: 'the system will sleep for expected time'
- 1 * objectUnderTest.haveALittleSleepInMs(expectedDelay) >> { /* don't really sleep */ }
+ 1 * objectUnderTest.haveALittleSleepInMs(expectedDelay)
}
def 'Startup delay when hostname cannot be resolved.'() {