DoubleEntryLedger.Occ.Helper (double_entry_ledger v0.2.0)

View Source

Provides helper functions for managing Optimistic Concurrency Control (OCC) in the Double Entry Ledger system.

This module contains utilities for implementing OCC retry logic, including backoff timing, error tracking, and retry state management. It offers a consistent framework for handling concurrent modification conflicts across the application.

Key Functionality

  • Retry Management: Calculate and apply appropriate backoff delays
  • Error Tracking: Update error maps with retry information and messages
  • Configuration: Access OCC-related configuration settings like max retries
  • Timing Utilities: Calculate next retry times for command scheduling

Configuration

The module's behavior can be configured through the following application environment variables:

  • :max_retries - Maximum number of retry attempts (default: 5)
  • :retry_interval - Base interval for retries in milliseconds (default: 200)
  • :next_retry_after_interval - Time to wait before next retry attempt (default: max_retries * retry_interval)

Usage Examples

Setting a delay based on current attempt number:

# Pause execution with exponential backoff
DoubleEntryLedger.Occ.Helper.set_delay_timer(attempt_number)

Updating an error map after an OCC conflict:

updated_error_map = DoubleEntryLedger.Occ.Helper.update_error_map(
  existing_error_map,
  current_attempt_number,
  %{step_1: result_1, step_2: result_2}
)

Calculating timestamps for retry scheduling:

{now, next_retry_time} = DoubleEntryLedger.Occ.Helper.get_now_and_next_retry_after()

Implementation Notes

This module uses an exponential backoff strategy where retry intervals double with each successive attempt, helping to reduce contention over time while maintaining responsiveness for quick resolutions.

Summary

Functions

Calculates the delay duration based on the number of attempts using exponential backoff. Can be configured via the :retry_interval application environment. The delay doubles with each successive retry.

Returns the maximum number of retries allowed. Can be configured via the :max_retries application environment.

Generates an appropriate error message for OCC conflicts.

Creates a changeset to mark a command as timed out due to OCC conflicts.

Returns the retry interval in milliseconds. Can be configured via the :retry_interval application environment.

Pauses execution for a calculated delay based on the number of attempts.

Updates the given ErrorMap with a new error message, incrementing the retry count and updating the steps completed so far.

Functions

create_error_map(command)

See DoubleEntryLedger.Command.ErrorMap.create_error_map/1.

delay(attempts)

@spec delay(integer()) :: number()

Calculates the delay duration based on the number of attempts using exponential backoff. Can be configured via the :retry_interval application environment. The delay doubles with each successive retry.

Parameters

  • attempts: The current attempt number (counts down from max_retries).

Examples

iex> DoubleEntryLedger.Occ.Helper.delay(4)
20
iex> DoubleEntryLedger.Occ.Helper.delay(2)
80

max_retries()

@spec max_retries() :: integer()

Returns the maximum number of retries allowed. Can be configured via the :max_retries application environment.

Examples

iex> DoubleEntryLedger.Occ.Helper.max_retries()
5

occ_error_message(attempts)

@spec occ_error_message(integer()) :: String.t()

Generates an appropriate error message for OCC conflicts.

Creates different messages depending on the number of attempts remaining:

  • When attempts > 1, shows a retry message with delay time and attempts left
  • When attempts <= 1, shows a final timeout message

Parameters

  • attempts (integer()): The current attempt number

Returns

  • String.t(): A formatted error message

Examples

iex> DoubleEntryLedger.Occ.Helper.occ_error_message(3)
"OCC conflict detected, retrying after 40 ms... 2 attempts left"

iex> DoubleEntryLedger.Occ.Helper.occ_error_message(1)
"OCC conflict: Max number of 5 retries reached"

occ_timeout_changeset(command, error_map)

Creates a changeset to mark a command as timed out due to OCC conflicts.

This function prepares a changeset that updates a command to the :occ_timeout status, recording the error information from the error map and updating the retry count.

Parameters

  • command (Command.t()): The command to update
  • %{errors: errors, retries: retries}: An error map containing:
    • errors: List of error messages accumulated during retry attempts
    • retries: Number of retry attempts made

Returns

  • Ecto.Changeset.t(): A changeset ready to update the command with timeout information

retry_interval()

@spec retry_interval() :: integer()

Returns the retry interval in milliseconds. Can be configured via the :retry_interval application environment.

Examples

iex> DoubleEntryLedger.Occ.Helper.retry_interval()
10

set_delay_timer(attempts)

@spec set_delay_timer(integer()) :: :ok

Pauses execution for a calculated delay based on the number of attempts.

Parameters

  • attempts: The current attempt number.

Examples

iex> DoubleEntryLedger.Occ.Helper.set_delay_timer(2)
:ok

update_error_map(error_map, attempts, steps_so_far)

Updates the given ErrorMap with a new error message, incrementing the retry count and updating the steps completed so far.

Parameters

  • error_map (ErrorMap.t()): The current error map to be updated.
  • attempts (integer()): The number of attempts made so far.
  • steps_so_far (map()): A map representing the steps completed so far.

Returns

  • ErrorMap.t(): The updated error map with the new error message, incremented retry count, and updated steps.

Examples

iex> error_map = %DoubleEntryLedger.Command.ErrorMap{errors: [], retries: 0, steps_so_far: %{}}
iex> updated_error_map = DoubleEntryLedger.Occ.Helper.update_error_map(error_map, 3, %{step: "processing"})
iex> updated_error_map.retries
1
iex> updated_error_map.steps_so_far
%{step: "processing"}
iex> length(updated_error_map.errors) > 0
true