Sunteți pe pagina 1din 47

IGNITE

Apache Ignite is an in-memory computing platform that delivers unprecedented speed and
unlimited scale. It enables high-performance transactions, real-time streaming, and fast analytics in
a single, comprehensive data access and processing layer.
Apache Ignite provides a unified API which supports SQL, C++, .NET, Java/Scala/Groovy, Node.js and
more access for the application layer. The unified API connects cloud-scale applications with multiple
data stores containing structured, semi-structured and unstructured data (SQL, NoSQL, Hadoop). It
offers a high-performance data environment that allows to process full ACID transactions and
generate valuable insights from real-time, interactive and batch queries.
Apache Ignite works on top of existing databases and requires no rip-and-replace or any changes to
an existing RDBMS. Users can keep their existing RDBMSs in place and deploy Apache Ignite as a
layer above it. Apache Ignite can even automatically integrate with different RDBMS systems, such
as Oracle, MySQL, Postgres, DB2, Microsoft SQL and others. This feature automatically generates the
application domain model based on the schema definition of the underlying database and then loads
the data.
Apache Ignite supports key/value stores, SQL access, MapReduce, HPC/MPP processing,
streaming/CEP processing and Hadoop acceleration, all in one well-integrated in-memory data
fabric.
Architecture
Apache Ignite is JVM-based distributed middleware software. It is based on a homogeneous cluster
topology implementation that does not require separate server and client nodes. All nodes in an
Apache Ignite cluster are equal and can play any logical role per runtime application requirement.
At the core of Apache Ignite is a Service Provider Interface (SPI) design. The SPI-based design makes
every internal component of Apache Ignite fully customizable and pluggable by the developer.

Key Features

Clustering
Ignite has advanced clustering capabilities including logical cluster groups and auto-discovery. At the
core of Apache Ignite is a Service Provider Interface (SPI) design. Ignite nodes can automatically
discover each other. This helps to scale the cluster when needed, without having to restart the
whole cluster. It detects node failure and ensure fault tolerance through intelligent failover
mechanism.
Ignite is JVM-based. Single JVM represents one or more logical Ignite nodes. Most of the time, a
single JVM runs just one Ignite node. For example, when we say that you can "run 5 nodes on this
host" - in most cases it technically means that you can start 5 JVMs on this host each running a single
Ignite node. Ignite also supports multiple Ignite nodes in a single JVM that is exactly how most of the
internal tests run for Ignite.
A collection of Nodes creates cluster. All nodes run with same control logic and there is no special
node that operates as master or coordinator. All nodes communicate via message passing.
An Ignite node can be started from command line either with default configuration or by passing a
configuration file.
For ex.
Ignite.sh
Or
Ignite.sh confugration.xml
By default ignite.sh starts Ignite node with the default configuration: config/default-config.xml.
to get started with Apache using Maven dependency management.
Ignite requires only one ignite-core mandatory dependency. Usually you will also need to
add ignite-spring for spring-based XML configuration and ignite-indexing for SQL querying.
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-core</artifactId>
<version>${ignite.version}</version>
</dependency>
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-spring</artifactId>
<version>${ignite.version}</version>
</dependency>
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-indexing</artifactId>
<version>${ignite.version}</version>
</dependency>

The Ignition class starts individual Ignite nodes in the network topology. A physical server (like a
computer on the network) can have multiple Ignite nodes running on it.
try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
}

To perform certain actions before or after the Ignite node starts or stops. This can be done by
implementing the LifecycleBean interface, and specifying the implementation bean
in lifecycleBeans property of IgniteConfiguration in the spring XML file:
<bean class="org.apache.ignite.IgniteConfiguration">
...
<property name="lifecycleBeans">
<list>
<bean class="com.mycompany.MyLifecycleBean"/>
</list>
</property>
...
</bean>

// Create new configuration.


IgniteConfiguration cfg = new IgniteConfiguration();

// Provide lifecycle bean to configuration.


cfg.setLifecycleBeans(new MyLifecycleBean());

// Start Ignite node with given configuration.


Ignite ignite = Ignition.start(cfg)

An implementation of LifecycleBean may look like the following:


public class MyLifecycleBean implements LifecycleBean {
@Override public void onLifecycleEvent(LifecycleEventType evt) {
if (evt == LifecycleEventType.BEFORE_NODE_START) {
// Do something.
...
}
}
}

The following lifecycle event types are supported:


BEFORE_NODE_START
Invoked before Ignite node startup routine is initiated.
AFTER_NODE_START
Invoked right after Ignite node has started.
BEFORE_NODE_STOP
Invoked right before Ignite stop routine is initiated.
AFTER_NODE_STOP
Invoked right after Ignite node has stopped.

Ignite has an optional notion of client and server nodes. Server nodes participate in caching,
compute execution, stream processing, etc., while the native client nodes provide ability to connect
to the servers remotely. Ignite native clients allow using the whole set of Ignite APIs, including
near caching, transactions, compute, streaming, services, etc. from the client side.
By default, all Ignite nodes are started as server nodes, and client mode needs to be explicitly
enabled. configure a node to be either a client or a server via
IgniteConfiguration.setClientMode(...) property.
IgniteConfiguration cfg = new IgniteConfiguration();

// Enable client mode.


cfg.setClientMode(true);

// Start Ignite in client mode.


Ignite ignite = Ignition.start(cfg);

Alternatively, for convenience, We can also enable or disable the client mode on the Ignition class
to allow clients and servers reuse the same configuration.
Ignition.setClientMode(true);

// Start Ignite in client mode.


Ignite ignite = Ignition.start();

In Ignite, nodes can discover each other by using DiscoverySpi. Ignite


provides TcpDiscoverySpi as a default implementation of DiscoverySpi that uses TCP/IP for node
discovery. Discovery SPI can be configured for Multicast and Static IP based node discovery.
TcpDiscoveryMulticastIpFinder uses Multicast to discover other nodes in the grid and is the
default IP finder. We should not have to specify it unless we plan to override default settings. Here is
an example of how to configure this finder via Spring XML file or programmatically from Java:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
...
<property name="discoverySpi">
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
<property name="ipFinder">
<bean
class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFi
nder">
<property name="multicastGroup" value="228.10.10.157"/>
</bean>
</property>
</bean>
</property>
</bean>

TcpDiscoverySpi spi = new TcpDiscoverySpi();

TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder();

ipFinder.setMulticastGroup("228.10.10.157");

spi.setIpFinder(ipFinder);

IgniteConfiguration cfg = new IgniteConfiguration();

// Override default discovery SPI.


cfg.setDiscoverySpi(spi);

// Start Ignite node.


Ignition.start(cfg);
For cases when Multicast is disabled, TcpDiscoveryVmIpFinder should be used with pre-
configured list of IP addresses.
You are only required to provide at least one IP address of a remote node, but usually it is advisable
to provide 2 or 3 addresses of grid nodes that you plan to start at some point of time in the future.
Once a connection to any of the provided IP addresses is established, Ignite will automatically
discover all other grid nodes.
By default the TcpDiscoveryVmIpFinder is used in non-shared mode. If you plan to start a
server node then in this mode the list of IP addresses should contain an address of the local node as
well. It will let the node not to wait while other nodes join the cluster but rather become the first
cluster node and operate normally.
Here is an example of how to configure this finder via Spring XML file or programmatically from Java:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
...
<property name="discoverySpi">
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
<property name="ipFinder">
<bean
class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
<property name="addresses">
<list>
<!--
Explicitly specifying
address of a local node to let it start
and operate normally even if
there is no more nodes in the cluster.
You can also optionally
specify an individual port or port range.
-->
<value>1.2.3.4</value>

<!--
IP Address and optional port range of a remote node.
You can also optionally specify an individual port and don't set
the port range at all.
-->
<value>1.2.3.5:47500..47509</value>
</list>
</property>
</bean>
</property>
</bean>
</property>
</bean>

TcpDiscoverySpi spi = new TcpDiscoverySpi();

TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();

// Set initial IP addresses.


// Note that you can optionally specify a port or a port range.
ipFinder.setAddresses(Arrays.asList("1.2.3.4", "1.2.3.5:47500..47509"));
spi.setIpFinder(ipFinder);

IgniteConfiguration cfg = new IgniteConfiguration();

// Override default discovery SPI.


cfg.setDiscoverySpi(spi);

// Start Ignite node.


Ignition.start(cfg);

You can use both, Multicast and Static IP based discovery together. In this case, in addition to
addresses received via multicast, if any, TcpDiscoveryMulticastIpFinder can also work with pre-
configured list of static IP addresses, just like Static IP-Based Discovery described above. Here is an
example of how to configure Multicast IP finder with static IP addresses:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
...
<property name="discoverySpi">
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
<property name="ipFinder">
<bean
class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFi
nder">
<property name="multicastGroup" value="228.10.10.157"/>

<!-- list of static IP addresses-->


<property name="addresses">
<list>
<value>1.2.3.4</value>

<!--
IP Address and optional port range.
You can also optionally specify an individual port.
-->
<value>1.2.3.5:47500..47509</value>
</list>
</property>
</bean>
</property>
</bean>
</property>
</bean>

TcpDiscoverySpi spi = new TcpDiscoverySpi();

TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder();

// Set Multicast group.


ipFinder.setMulticastGroup("228.10.10.157");

// Set initial IP addresses.


// Note that you can optionally specify a port or a port range.
ipFinder.setAddresses(Arrays.asList("1.2.3.4", "1.2.3.5:47500..47509"));
spi.setIpFinder(ipFinder);

IgniteConfiguration cfg = new IgniteConfiguration();

// Override default discovery SPI.


cfg.setDiscoverySpi(spi);

// Start Ignite node.


Ignition.start(cfg);

There can be a case when you need to start two isolated Ignite clusters on the same set of machines
due to testing purposes or by some other reason.
In Ignite this task is achievable if nodes from different clusters will use non intersecting local port
ranges for TcpDiscoverySpi and TcpCommunicationSpi.
Let's say that you need to start two isolated clusters on a single machine for testing purposes.
Then for the nodes from the first cluster, you should use the
following TcpDiscoverySpi and TcpCommunicationSpi configurations:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
...
<!--
Explicitly configure TCP discovery SPI to provide list of
initial nodes from the first cluster.
-->
<property name="discoverySpi">
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
<!-- Initial local port to listen to. -->
<property name="localPort" value="48500"/>

<!-- Changing local port range. This is an optional action. -->


<property name="localPortRange" value="20"/>

<!-- Setting up IP finder for this cluster -->


<property name="ipFinder">
<bean
class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
<property name="addresses">
<list>
<!--
Addresses and port range of the nodes from the first

cluster.
127.0.0.1 can be replaced with actual IP addresses or

host names. Port range is optional.


-->
<value>127.0.0.1:48500..48520</value>
</list>
</property>
</bean>
</property>
</bean>
</property>

<!--
Explicitly configure TCP communication SPI changing local
port number for the nodes from the first cluster.
-->
<property name="communicationSpi">
<bean class="org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi">
<property name="localPort" value="48100"/>
</bean>
</property>
</bean>

IgniteConfiguration cfg=new IgniteConfiguration();

// Explicitly configure TCP discovery SPI to provide list of initial nodes


// from the first cluster.
TcpDiscoverySpi discoverySpi=new TcpDiscoverySpi();

// Initial local port to listen to.


discoverySpi.setLocalPort(48500);

// Changing local port range. This is an optional action.


discoverySpi.setLocalPortRange(20);

TcpDiscoveryVmIpFinder ipFinder=new TcpDiscoveryVmIpFinder();

// Addresses and port range of the nodes from the first cluster.
// 127.0.0.1 can be replaced with actual IP addresses or host names.
// The port range is optional.
ipFinder.setAddresses(Arrays.asList("127.0.0.1:48500..48520"));

// Overriding IP finder.
discoverySpi.setIpFinder(ipFinder);

// Explicitly configure TCP communication SPI by changing local port number for
// the nodes from the first cluster.
TcpCommunicationSpi commSpi=new TcpCommunicationSpi();

commSpi.setLocalPort(48100);

// Overriding discovery SPI.


cfg.setDiscoverySpi(discoverySpi);

// Overriding communication SPI.


cfg.setCommunicationSpi(commSpi);

// Starting a node.
Ignition.start(cfg);

While for the nodes from the second cluster, the configuration can look like this:
<bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
<!--
Explicitly configure TCP discovery SPI to provide list of initial
nodes from the second cluster.
-->
<property name="discoverySpi">
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
<!-- Initial local port to listen to. -->
<property name="localPort" value="49500"/>

<!-- Changing local port range. This is an optional action. -->


<property name="localPortRange" value="20"/>

<!-- Setting up IP finder for this cluster -->


<property name="ipFinder">
<bean
class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
<property name="addresses">
<list>
<!--
Addresses and port range of the nodes from the second

cluster.
127.0.0.1 can be replaced with actual IP addresses or

host names. Port range is optional.


-->
<value>127.0.0.1:49500..49520</value>
</list>
</property>
</bean>
</property>
</bean>
</property>

<!--
Explicitly configure TCP communication SPI changing local port number
for the nodes from the second cluster.
-->
<property name="communicationSpi">
<bean class="org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi">
<property name="localPort" value="49100"/>
</bean>
</property>
</bean>

IgniteConfiguration cfg=new IgniteConfiguration();

// Explicitly configure TCP discovery SPI to provide list of initial nodes


// from the second cluster.
TcpDiscoverySpi discoverySpi=new TcpDiscoverySpi();

// Initial local port to listen to.


discoverySpi.setLocalPort(49500);

// Changing local port range. This is an optional action.


discoverySpi.setLocalPortRange(20);

TcpDiscoveryVmIpFinder ipFinder=new TcpDiscoveryVmIpFinder();

// Addresses and port range of the nodes from the second cluster.
// 127.0.0.1 can be replaced with actual IP addresses or host names.
// The port range is optional.
ipFinder.setAddresses(Arrays.asList("127.0.0.1:49500..49520"));

// Overriding IP finder.
discoverySpi.setIpFinder(ipFinder);

// Explicitly configure TCP communication SPI by changing local port number for
// the nodes from the second cluster.
TcpCommunicationSpi commSpi=new TcpCommunicationSpi();
commSpi.setLocalPort(49100);
// Overriding discovery SPI.
cfg.setDiscoverySpi(discoverySpi);
// Overriding communication SPI.
cfg.setCommunicationSpi(commSpi);
// Starting a node.
Ignition.start(cfg);

As you see from the configurations the difference between them is minor - only port numbers for
SPIs and IP finder vary.
If you want the nodes from different clusters are able to look for each other using multicast protocol
then replace TcpDiscoveryVmIpFinder with TcpDiscoveryMulticastIpFinder and set
unique TcpDiscoveryMulticastIpFinder.multicastGroups in each configuration above.

Failure detection timeout is used to determine how long a cluster node should wait before
considering a remote connection, with another node, failed.
Every node in the Ignite cluster is connected with another node, at the level of discovery SPI. NodeA
sends heartbeats (and other system messages transferred over the cluster ring - discovery SPI) to
nodeB, and if the latter doesn’t reply in failureDetectionTimeout, then nodeB will be kicked off
the cluster.
This timeout is the easiest way to tune discovery SPI's failure detection feature depending on the
network and hardware conditions of your environment.
The timeout automatically controls configuration parameters of TcpDiscoverySpi, such as socket
timeout, message acknowledgment timeout and others. If any of these parameters is set explicitly,
then the failure timeout setting will be ignored.
The failure detection timeout is configured
using IgniteConfiguration.setFailureDetectionTimeout(long) for Apache Ignite server nodes
and IgniteConfiguration.setClientFailureDetectionTimeout(long) for client nodes. The
default value, that is equal to 10 seconds for the server nodes and 30 seconds for the client nodes.
This allows the discovery SPI to work reliably on most of the on-premise and containerized
deployments. However, for stable low-latency networks, the parameter can be set to ~200
milliseconds in order to detect and react to failures more quickly.
Refer to TcpDiscoverySpi javadoc to see the full list of configuration options.
In many deployments, client nodes are launched outside of the main cluster on slower machines
with worse network. In these scenarios, it is possible that servers will generate load (such as
continuous queries notification, for example) that clients will not be able to handle resulting in
growing queue of outbound messages on servers. This may eventually cause either an out-of-
memory situation on the server or block the whole cluster if the back-pressure control is enabled.
To manage these situations, you can configure the maximum number of allowed outgoing messages
for client nodes. If the size of the outbound queue exceeds this value, the client node will be
disconnected from the cluster, preventing global slowdown.
IgniteConfiguration cfg = new IgniteConfiguration();

// Configure Ignite here.

TcpCommunicationSpi commSpi = new TcpCommunicationSpi();


commSpi.setSlowClientQueueLimit(1000);

cfg.setCommunicationSpi(commSpi)

<bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">


<!-- Configure Ignite here. -->

<property name="communicationSpi">
<bean class="org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi">
<property name="slowClientQueueLimit" value="1000"/>
</bean>
</property>
</bean>

Client nodes can get disconnected from the cluster in several cases:
When a client node cannot re-establish the connection with the server node due to network issues.
Connection with the server node was broken for some time; the client node is able to re-establish
the connection with the server, but the server already dropped the client node since the server did
not receive client heartbeats
Slow clients can be kicked out by server nodes.
When a client determines that it is disconnected from the cluster, it assigns a new node 'id' to itself
and tries to reconnect to the cluster. Note that this has a side effect - the 'id' property of the
local ClusterNode will change in case of a client reconnection. This means that any application logic
that relied on the 'id' value may be affected.
While a client is in a disconnected state and an attempt to reconnect is in progress, the Ignite API
throws a special exception - IgniteClientDisconnectedException. This exception provides
a future which will be completed when the client reconnects with the cluster (IgniteCache API
throws CacheException which has IgniteClientDisconnectedExceptionas its cause). This future can
also be obtained using the IgniteCluster.clientReconnectFuture() method.
Also, there are special events for client reconnection (these events are local, i.e. they are fired only
on the client node):
EventType.EVT_CLIENT_NODE_DISCONNECTED
EventType.EVT_CLIENT_NODE_RECONNECTED

IgniteCache cache = ignite.getOrCreateCache(new CacheConfiguration<>());

while (true) {
try {
cache.put(key, val);
}
catch (CacheException e) {
if (e.getCause() instanceof IgniteClientDisconnectedException) {
IgniteClientDisconnectedException cause =
(IgniteClientDisconnectedException)e.getCause();

cause.reconnectFuture().get(); // Wait for reconnection.

// Can proceed and use the same IgniteCache instance.


}
}
}

Automatic client reconnection can be disabled using the 'clientReconnectDisabled' property


on TcpDiscoverySpi. When reconnection is disabled, client node is stopped.
IgniteConfiguration cfg = new IgniteConfiguration();

// Configure Ignite here.

TcpDiscoverySpi discoverySpi = new TcpDiscoverySpi();

discoverySpi.setClientReconnectDisabled(true);

cfg.setDiscoverySpi(discoverySpi);

Client nodes require live server nodes in the topology to start.


However, to start a client node without a running server node, you can force server mode discovery
on client nodes the following way:
IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setClientMode(true);

// Configure Ignite here.

TcpDiscoverySpi discoverySpi = new TcpDiscoverySpi();

discoverySpi.setForceServerMode(true);

cfg.setDiscoverySpi(discoverySpi);

Cluster functionality is provided via the IgniteCluster interface. Get an instance


of IgniteCluster from Ignite as follows:
IgniteCluster cluster = ignite.cluster();

Through the IgniteCluster interface you can:


Start and stop remote cluster nodes
Get a list of all cluster members
Create logical Cluster Groups
The ClusterNode interface has a very concise API and deals only with the node as a logical network
endpoint in the topology: its globally unique ID, the node metrics, its static attributes set by the user
and a few other parameters.
Ignite automatically collects metrics for all the nodes in the cluster. Metrics are collected in the
background and are updated with every heartbeat message exchanged between the cluster nodes.
Node metrics are available via the ClusterMetrics interface which contains over 50 various
metrics. The same metrics are available for Cluster Groups as well).
Here is an example of getting some metrics, including average CPU load and used heap, for the local
node:
// Local Ignite node.
ClusterNode localNode = cluster.localNode();

// Node metrics.
ClusterMetrics metrics = localNode.metrics();

// Get some metric values.


double cpuLoad = metrics.getCurrentCpuLoad();
long usedHeap = metrics.getHeapMemoryUsed();
int numberOfCores = metrics.getTotalCpus();
int activeJobs = metrics.getCurrentActiveJobs();

The local grid node is an instance of the ClusterNode representing this Ignite node.

In Ignite all nodes are equal by design, so you don't have to start any node in a specific order, or
assign any specific roles to them. However, Ignite allows users to logically group cluster nodes for
any application-specific purpose. For example, you may wish to deploy a service only on remote
nodes or assign a role of "worker" to some worker nodes for job execution.
IgniteCluster interface is also a cluster group which includes all the nodes in the cluster.
You can limit job execution, service deployment, messaging, events, and other tasks to run only
within some cluster group. You can create cluster groups based on any predicate. For convenience,
Ignite comes with some predefined cluster groups.
IgniteCluster cluster = ignite.cluster();

// Cluster group with remote nodes, i.e. other than this node.
ClusterGroup remoteGroup = cluster.forRemotes();

IgniteCluster cluster = ignite.cluster();

// All nodes on which cache with name "myCache" is deployed,


// either in client or server mode.
ClusterGroup cacheGroup = cluster.forCache("myCache");

// All data nodes responsible for caching data for "myCache".


ClusterGroup dataGroup = cluster.forDataNodes("myCache");

// All client nodes that access "myCache".


ClusterGroup clientGroup = cluster.forClientNodes("myCache");

IgniteCluster cluster = ignite.cluster();

// Cluster group containing one random node.


ClusterGroup randomGroup = cluster.forRandom();
// First (and only) node in the random group.
ClusterNode randomNode = randomGroup.node();

IgniteCluster cluster = ignite.cluster();

// Pick random node.


ClusterGroup randomNode = cluster.forRandom();

// All nodes on the same physical host as the random node.


ClusterGroup cacheNodes = cluster.forHost(randomNode);

IgniteCluster cluster = ignite.cluster();

// Dynamic cluster group representing the oldest cluster node.


// Will automatically shift to the next oldest, if the oldest
// node crashes.
ClusterGroup oldestNode = cluster.forOldest();

IgniteCluster cluster = ignite.cluster();

// Cluster group with only this (local) node in it.


ClusterGroup localGroup = cluster.forLocal();

// Local node.
ClusterNode localNode = localGroup.node();

IgniteCluster cluster = ignite.cluster();

// All client nodes.


ClusterGroup clientGroup = cluster.forClients();

// All server nodes.


ClusterGroup serverGroup = cluster.forServers();

The unique characteristic of Ignite is that all grid nodes are equal. There are no master or server
nodes, and there are no worker or client nodes either. All nodes are equal from Ignite’s point of view
- however, users can configure nodes to be masters and workers, or clients and data nodes.
All cluster nodes, on startup, automatically register all the environment and system properties as
node attributes. However, users can choose to assign their own node attributes through Ignite
configuration:
<bean class="org.apache.ignite.IgniteConfiguration">
...
<property name="userAttributes">
<map>
<entry key="ROLE" value="worker"/>
</map>
</property>
...
</bean>

IgniteConfiguration cfg = new IgniteConfiguration();

Map<String, String> attrs = Collections.singletonMap("ROLE", "worker");

cfg.setUserAttributes(attrs);

// Start Ignite node.


Ignite ignite = Ignition.start(cfg);

to get the nodes where "worker" attribute has been set.


IgniteCluster cluster = ignite.cluster();

ClusterGroup workerGroup = cluster.forAttribute("ROLE", "worker");

Collection<GridNode> workerNodes = workerGroup.nodes();

You can define dynamic cluster groups based on some predicate. Such cluster groups will always
only include the nodes that pass the predicate.
Here is an example of a cluster group over nodes that have less than 50% CPU utilization. Note that
the nodes in this group will change over time based on their CPU load.
IgniteCluster cluster = ignite.cluster();

// Nodes with less than 50% CPU load.


ClusterGroup readyNodes = cluster.forPredicate(
new IgnitePredicate<ClusterNode>() {
@Override public boolean apply(ClusterNode node) {
return node.metrics().getCurrentCpuLoad() < 0.5;
}
}
));

You can combine cluster groups by nesting them within each other. For example
// Group containing oldest node out of remote nodes.
ClusterGroup oldestGroup = cluster.forRemotes().forOldest();

ClusterNode oldestNode = oldestGroup.node();

You can get to various cluster group nodes as follows:


ClusterGroup remoteGroup = cluster.forRemotes();

// All cluster nodes in the group.


Collection<ClusterNode> grpNodes = remoteGroup.nodes();

// First node in the group (useful for groups with one node).
ClusterNode node = remoteGroup.node();

// And if you know a node ID, get node by ID.


UUID myID = ...;

node = remoteGroup.node(myId);

Ignite automatically collects metrics about all the cluster nodes. The cool thing about cluster groups
is that it automatically aggregates the metrics across all the nodes in the group and provides proper
averages, mins, and maxes within the group.
Group metrics are available via ClusterMetrics interface which contains over 50 various metrics.
Here is an example of getting some metrics, including average CPU load and used heap, across all
remote nodes
// Cluster group with remote nodes, i.e. other than this node.
ClusterGroup remoteGroup = ignite.cluster().forRemotes();

// Cluster group metrics.


ClusterMetrics metrics = remoteGroup.metrics();

// Get some metric values.


double cpuLoad = metrics.getCurrentCpuLoad();
long usedHeap = metrics.getHeapMemoryUsed();
int numberOfCores = metrics.getTotalCpus();
int activeJobs = metrics.getCurrentActiveJobs();

When working in distributed environments, sometimes you need to have a guarantee that you
always will pick the same node, regardless of the cluster topology changes. Such nodes are usually
called leaders.
In many systems, electing cluster leaders usually have to do with data consistency and is handled via
collecting votes from cluster members. Since in Ignite the data consistency is handled by data grid
affinity function (e.g. Rendezvous Hashing), picking leaders in the traditional sense for data
consistency outside of the data grid is not needed.
However, you may still wish to have a coordinator node for certain tasks. For this purpose, Ignite
lets you automatically always pick either the oldest or the youngest node in the cluster
The oldest node has a property that it remains constant whenever new nodes are added. The only
time when the oldest node in the cluster changes is when it leaves the cluster or crashes.
IgniteCluster cluster = ignite.cluster();

// Dynamic cluster group representing the oldest cluster node.


// Will automatically shift to the next oldest, if the oldest
// node crashes.
ClusterGroup oldestNode = cluster.forOldest();

The youngest node, unlike the oldest node, constantly changes every time a new node joins a
cluster. However, sometimes it may still become handy, especially if you need to execute some task
only on the newly joined node.
gniteCluster cluster = ignite.cluster();

// Dynamic cluster group representing the youngest cluster node.


// Will automatically shift to the next youngest, if the youngest
// node crashes.
ClusterGroup youngestNode = cluster.forYoungest();
Data Grid

Ignite in-memory data grid has been built from the ground up with a notion of horizontal scale and
ability to add nodes on demand in real-time; it has been designed to linearly scale to hundreds of
nodes with strong semantics for data locality and affinity data routing to reduce redundant data
noise. As long the cluster is alive, Ignite will guarantee that the data between different node will
always remain consistence regardless of crashes or topology changes.
Apache Ignite data grid is an implementation of JCache (JSR 107) specification.
 JCache provides a very simple to use, but very powerful API for data access.
 With JCache support you get the following:
 Basic Cache Operations
 ConcurrentMap APIs
 Collocated Processing (EntryProcessor)
 Events and Metrics
 Pluggable Persistence
In addition to JCache, Ignite provides ACID transactions, data querying capabilities (including SQL),
various memory models, queries, transactions, etc...
Ignite data grid is an in-memory distributed key-value store that can be viewed as a distributed
partitioned hash map with every cluster node owning a portion of the overall data. A cache is logical
location where data is stored in data grid. The more cluster nodes we add, the more data we can
cache. Ignite determines data locality using a pluggable hashing algorithm. Every client can
determine which node a key belongs to by plugging it into a hashing function, without a need for any
special mapping servers or name nodes.
Ignite data grid supports local, replicated, and partitioned data sets and allows to freely cross query
between these data sets using standard SQL syntax. Ignite supports standard SQL for querying in-
memory data including support for distributed SQL joins.

Ignite provides three different modes of cache operation: REPLICATED, PARTITIONED


and LOCAL. A cache mode is configured for each cache. Cache modes are defined in CacheMode
enumeration.
LOCAL mode is the light weight mode of cache operation, as no data is distributed to other cache
nodes. It is ideal for scenarios where data is either read-only, or can be periodically refreshed at
some expiration frequency. It also works very well with read-through behaviour where data is
loaded from persistent storage on misses. Other than distribution, local caches still have all the
features of a distributed cache, such as automatic data eviction, expiration, disk swapping, data
querying, and transactions.

In REPLICATED mode, all the data is replicated to every node in the cluster. This cache mode
provides the utmost availability of data as it is available on every node. However, in this mode every
data updates must be propagated to all other nodes which can have an impact on performance and
scalability. In Ignite, replicated caches are implemented using partitioned caches where every
key has a primary copy and is also backed up on all other nodes in the cluster ( no of backup = no of
nodes ). For example, in the diagram below, the node running in JVM1 is a primary node for key A,
but it also stores backup copies for all other keys as well (B, C, D).

As the same data is stored on all cluster nodes, the size of a replicated cache is limited by the
amount of memory available on the node with the smallest amount of RAM. This mode is ideal for
scenarios where cache reads are a lot more frequent than cache writes, and data sets are small. If
your system does cache lookups over 80% of the time, then you should consider
using REPLICATED cache mode.
PARTITIONED mode is the most scalable distributed cache mode. In this mode the overall data
set is divided equally into partitions and all partitions are split equally between participating nodes,
essentially creating one huge distributed in-memory store for caching data. This approach allows you
to store as much data as can be fit in the total memory available across all nodes, hence allowing for
multi-terabytes of data in cache memory across all cluster nodes. Essentially, the more nodes you
have, the more data you can cache.
Unlike REPLICATED mode, where updates are expensive because every node in the cluster
needs to be updated, with PARTITIONED mode, updates become cheap because only one
primary node (and optionally 1 or more backup nodes) need to be updated for every key. However,
reads become somewhat more expensive because only certain nodes have the data cached.
To avoid extra data movement, it is important to always access the data exactly on the node that has
that data cached. This approach is called affinity colocation and is strongly recommended when
working with partitioned caches. Partitioned cache are ideal when working with large data set and
updates are frequent.

Once a distributed cache is created, either in XML or via programmatically, Ignite will automatically
deploy the distributed cache on all the existing and future server nodes. Caches are distributed
concurrent hash map and by default they divided in 1024 partition. Every node which participate in
cache will host several partitions. Partitions are move from on node to another node.

All cache can be defined in Ignite Spring XML configuration on any cluster node and automatically
created and deployed on all server node. No need to specify the same configuration on each node.

<bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">


<property name="peerClassLoadingEnabled" value="true"/>
<property name="cacheConfiguration">
<list>
<bean class="org.apache.ignite.configuration.CacheConfiguration">
<property name="name" value="CUSTOMER_CACHE"/>
<property name="cacheMode" value="PARTITIONED"/>
<property name="backups" value="1"/> // Only relevant for partitioned cache
<property name="memoryMode" value="OFFHEAP_TIERED"/>
<property name="writeSynchronizationMode" value="FULL_SYNC"/>
<property name="atomicityMode" value="TRANSACTIONAL"/>
<property name="sqlEscapeAll" value="true"/>
<property name="indexedTypes">
<list>
<value>org.apache.ignite.cache.affinity.AffinityKey</value>
<value>com.c123.gridgain.orderit.model.entities.Customer</value>
</list>
</property>
</bean>
</list>
</property>

We can create an instance of the cache programmatically, in which case Ignite will create and deploy
the cache across all server cluster members that match cache node filter. After a dynamic cache has
been started, it will also be automatically deployed on all the newly joined server cluster members
that match the cache node filter.

// Enable client mode locally.


Ignition.setClientMode(true);

// Start Ignite in client mode.


Ignite ignite = Ignition.start();

CacheConfiguration cfg = new CacheConfiguration("myCache");

// Set required cache configuration properties.


...

// Create cache on all the existing and future server nodes.


// Note that since the local node is a client, it will not
// be caching any data.
// Ignite.createCache(...)
IgniteCache<?, ?> cache = ignite.getOrCreateCache(cfg);

For each cache we should define following things:-

name - a string, usually derived from the name of the datatype the cache stores (e.g.,
“ORDER_CACHE” stores objects of class Order) that uniquely identifies the cache.

cacheMode - either PARTITIONED or REPLECATED or LOCAL. It shows cache distribution over


cluster grid.

backups // Only relevant for partitioned cache – no of backup required.


In PARTITIONED mode, nodes to which the keys are assigned to are called primary nodes for
those keys. You can also optionally configure any number of backup nodes for cached data. If
the number of backups is greater than 0, then Ignite will automatically assign backup nodes for
each individual key. For example if the number of backups is 1, then every key cached in the
data grid will have 2 copies, 1 primary and 1 backup. By default, backups are turned off for better
performance. Backups can be configured by setting backup property of CacheConfiguration

CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setName("cacheName");

cacheCfg.setCacheMode(CacheMode.PARTITIONED);

cacheCfg.setBackups(1);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Start Ignite node.


Ignition.start(cfg);

writeSynchronizationMode –

CacheWriteSynchronizationMode enum can be used to configure synchronous or asynchronous


update of primary and backup copies. Write synchronization mode tells Ignite whether the client
node should wait for responses from remote nodes, before completing a write or commit. Write
synchronization mode can be set in one of following 3 modes:

FULL_SYNC

Client node will wait for write or commit to complete on all participating remote nodes (primary
and backup).

FULL_ASYNC

In this mode, client node does not wait for responses from participating nodes, in which case
remote nodes may get their state updated slightly after any of the cache write methods complete
or after Transaction.commit() method completes.

PRIMARY_SYNC

This is the default mode. Client node will wait for write or commit to complete on primary node,
but will not wait for backups to be updated.

Write synchronization mode may be configured by setting writeSynchronizationModeproperty


of CacheConfiguration
CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setName("cacheName");

cacheCfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Start Ignite node.


Ignition.start(cfg);

atomicityMode –

Ignite supports 2 atomicity modes for cache operations, transactional and atomic.
In transactional mode we are able to group multiple cache operations in a transaction,
while atomic mode supports multiple atomic operations, one at a time.
These atomicity modes are defined in atomicityMode enum.

TRANSACTIONAL mode enables fully ACID-compliant transactions, however, when only atomic
semantics are needed, it is recommended that ATOMIC mode be used for better performance.
ATOMIC mode provides better performance by avoiding transactional locks, while still providing
data atomicity and consistency. Another difference in ATOMIC mode is that bulk writes, such
as putAll(...)and removeAll(...) methods are no longer executed in one transaction and
can partially fail. In case of partial failure, CachePartialUpdateException will be thrown which
will contain a list of keys for which the update failed.
Atomicity mode can be configured via atomicityMode property of CacheConfiguration.

CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setName("cacheName");

cacheCfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Optional transaction configuration. Configure TM lookup here.


TransactionConfiguration txCfg = new TransactionConfiguration();

cfg.setTransactionConfiguration(txCfg);

// Start Ignite node.


Ignition.start(cfg);

Transactions are disabled whenever ATOMIC mode is used, which allows to achieve much higher
performance and throughput in cases when transactions are not needed.
When using partitioned cache in CacheAtomicityMode.ATOMIC mode, we can configure atomic
cache write order mode. Atomic write order mode determines which node will assign write
version (sender or primary node) and is defined by CacheAtomicWriteOrderMode enumeration.
There are 2 modes, CLOCK and PRIMARY.
In CLOCK write order mode, write versions are assigned on a sender node. CLOCK mode is
automatically turned on only when CacheWriteSynchronizationMode.FULL_SYNC is used, as it
generally leads to better performance since write requests to primary and backups nodes are
sent at the same time.

In PRIMARY write order mode, cache version is assigned only on primary node. In this mode the
sender will only send write requests to primary nodes, which in turn will assign write version and
forward them to backups.

Atomic write order mode can be configured via atomicWriteOrderMode property


of CacheConfiguration.

CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setName("cacheName");

cacheCfg.setAtomicWriteOrderMode(CacheAtomicWriteOrderMode.CLOCK);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Start Ignite node.


Ignition.start(cfg);

A partitioned cache can also be fronted by a Near cache, which is a smaller local cache that
stores most recently or most frequently accessed data. Just like with a partitioned cache, the
user can control the size of the near cache and its eviction policies.
Near caches can be created directly on client nodes by passing NearConfiguration into
the Ignite.createNearCache(NearConfiguration) or Ignite.getOrCreateNearCache(Near
Configuration) nodes. Use Ignite.getOrCreateCache(CacheConfiguration,
NearCacheConfiguration) method if you need both start a distributed cache dynamically and
create a near-cache for it

// Create near-cache configuration for "myCache".


NearCacheConfiguration<Integer, Integer> nearCfg =
new NearCacheConfiguration<>();

// Use LRU eviction policy to automatically evict entries


// from near-cache, whenever it reaches 100_000 in size.
nearCfg.setNearEvictionPolicy(new LruEvictionPolicy<>(100_000));
// Create a distributed cache on server nodes and
// a near cache on the local node, named "myCache".
IgniteCache<Integer, Integer> cache = ignite.getOrCreateCache(
new CacheConfiguration<Integer, Integer>("myCache"), nearCfg);

whenever utilizing Ignite with affinity colocation, near caches should not be used. If
computations are collocated with the corresponding partition cache nodes then the near cache is
simply not needed because all the data is available locally in the partitioned cache. Near caches
are fully transactional and get updated or invalidated automatically whenever the data changes
on the servers. Whenever accessing data from PARTITIONED caches on the server side in a non-
collocated fashion, you may need to configure near-caches on the server nodes
via CacheConfiguration.setNearConfiguration(...) property.

Most configuration parameters available on CacheConfiguration that make sense for the near
cache are inherited from the server configuration. For example, if the server cache has
an ExpiryPolicy, entries in the near cache will be expired based on the same policy.

Parameters listed in the table below are not inherited from the server configuration and provided
separately, via the NearCacheConfiguration object.

setNearEvictionPolicy(CacheEvictionPolicy) Eviction policy for the near cache. None

setNearStartSize(int) Start size for near cache. 375,000

Affinity Collocation
It is beneficial to collocate different cache keys together if they will be accessed together. Quite
often your business logic will require access to more than one cache key. By collocating them
together you can make sure that all keys with the same affinityKey will be cached on the
same processing node, hence avoiding costly network trips to fetch data from remote nodes.

For example, let's say you have Person and Company objects and you want to
collocate Person objects with Company objects for which this person works. To achieve that,
cache key used to cache Person objects should have a field or method annotated
with @AffinityKeyMapped annotation, which will provide the value of the company key for
collocation. For convenience, you can also optionally use AffinityKey class.

public class PersonKey {


// Person ID used to identify a person.
private String personId;

// Company ID which will be used for affinity.


@AffinityKeyMapped
private String companyId;
...
}
// Instantiate person keys with the same company ID which is used as affinity key.
Object personKey1 = new PersonKey("myPersonId1", "myCompanyId");
Object personKey2 = new PersonKey("myPersonId2", "myCompanyId");

Person p1 = new Person(personKey1, ...);


Person p2 = new Person(personKey2, ...);

// Both, the company and the person objects will be cached on the same node.
comCache.put("myCompanyId", new Company(...));
perCache.put(personKey1, p1);
perCache.put(personKey2, p2);

Object personKey1 = new AffinityKey("myPersonId1", "myCompanyId");


Object personKey2 = new AffinityKey("myPersonId2", "myCompanyId");

Person p1 = new Person(personKey1, ...);


Person p2 = new Person(personKey2, ...);

// Both, the company and the person objects will be cached on the same node.
comCache.put("myCompanyId", new Company(..));
perCache.put(personKey1, p1);
perCache.put(personKey2, p2);

It is also possible to route computations to the nodes where the data is cached. This concept is
known as Collocation Of Computations And Data. It allows to route whole units of work to a
certain node.

To collocate compute with data you should


use IgniteCompute.affinityRun(...) and IgniteCompute.affinityCall(...) methods.

final String companyId = "myCompanyId";

// Execute Runnable on the node where the key is cached.


ignite.compute().affinityRun("myCache", companyId, new IgniteRunnable() {
@Override public void run() {
Company company = cache.get(companyId);

Person person1 = cache.get(personKey1);


Person person2 = cache.get(personKey2);
...
}
};

Both IgniteCompute.affinityRun(...) and IgniteCache.invoke(...) methods offer ability to


collocate compute and data. The main difference is that invoke(...) methods is atomic and
executes while holding a lock on a key. You should not access other keys from within
the EntryProcessor logic as it may cause a deadlock.

affinityRun(...) and affinityCall(...), on the other hand, do not hold any locks. For
example, it is absolutely legal to start multiple transactions or execute cache queries from these
methods without worrying about deadlocks. In this case Ignite will automatically detect that the
processing is collocated and will employ a light-weight 1-Phase-Commit optimization for
transactions (instead of 2-Phase-Commit).

AffinityFunction is a pluggable API used to determine an ideal mapping of partitions to nodes in


the grid. When cluster topology changes, the partition-to-node mapping may be different from an
ideal distribution provided by the affinity function until rebalancing is completed.

Ignite is shipped with RendezvousAffinityFunction which allows a bit of discrepancy in partition-


to-node mapping (i.e. some nodes may be responsible for a slightly larger number of partitions
than others), however, it guarantees that when topology changes, partitions are migrated only to
a joined node or only from a left node. No data exchange will happen between existing nodes in
a cluster.

Note that the cache affinity function does not directly map keys to nodes, it maps keys to
partitions. A partition is simply a number from a limited set (0 to 1024 by default). After the keys
are mapped to their partitions (i.e. they get their partition numbers), the existing partition-to-
nodes mapping is used for current topology version. A key-to-partition mapping must not change
over the time.

<bean class="org.apache.ignite.configuration.IgniteConfiguration">
<property name="cacheConfiguration">
<list>
<!-- Creating a cache configuration. -->
<bean class="org.apache.ignite.configuration.CacheConfiguration">
<property name="name" value="myCache"/>

<!-- Creating the affinity function with custom setting. -->


<property name="affinity">
<bean
class="org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction">
<property name="excludeNeighbors" value="true"/>
<property name="partitions" value="2048"/>
</bean>
</property>
</bean>
</list>
</property>
</bean>
// Preparing Apache Ignite node configuration.
IgniteConfiguration cfg = new IgniteConfiguration();

// Creating a cache configuration.


CacheConfiguration cacheCfg = new CacheConfiguration("myCache");

// Creating the affinity function with custom setting.


RendezvousAffinityFunction affFunc = new RendezvousAffinityFunction();

affFunc.setExcludeNeighbors(true);

affFunc.setPartitions(2048);

// Applying the affinity function configuration.


cacheCfg.setAffinity(affFunc);

// Setting the cache configuration.


cfg.setCacheConfiguration(cacheCfg);

It is useful to arrange partitions in a cluster in such a way that primary and backup copies are not
located on the same physical machine. To ensure this property, a user can
set excludeNeighbors flag on RendezvousAffinityFunction.

Sometimes it is also useful to have primary and backup copies of a partition on different racks. In
this case, a user may assign a specific attribute to each node and then
use AffinityBackupFilter property of RendezvousAffinityFunction to exclude nodes from the
same rack that are candidates for backup copy assignment.

AffinityFunction is a pluggable API and a user can provide it's own implementation of the
function. The 3 main methods of AffinityFunction API are:

 partitions() - Gets the total number of partitions for a cache. Cannot be changed while
cluster is up.

 partition(...) - Given a key, this method determines which partition a key belongs to. The
mapping must not change over time.

 assignPartitions(...) - This method is called every time a cluster topology changes. This
method returns a partition-to-node mapping for the given cluster topology.

 CacheAffinityKeyMapper is a pluggable API responsible for getting an affinity key for a


cache key. Usually cache key itself is used for affinity, however sometimes it is important
to change affinity of a cache key in order to collocate it with other cache keys.
 The main method of CacheAffinityKeyMapper is affinityKey(key) which
returns affinityKey for a cache key. By default, Ignite will look for any field or method
annotated with @CacheAffinityKeyMapped annotation. If such field or method is not found,
then the cache key itself is used for affinity. If such field or method is found, then the
value of this field or method will be returned
from CacheAffinityKeyMapper.affinityKey(key)method. This allows you to specify an
alternate affinity key, other than the cache key itself, whenever needed.

Apache Ignite memory-centric platform is based on the durable memory architecture that
allows storing and processing data and indexes both in memory and on disk when
the Ignite Native Persistence feature is enabled. The durable memory architecture helps
achieve in-memory performance with the durability of disk using all the available
resources of the cluster.

Ignite's durable memory is built and operates in a way similar to the virtual memory of
operating systems, such as Linux. However, one significant difference between these two
types of architecture is that the durable memory, whenever the persistence is enabled,
will always treat the disk as the superset of the data, capable of surviving crashes and
restarts, while the traditional virtual memory uses the disk only as a swap extension,
which gets erased once the process stops.

Since this architecture is memory-centric, the RAM is always treated as the first memory tier,
where all the processing happens. Following are the characteristics and benefits of the in-
memory part of the architecture:

 Off-heap Based. All the data and indexes are stored outside of Java heap that allows easy
processing petabytes of data located in the cluster.
 Removes Noticeable Garbage Collection Pauses. Since all the data and indexes are
stored off-heap, application's code is the only source possible for long stop-the-world pauses.

 Predictable memory usage. By default, durable memory uses as much RAM and disk space
as possible. However, it's easy to set up memory utilization to suit your application
requirements.

 Automatic memory defragmentation. Apache Ignite uses the memory as efficiently as


possible and executes defragmentation routines in the background avoiding fragmentation.

 Improved performance and memory utilization. All the data and indexes are stored in
paged format with similar representation in memory and on disk, which removes any need for
serializing or deserializing of data.

Ignite native persistence adds the following benefits:

 The persistence is optional. Choose which data sets are stored only in memory, only on
disk, or when memory is used as a caching layer for disk.

 Data Resiliency. Since Ignite persistent tier stores the full data set, it can survive cluster
crashes and restarts, without loosing any data and while preserving its strong transactional
consistency.

 Cache only hot data in memory. Store the superset of data on disk and a subset of data in
memory. With the Ignite Native Persistence feature enabled, there is no need to fit all the data
in RAM. The durable memory will continue to retain the hot data in RAM, automatically
purging the cold data from memory when there is no more space is left.

 Execute SQL over the whole data sets. Most in-memory systems can query data only when
it is preloaded in memory, therefore limiting the data size to the size of memory only. Ignite
SQL will always span the whole distributed persisted data set, utilizing the in-memory caches
for better performance. Essentially, Ignite can serve as a fully featured distributed
transactional SQL database.

 Instantaneous cluster restarts. If the whole cluster goes down, it can restart and become
operational immediately. The in memory caches will warm up automatically, as the data starts
getting accessed.

Ignite Durable Memory is a page based memory architecture that is split into pages of
fixed size. The pages are stored in managed off-heap regions in RAM (outside of Java
heap) and are organized in a special hierarchy on disk Ignite maintains the same binary
data representation both, in memory and on disk. This removes the need for costly
serialization whenever moving data between memory and disk.

Durable memory can consist of one or many off-heap memory regions. A memory region is a
logical expandable area that is configured with a memory policy. The regions can vary in size,
eviction policies and other parameters explained in the memory policy section below.

By default, durable memory allocates a single memory region that can occupy up to 80% of RAM
available on a local cluster node.

For better performance you should always try to keep operational data in memory as much as
possible. To achieve that you will need to configure multiple memory regions.
For example, let us assume that we have Person, Purchases and Records entities stored
in PersonsCache, PurchasesCache and RecordsCache respectively. The Persons and Purchase
data is operational, i.e. the data we have to access frequently, and the Records data contains
historical data accessed less often.

Now, let us assume that we only have 200GB of RAM available to us. In this case, we may wish
to share the physical memory as follows:

 190 GB memory region will be created for our operational or frequently accessed data such
as Persons and Purchases. So both PersonsCache and PurchasesCache will get the
maximum performance out of our cluster.

 10 GB memory region will be allocated for historical or rarely accessed data sets like
RecordsCache, whose data will be mostly located on disk.

Every memory region starts with an initial size and has a maximum size it can grow to. The
region expands to its maximum boundary allocating continuous memory segments. By default,
the max size of a memory region is set to 80% of the RAM available on the system. If the max
size of a memory region is not explicitly set (via
org.apache.ignite.configuration.MemoryPolicyConfiguration.setMaxSize()), then it can take up to
80% of the RAM available on your machine.

A memory segment is a continuous byte array or physical memory allocated from the operating
system. The array is divided into pages of fixed size. There are several types of pages that can
reside in the segment, as it is shown in the picture below

A data page stores cache entries you put into Apache Ignite caches from an application side
(data pages are colored in green in the picture above).

Usually, a single data page holds multiple key-value entries in order to use the memory as
efficiently as possible and avoid memory fragmentation. When a new key-value entry is being
added to a cache, Ignite will make a best effort to look up a page that can fit the whole key-value
entry.
However, if an entry's total size exceeds the page size configured via
theMemoryConfiguration.setPageSize(..) parameter, then the entry will occupy more than one
data page. If you have many cache entries that do not fit in a single page, then it makes sense to
increase the page size configuration parameter.

If during an update an entry size expands beyond the free space available in its data page, then
Ignite will search for a new data page that has enough room to take the updated entry and will
move the entry there.

All SQL indexes defined and used in an application are arranged and maintained in a B+
treedata structure. For every unique index that is declared in an SQL schema, Ignite instantiates
and manages a dedicated B+ tree instance.

The cache keys are also stored in B+ trees and are ordered by their hash code values.
As shown in the picture above, the whole purpose of a B+ tree is to link and order the index
pages that are allocated and stored within the durable memory. Internally, an index page
contains all the information needed to locate the indexed value, entry offset in a data page, and
links to other index pages in order to traverse the tree (index pages are colored in purple in the
picture above).

B+ tree Meta Page is needed to get to the root of a specific B+ tree and to its layers for efficient
execution of range queries. For instance, when myCache.get(keyA) operation is executed, it will
trigger the following execution flow:

1. Ignite will look for a memory region to which myCache belongs to.

2. The meta page pointing to the hash index B+ tree of myCache will be located.

3. Based on keyA hash code, the index page the key belongs to will be located in the B+ tree.

4. If the corresponding index page is not found neither in memory or on disk, then Ignite
concludes that the key does not exist and will return null.

5. If the index page exists, then it will contain all the information needed to find the data page of
the cache entry keyA refers to.

Ignite will locate the data page for keyA and will return the value to the user.

The execution flow above explains how a cache entry is looked up in the page memory. Now let
us go over how Ignite stores a new cache entry if an operation like myCache.put(keyA, valueA) is
called.

In this scenario, the durable memory relies on free lists data structures. A free list is a doubly
linked list that stores references to memory pages of approximately equal free space. For
instance, there is a free list that stores all the data pages that have up to 75% free space, and a
list that keeps track of the index pages with 25% capacity left. Data and index pages are tracked
in separate free lists.
Here is the execution flow of myCache.put(keyA, valueA) operation:

1. Ignite will look for a memory region to which myCache belongs to.

2. The meta page pointing to the hash index B+ Tree of myCache will be located.

3. Based on keyA hash code, the index page the key belongs to will be located in the B+ tree.
4. If the corresponding index page is not found neither in memory or on disk, then a new page
will be requested from one of the free lists. Once the index page is provided, it will be added
to the B+ tree.

5. If the index page is empty (i.e. does not refer to any data page), then the data page will be
provided by one of the free lists, depending on the total cache entry size. A reference to the
data page will be added to the index page.

6. The cache entry is added to the data page.

By default, Ignite nodes consume up to 80% of the RAM available locally, and in most cases this
is the only parameter you might need to change. To do this, change the size of the default
memory region

<bean class="org.apache.ignite.configuration.IgniteConfiguration">

<property name="memoryConfiguration">
<bean class="org.apache.ignite.configuration.MemoryConfiguration">
<!-- Set the size of default memory region to 4GB. -->
<property name="defaultMemoryPolicySize" value="#{4L * 1024 * 1024 * 1024}"/>
</bean>
</property>

<!-- The rest of the parameters -->


</bean>
IgniteConfiguration cfg = new IgniteConfiguration();

// Changing total RAM size to be used by Ignite Node.


MemoryConfiguration memCfg = new MemoryConfiguration();

// Setting the size of the default memory region to 4GB to achieve this.
memCfg.setDefaultMemoryPolicySize(4L * 1024 * 1024 * 1024);

cfg.setMemoryConfiguration(memCfg);

// Starting the node.


Ignition.start(cfg);

For more advanced tuning of the durable memory, refer to the following documentation sections:

 Global Configuration Parameters

 Memory Policies

 On-heap caching

 Example
To alter the main durable memory settings such as page size,
use org.apache.ignite.configuration.MemoryConfiguration that is passed via
the IgniteConfiguration.setMemoryConfiguration(...) method

Parameter Name Description Default Value

setPageSize(...) Sets default page 2 KB


size.
Usually, you change
the parameter when
an application
generates a lot of the
objects that don't fit
into a single page.

setDefaultMemoryPolicySize(...) Sets the size of the 80% of RAM


default memory
region which is
created
automatically.
If the proper is not set
then the default
region can consume
up to 80% of RAM
available on a local
machine.

setDefaultMemoryPolicyName(...) Sets default memory default


policy's name. By
default every cache is
bound to a memory
region instantiated
with this policy.
Refer to memory
policies section to
learn more about
memory policies.

setMemoryPolicies(...) Sets a list of all An empty array. A


memory policies configuration that is
available in the used to create the
cluster.
default region is not
Refer to memory
stored there.
policies section to
learn more about
memory policies.

setSystemCacheInitialSize(...) Sets initial size of a 40 MB

memory region
reserved for system
cache.

setSystemCacheMaxSize(...) Sets maximum size 100 MB


of a memory region
reserved for system
cache.
The total size should
not be less than 10
MB due to internal
data structures
overhead

setConcurrencyLevel(...) Sets the number of A total number of


concurrent segments available CPUs times
in Apache Ignite 4.
internal page
mapping tables

to change the page size and concurrency level using MemoryConfiguration:


<bean class="org.apache.ignite.configuration.IgniteConfiguration">
<property name="memoryConfiguration">
<bean class="org.apache.ignite.configuration.MemoryConfiguration">
<!-- Set concurrency level -->
<property name="concurrencyLevel" value="4"/>

<!-- Set the page size to 4 KB -->


<property name="pageSize" value="4096"/>
</bean>
</property>

<!--- Additional settings ---->


</bean>
// Ignite configuration.
IgniteConfiguration cfg = new IgniteConfiguration();

// Page memory configuration.


MemoryConfiguration memCfg = new MemoryConfiguration();

// Altering the concurrency level.


memCfg.setConcurrencyLevel(4);

// Changing the page size to 4 KB.


memCfg.setPageSize(4096);

// Applying the new page memory configuration.


cfg.setMemoryConfiguration(memCfg);

By default, the durable memory initiates a single expandable memory region that can take up to
80% of the RAM available on a local machine. However, there is a way to define multiple
memory regions with various parameters and custom behavior relying on the memory policies'
API.

A memory policy is a set of configuration parameters, exposed


through org.apache.ignite.configuration.MemoryPolicyConfiguration, like initial and maximum
region size, an eviction policy and more.

For instance, to configure a 500 MB memory region with enabled data pages eviction, we need
to define a memory policy like below:

<bean class="org.apache.ignite.configuration.IgniteConfiguration">
<!-- Durable Memory configuration -->
<property name="memoryConfiguration">
<bean class="org.apache.ignite.configuration.MemoryConfiguration">
<!-- Defining a custom memory policy. -->
<property name="memoryPolicies">
<list>
<!-- 500 MB total size and RANDOM_2_LRU eviction algorithm. -->
<bean
class="org.apache.ignite.configuration.MemoryPolicyConfiguration">
<property name="name" value="500MB_Region"/>
<!-- 100 MB initial size. -->
<property name="initialSize" value="#{100 * 1024 * 1024}"/>
<!-- 500 MB maximum size. -->
<property name="maxSize" value="#{500 * 1024 * 1024}"/>
<!-- Enabling data pages eviction. -->
<property name="pageEvictionMode" value="RANDOM_2_LRU"/>
</bean>
</list>
</property>
</bean>
</property>

<!-- The rest of the configuration. -->


<!-- ....... -->
</bean>
// Ignite configuration.
IgniteConfiguration cfg = new IgniteConfiguration();

// Durable Memory configuration.


MemoryConfiguration memCfg = new MemoryConfiguration();

// Creating a custom memory policy for a new memory region.


MemoryPolicyConfiguration plCfg = new MemoryPolicyConfiguration();

// Policy/region name.
plCfg.setName("500MB_Region_Eviction");

// Setting initial size.


plCfg.setInitialSize(100L * 1024 * 1024);

// Setting maximum size.


plCfg.setMaxSize(500L * 1024 * 1024);

// Setting data pages eviction algorithm.


plCfg.setPageEvictionMode(DataPageEvictionMode.RANDOM_2_LRU);

// Applying the memory policy.


memCfg.setMemoryPolicies(plCfg);

// Applying the new page memory configuration.


cfg.setMemoryConfiguration(memCfg);

Ignite cache can be mapped to this region (see the following configuration example). To achieve
this, the name of the policy has to be passed as a parameter
to org.apache.ignite.configuration.CacheConfiguration.setMemoryPolicyName(...)
method:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
<!-- Durable Memory and other configuration parameters. -->
<!-- ....... -->

<property name="cacheConfiguration">
<list>
<!-- Cache that is mapped to non-default memory region. -->
<bean class="org.apache.ignite.configuration.CacheConfiguration">
<!--
Setting a memory policy name to bind to a specific region.
-->
<property name="memoryPolicyName" value="500MB_Region"/>
<!-- Cache unique name. -->
<property name="name" value="SampleCache"/>

<!-- Additional cache configuration parameters -->


</bean>
</list>
</property>

<!-- The rest of the configuration. -->


<!-- ....... -->
</bean>
// Ignite configuration.
IgniteConfiguration cfg = new IgniteConfiguration();

// Durable Memory configuration and the rest of the configuration.


// ....

// Creating a cache configuration.


CacheConfiguration cacheCfg = new CacheConfiguration();

// Setting a memory policy name to bind to a specific memory region.


cacheCfg.setMemoryPolicyName("500MB_Region_Eviction");

// Setting the cache name.


cacheCfg.setName("SampleCache");

// Applying the cache configuration.


cfg.setCacheConfiguration(cacheCfg);

Once an Ignite cluster is started with this configuration, the durable memory will allocate 256 MB
(default initial size) region that can grow up to 500 MB. That new region will coexist with the
default memory region and all the data, as well as indexes for SampleCache will reside in that
region. The rest of the caches you might have in your deployment will be bound to the default
memory region unless you map them to another region explicitly, as shown above.

If the overall memory usage goes beyond the maximum size parameter, then an out of memory
exception will be thrown. To avoid this, do one or combination of the following:
 Enable Ignite Persistent Store that keeps all the data on disk helping the durable memory to
store only hot data in RAM.

 Use one of eviction algorithms available. Note, that the eviction will be enabled by default only
if the Ignite Persistent Store is used, otherwise it will be disabled.

 Set the maximy size to a bigger value.

The default memory region is instantiated with parameters of the default memory policy prepared
by org.apache.ignite.configuration.MemoryConfiguration.createDefaultPolicyConfig()metho
d. If you need to change some of the parameters, follow the steps below:

 Create a new memory policy with a custom name and parameters.


 Pass the name of the policy
to org.apache.ignite.configuration.MemoryConfiguration.
setDefaultMemoryPolicyName(...) method.

Refer to memory policies example and to the configuration file used by the example to see how
to configure and use multiple memory regions in your cluster.

org.apache.ignite.configuration.MemoryPolicyConfiguration supports the following

parameters

setName(...) Unique memory policy name. Required parameter.

setInitialSize(...) Sets initial size of the 256 MB

memory region defined by


this memory policy. When the
used memory size exceeds
this value, new chunks of
memory will be allocated until
maximum size is reached.

setMaxSize(...) Sets maximum size of the 80% of RAM


memory region defined by
this memory policy.
The total size should not be
less than 10 MB due to the
internal data structures
overhead.
If the overall memory usage
goes beyond this parameter
an out of memory exception
will be thrown. To avoid this,
enable Ignite Persistent
Store or use an eviction
algorithm described below, or
set the default size to a
bigger value

setSwapFilePath(...) A path to the memory- Disabled by default.


mapped file the memory
region defined by this
memory policy will be
mapped to. Having this path
set, allows relying on
swapping capabilities of an
underlying operating system
for the region.

setPageEvictionMode(...) Sets one of the data pages Disabled by default.


eviction algorithms available
for usage. Refer to eviction
policies for more details.

setEvictionThreshold(...) A threshold for memory 0.9

pages eviction initiation. For


instance, if the threshold is
0.9 it means that the page
memory will start the eviction
only after 90% of the memory
region (defined by this policy)
is occupied.

setEmptyPagesPoolSize(...) Specifies the minimal number 100


of empty pages to be present
in reuse lists for this memory
policy. This parameter
ensures that Apache Ignite
will be able to successfully
evict old data entries when
the size of a (key, value) pair
is slightly larger than page
size / 2.
Increase this parameter if a
cache can contain very big
entries (total size of pages in
this pool should be enough to
contain largest cache entry).

setMetricsEnabled(...) Enables memory metrics false

aggregation for this region.


Refer to Memory & Cache
Metrics for more details.

Ignite Durable Memory is an off-heap memory that allocates all the memory regions outside of
Java heap and stores cache entries there. However, you can enable on-heap caching for the
cache entries by
setting org.apache.ignite.configuration.CacheConfiguration.setOnheapCacheEnabled(...) to t
rue.

On-heap caching is useful for scenarios when you do a lot of cache reads on server nodes that
work with cache entries in the binary form or invoke cache entries' deserialization. For instance,
this might happen when a distributed computation or deployed service gets some data from
caches for further processing.

To manage the on-heap cache size and avoid its constant growth, make sure to enable one of
available cache entries based eviction policies. For more information on how to configure and
use multiple memory regions in your cluster, refer to the memory policies example and to
the configuration file used in the example.

Ignite supports two distinct data eviction types -

1. page-based eviction for the off-heap memory data sets.


2. entry-based eviction for the optional on-heap cache.

Page-based eviction is configured via memory policies. Ignite durable memory consists of one or
more memory regions configured by MemoryPolicyConfigurations. By default, a region
constantly grows in size until its maximum size is reached. To avoid possible region exhaustion,
you may need to set one of the data page eviction modes - Random-LRU or Random-2-LRU via
the MemoryPolicyConfiguration.setPageEvictionMode(...) configuration parameter. The
eviction modes track data pages usage and evict them based on the specific eviction
algorithm. if Ignite Persistence is enabled, then the page-based evictions have no effect,
because the oldest pages will be purged from memory automatically.

To enable Random-LRU eviction algorithm, pass DataPageEvictionMode.RANDOM_LRU value to


a respective MemoryPolicyConfiguration, as shown in the example below:

<bean class="org.apache.ignite.configuration.MemoryConfiguration">
<!-- Defining additional memory poolicies. -->
<property name="memoryPolicies">
<list>
<!--
Defining a policy for 20 GB memory region with RANDOM_LRU eviction.
-->
<bean class="org.apache.ignite.configuration.MemoryPolicyConfiguration">
<property name="name" value="20GB_Region_Eviction"/>
<!-- Initial size is 5 GB. -->
<property name="initialSize" value="#{5 * 1024 * 1024 * 1024}"/>
<!-- Maximum size is 20 GB. -->
<property name="maxSize" value="#{20 * 1024 * 1024 * 1024}"/>
<!-- Enabling RANDOM_LRU eviction. -->
<property name="pageEvictionMode" value="RANDOM_LRU"/>
</bean>
</list>
...
</property>
...
</bean>
// Defining additional memory poolicies.
MemoryConfiguration memCfg = new MemoryConfiguration();

// Defining a policy for 20 GB memory region with RANDOM_LRU eviction.


MemoryPolicyConfiguration memPlc = new MemoryPolicyConfiguration();

memPlc.setName("20GB_Region_Eviction");

// Initial size is 5 GB.


memPlc.setInitialSize(5L * 1024 * 1024 * 1024);

// Maximum size is 20 GB.


memPlc.setMaxSize(20L * 1024 * 1024 * 1024);

// Enabling RANDOM_LRU eviction.


memPlc.setPageEvictionMode(DataPageEvictionMode.RANDOM_LRU);

// Setting the new memory policy.


memCfg.setMemoryPolicies(memPlc);

Random-LRU algorithm works the following way:

 Once a memory region defined by a memory policy is configured, an off-heap array is


allocated to track 'last usage' timestamp for every individual data page.

 When a data page is accessed, its timestamp gets updated in the tracking array.

 When it is time to evict a page, the algorithm randomly chooses 5 indexes from the tracking
array and evicts the page with the oldest timestamp. If some of the indexes point to non-data
pages (index or system pages), then the algorithm picks another page.

To enable Random-2-LRU eviction algorithm, which is a scan-resistant version of Random-LRU,


pass DataPageEvictionMode.RANDOM_2_LRU value to a
respective MemoryPolicyConfiguration, as shown in the example below:

<bean class="org.apache.ignite.configuration.MemoryConfiguration">
<!-- Defining additional memory poolicies. -->
<property name="memoryPolicies">
<list>
<!--
Defining a policy for 20 GB memory region with RANDOM_2_LRU eviction.
-->
<bean class="org.apache.ignite.configuration.MemoryPolicyConfiguration">
<property name="name" value="20GB_Region_Eviction"/>
<!-- Initial size is 5 GB. -->
<property name="initialSize" value="#{5 * 1024 * 1024 * 1024}"/>
<!-- Maximum size is 20 GB. -->
<property name="maxSize" value="#{20 * 1024 * 1024 * 1024}"/>
<!-- Enabling RANDOM_2_LRU eviction. -->
<property name="pageEvictionMode" value="RANDOM_2_LRU"/>
</bean>
</list>
...
</property>
...
</bean>
// Defining additional memory poolicies.
MemoryConfiguration memCfg = new MemoryConfiguration();

// Defining a policy for 20 GB memory region with RANDOM_LRU eviction.


MemoryPolicyConfiguration memPlc = new MemoryPolicyConfiguration();
memPlc.setName("20GB_Region_Eviction");

// Initial size is 5 GB.


memPlc.setInitialSize(5L * 1024 * 1024 * 1024);

// Maximum size is 20 GB.


memPlc.setMaxSize(20L * 1024 * 1024 * 1024);

// Enabling RANDOM_2_LRU eviction.


memPlc.setPageEvictionMode(DataPageEvictionMode.RANDOM_2_LRU);

// Setting the new memory policy.


memCfg.setMemoryPolicies(memPlc);

In Random-2-LRU the two most recent access timestamps are stored for every data page. At the
time of eviction, the algorithm randomly chooses 5 indexes from the tracking array and the
minimum between two latest timestamps is taken for further comparison with corresponding
minimums of four other pages that are chosen as eviction candidates.

Random-LRU-2 outperforms LRU by resolving "one-hit wonder" problem - if a data page is


accessed rarely, but accidentally accessed once, it's protected from eviction for a long time.

In Random-LRU eviction mode the most recent access timestamp is stored for a data page
whereas in Random-2-LRU mode two most recent access timestamps are stored for every data
page.

By default, a data page's eviction algorithm is triggered when the total memory region's
consumption gets to 90%.
Use MemoryPolicyConfiguration.setEvictionThreshold(...) parameter if you need to
initiate the eviction earlier or later.

Entry-Based Eviction for On-Heap Cache

The durable memory allows storing hot cache entries in Java heap if on-heap caching feature is
enabled via CacheConfiguration.setOnheapCacheEnabled(...). Once the on-heap cache is turned
on, you can use one of the on-heap eviction policies to manage the growing on-heap cache.

Eviction policies control the maximum number of elements that can be stored in a cache's on-
heap memory. Whenever the maximum on-heap cache size is reached, entries are evicted from
Java heap.

S-ar putea să vă placă și