...
In general from some point of view node is a collaboration of components mentioned above. In order to satisfy needs of:
- Node start/stop
- Dynamic component start/stop
- Dynamic run level changes both forward and backwards
it makes sense to describe component flow in more detail. Here it is:
- Component object instantiation.
- Dependencies injection either with constructor or DI framework.
- Component start either in specified run level or with default one:
- Read and apply related local configuration if any.
- Check whether a component's state lags behind the state of already started components, in other words check if component's applied revision is less than node's applied revision (by design all already started components will have same applied revision, so it's possible to denote such bunch of applied revisions as node applied revision). Applied revision here is the latest revision of configuration and meta storage updates that component have successfully processed and committed to vault. If the current component's applied revision is less then node's one than component either uses historical update logic(optimization, not an option currently) or full one to promote the state. Both options will be described in more detail in corresponding sections.
- Deploy configuration and meta storage watches in order to be notified about any new updates.
- Register message handlers.
- Register local components listeners.
- Start threads/thread pools.
- All other inner related stuff.
- Please pay attention that the order and completeness of the above operations are not strictly defined and depend on the design specifics of a particular component.
Component is ready.
- At a given state it's possible to move component's run level both further or backwards. Depending on the specified run level it might be necessary to process logic specified at step 3.
- Component stop. The core point of stopping a component is to safely terminate the component's ongoing operations as fast as possible. That include following steps:
- Unregister configuration component's configuration listeners and meta storage watches.
Prevent any network communication. In case of an attempt of a network communication - ComponentStoppingException should be thrown. Catching such exceptions should complete awaiting futures if any with corresponding reason. - Unregister local listeners that will also complete local listeners futures with ComponentStoppingException.
- Stop treads/thread pools and do all other inner component related stuff.
Historical update
Historical here reflects a certain similarity with the historical rebalance design. It is worth mentioning that the main problem of upgrading components to the state that is newer than node's state is an impossibility of doing consistant reads of beneath components. In other words, if we start TableManager from within appliedRevision 10 and SchemaManager already have appliedRevision 20, schemaManager.getScheama(tableId) will return schema for revision 20 that is not expected by TableManager that processes table updates for revision 11. In order to provide consistent reads, the requested component could analyze the callers context and either recalculate requested data based on callers applied revision or return previously cached historical data. In any case this logic seems to be non-trivial and might be implemented later as a sort of optimization.
Full update
Another solution to satisfy component update that will preserve consistent cross components reads will include:
- Component's data eviction.
- Synchronous component's state retrieval from distributedConfigurationManager. At this point, cause all intermediate updates are skipped and only up to date state is retrieved, consistent cross component reads are guaranteed.
Risks and Assumptions
// N/A
...