blob: 6ad9f2050b504aed008f3333e19cf8135344181b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package accord.api;
import javax.annotation.Nullable;
import accord.local.Node;
import accord.primitives.Ranges;
import accord.topology.Topology;
import accord.utils.async.AsyncResult;
import accord.utils.async.AsyncResults;
/**
* ConfigurationService is responsible for:
* - coordinating linearizable configuration / topology changes across the cluster
* - notifying the local node of topology changes
* - syncing transaction metadata between replicas on epoch changes
* - updating the fast path electorate when appropriate
* - acting as a database of previous epoch configurations
*
* The high level flow of a topology change is as follows:
*
* - ConfigurationService becomes aware of a new epoch/Topology
*
* - ConfigurationService notifies the node of the new configuration service by calling
* {@link accord.api.ConfigurationService.Listener#onTopologyUpdate(accord.topology.Topology)}
*
* - Once the node has setup the new topology, it will call {@link accord.api.ConfigurationService#acknowledgeEpoch(EpochReady)}
* which indicates the node will no longer create txnIds for the previous epoch, and it's commits can now be synced
* with other replicas.
*
* - ConfigurationService is now expected to begin syncing fast path decisions made in the previous epoch to other
* replicas. In the case of a replication change (new replica, ownership movement), the configuration service
* needs to notify the new replica of the transaction history of the relevant ranges.
*
* - Once the previous epoch data has been synced to other nodes, the configuration service needs to notify the other
* nodes that this node has synced data for the previous epoch.
*
* - ConfigurationService will notify the node when other nodes complete syncing an epoch by calling
* {@link accord.api.ConfigurationService.Listener#onRemoteSyncComplete(accord.local.Node.Id, long)}
*
*/
public interface ConfigurationService
{
/**
*
*/
class EpochReady
{
public static final AsyncResult<Void> DONE = AsyncResults.success(null);
public final long epoch;
/**
* The new epoch has been setup locally and the node is ready to process commands for it.
*/
public final AsyncResult<Void> metadata;
/**
* The node has retrieved enough remote information to become a coordinator for the new epoch.
*/
public final AsyncResult<Void> coordination;
/**
* The node has successfully replicated the underlying DataStore information for the new epoch, but may need
* to perform some additional coordination before it can execute the read portion of a transaction.
*/
public final AsyncResult<Void> data;
/**
* The node has retrieved enough remote information to safely process reads, including replicating all
* necessary DataStore information, and any additional transactions necessary for consistency.
*/
public final AsyncResult<Void> reads;
public EpochReady(long epoch, AsyncResult<Void> metadata, AsyncResult<Void> coordination, AsyncResult<Void> data, AsyncResult<Void> reads)
{
this.epoch = epoch;
this.metadata = metadata;
this.coordination = coordination;
this.data = data;
this.reads = reads;
}
public static EpochReady done(long epoch)
{
return new EpochReady(epoch, DONE, DONE, DONE, DONE);
}
}
// TODO (exepected): do we need two implementations of this? Can't we drive it all through Node?
interface Listener
{
/**
* Informs listeners of new topology. This is guaranteed to be called sequentially for each epoch after
* the initial topology returned by `currentTopology` on startup.
*
* TODO (required): document what this Future represents, or maybe refactor it away - only used for testing
*/
AsyncResult<Void> onTopologyUpdate(Topology topology, boolean startSync);
/**
* Called when accord data associated with a superseded epoch has been sync'd from previous replicas.
* This should be invoked on each replica once EpochReady.coordination has returned on a replica.
*
* Once a quorum of these notifications have been received, no new TxnId may be executed in this epoch
* (though this is not a transitive property; earlier epochs may yet agree to execute TxnId if they have not been sync'd)
*/
void onRemoteSyncComplete(Node.Id node, long epoch);
/**
* Called when the configuration service is meant to truncate it's topology data up to (but not including)
* the given epoch
*/
void truncateTopologyUntil(long epoch);
/**
* Called when no new TxnId may be agreed with an epoch less than or equal to the provided one.
* This means future epochs are now aware of all TxnId with this epoch or earlier that may be executed
* on this range.
*/
void onEpochClosed(Ranges ranges, long epoch);
/**
* Called when all TxnId with an epoch equal to or before this that interact with this range have been executed,
* in whatever epoch they execute in. Once the whole range is covered this epoch is redundant, and may be cleaned up.
*/
void onEpochRedundant(Ranges ranges, long epoch);
}
void registerListener(Listener listener);
/**
* Returns the current topology. Also called on startup for the "starting point" topology.
*/
Topology currentTopology();
default long currentEpoch()
{
return currentTopology().epoch();
}
/**
* Returns the topology for the given epoch if it's available, null otherwise
*/
@Nullable Topology getTopologyForEpoch(long epoch);
/**
* Method for reporting epochs the configuration service may not be aware of. To be notified when the new epoch
* is available locally, use {@link accord.topology.TopologyManager#awaitEpoch(long)}
*/
void fetchTopologyForEpoch(long epoch);
/**
* Called after this node learns of an epoch as part of the {@code Listener#onTopologyUpdate} call.
* On invocation the system is not necessarily ready to process the epoch, and the BootstrapReady parameter
* provides indications of when the bootstrap has completed various phases of setup.
*/
void acknowledgeEpoch(EpochReady ready, boolean startSync);
void reportEpochClosed(Ranges ranges, long epoch);
void reportEpochRedundant(Ranges ranges, long epoch);
}