Interface SynchronizedStateProvider<P,​F>

  • Type Parameters:
    P - the object expected in the case of partial synchronization
    F - the object expected in the case of full synchronization.
    All Known Implementing Classes:
    AbstractSynchronizedStateProvider, DelegatingRoleAwareSynchronizedStateProvider

    public interface SynchronizedStateProvider<P,​F>
    This is a provider for a subsystem that wishes to synchronize data across redundancy. There are two possibilities: one way, where data is only synchronized from master to backup, or bi-directional, in which data is synchronized both ways.

    These are typically registered on subsystem setup, and thus handle the gateway role internally (that is, they don't get re-registered on change to role, if a gateway goes independent->master, or similar). If the role changes, however, the system will call shutdown and then init again.

    This provider packs functions for uni-directional and bi-direction together. If uni-directional, the PULL functions will never be called on the backup, and the apply functions will never be called on the master.

    One of the design goals of the sync system is that the implementation doesn't need to worry about sync logic too much, or node role. In practice, most implementations do care whether or not they're master for various reason. The AbstractSynchronizedStateProvider provides some useful helper functions, including isMaster().

    The sync methodology operates around the VersionToken. This is "version" comprising of a string id (usually a uuid) for "major", and then a long revision based on that. The token can also include arbitrary data, though, so there are a range of sync methodologies that can be employed:
    Basic Strategy:
    The basic strategy is to try to get the backup's version to match. Start with null version on the backup, and a new version on the master. Upon apply, set the current version to what was reported in the state.
    This strategy can be adapted for bi-directional, except in that case, you will also modify the revision on the backup. As a result, it is advised to set the current version to max(state.version, current.version)+1 on the backup after applying, and then immediately request sync. The master will then perform a pull with the last revision that it saw from the backup.
    Custom One-Way Strategy:
    Set the version id to something that is known on both sides, set the master revision to 1, and the backup to 0. Put an object with more information in the custom field of the version token. The backup will always try to do a partial pull, but the master is free to return an empty State if no changes have actually occurred.

    Note that sync attempts happen as a result of requestSync, so data is not sent between gateways if neither side is requesting a sync.

    Sync Cycle
    1. Upon connection, the backup will always call synchronize on the backup first, which means the backup will receive a manifest of all current provider versions. It will generate a list of sync operations to perform, based on reported current versions.
    2. The backup will execute and apply those requests.
    3. If any providers have requested sync, the backup will call synchronize on the master- in turn causing the master to generate pull requests.
    4. After this initial cycle, either side may call requestSync at any point, and synchronization attempts will occur.
    It is important to remember that everything works on a "pull" basis. Nothing is sent unsolicited from one side to the other. This is particularly important to keep in mind when watching the "Redundancy.StateMonitoring.SyncManager.*" loggers. If a gateway believes it has new data, it will call "synchronize" on the other node, which in turn generates pull requests that come back to the originating node.
    • Method Detail

      • init

        void init​(SyncController controller)
        "Starts" the provider. The controller can be used to request sync whenever the provider thinks there has been a change. It can be called freely, the parent system will coalesce calls into single operations that happen periodically.
      • shutdown

        void shutdown()
        Called when the system is being shut down or the gateway has changed role. It should be expected that startup may be called again on the same instance.
      • getId

        java.lang.String getId()
        A simple and unique identifier for this state subsystem.
      • isBidirectional

        boolean isBidirectional()
        Indicates the state can be synchronized bi-directionally.
      • fullRequiresRestart

        default boolean fullRequiresRestart()
        This is used by the system to detect "major" changes. When this is true, and a full sync is required, the system is marked as "Out of Date", and knows it will be restarted when an update arrives.
      • getVersion

        VersionToken getVersion()
        This represents the current version of the state. It is a *little* tricky, however, when it comes to bi-directional states. Bi-directional states mean that changes can happen on both sides and are merged when synchronized. How this works: 1) Utimately the goal is to have the version be the same on both sides. 2) The state synchronizer will keep track of the last version seen, both locally and remotely. 3) When a partial state is applied here, this provider determines if it still has changes. If so, it requests a sync. 4) When the partial pull comes in, it will be for an older revision- this revision is relative to this side. At this point, the provider returns the outstanding changes with an id equal to what was last received. If in the mean time the other side has modified the state again, it the cycle would continue- except in step 3 there shouldn't be any more outstanding changes. This logic is handled by the AbstractSynchronizedStateProvider.
      • pullPartial

        State<P> pullPartial​(VersionToken version)
        Should return a set of partial changes going from the provided version. If not available, should return State.outOfDate().
      • pullFull

        State<F> pullFull()
        Should return the full state.
      • applyPartialState

        void applyPartialState​(State<P> state)
        Should apply the partial state. If the process fails for some reason, the system should reset its internal version and request sync, so that the full state may be transferred.
      • applyFullState

        void applyFullState​(State<F> state)
        Applies the full state. If the update fails, the system should reset its internal version and request sync in order to retry.
      • appendMetrics

        default void appendMetrics​(java.util.List<MetricValue> metrics)