<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Coral Blocks</title>
	<atom:link href="https://www.coralblocks.com/index.php/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.coralblocks.com/index.php</link>
	<description>Building amazing software, one piece at a time.</description>
	<lastBuildDate>Mon, 13 Jul 2026 11:58:10 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.1</generator>
	<item>
		<title>The Sequencer Architecture and Why Financial Firms Love It</title>
		<link>https://www.coralblocks.com/index.php/the-sequencer-architecture-and-why-financial-firms-love-it/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=the-sequencer-architecture-and-why-financial-firms-love-it</link>
		<comments>https://www.coralblocks.com/index.php/the-sequencer-architecture-and-why-financial-firms-love-it/#comments</comments>
		<pubDate>Wed, 01 Jul 2026 17:28:28 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[determinism]]></category>
		<category><![CDATA[deterministic]]></category>
		<category><![CDATA[distributed systems]]></category>
		<category><![CDATA[failover]]></category>
		<category><![CDATA[Sequencer]]></category>

		<guid isPermaLink="false">https://www.coralblocks.com/index.php/?p=3337</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<style>
.article_coralsequencer {
font-size: 112%;
line-height: 1.55;
}</p>
<p>.article_coralsequencer h3 {
font-size: 130%;
line-height: 1.3;
margin-top: 50px;
margin-bottom: 18px;
}</p>
<p>.article_coralsequencer p {
margin-bottom: 26px;
}</p>
<p>.li_facts { margin: 0 0 17px 0; }</p>
</style>
<div class="article_coralsequencer">
<p style="margin-top: 20px;">
CoralSequencer implements the sequencer architecture, a messaging architecture widely used by financial firms to build deterministic distributed systems. Its core idea is simple: all nodes read all messages in the exact same order, always. From that single rule, the system gets consistency, replayability, high availability, and a clean way to evolve many independent services without losing a single authoritative timeline. <span id="more-3337"></span>
</p>
<p style="margin-top: 35px;">
<font color="blue"><strong>This article is a quick summary of the 1-hour video below.</strong></font> The video has a long demo section that you can skip if you want to. You can also watch it on YouTube by <a href="https://www.youtube.com/watch?v=DyktSiBTCdk" target="_blank">clicking here</a>. <b>Note:</b> Contrary to the YouTube video, the video below has <font color="red"><b>no ads</b></font>.<br />
<center><br />
<div style="width: 600px; max-width: 100%;" class="wp-video"><!--[if lt IE 9]><script>document.createElement('video');</script><![endif]-->
<video class="wp-video-shortcode" id="video-3337-1" width="600" height="338" preload="metadata" controls="controls"><source type="video/mp4" src="/wp-content/uploads/videos/CoralSequencer.mp4?_=1" /><a href="/wp-content/uploads/videos/CoralSequencer.mp4">/wp-content/uploads/videos/CoralSequencer.mp4</a></video></div><br />
</center>
</p>
<p style="margin-top: 35px;">
<h3>One Stream, One Order</h3>
</p>
<p>
The most important characteristic of the sequencer architecture is that there is only one ordered event-stream. Nodes do not subscribe to different topics that can race with each other or arrive in different orders. Every node receives the same messages in the same sequence, and each node can quickly ignore the messages it does not care about.
</p>
<p>
That is what makes the architecture deterministic. If two nodes run the same code, consume the same ordered input, and use the same sequenced clock, they will build the same state. A gateway, a position monitor, a risk engine, a logger, or a backup instance can all observe the same history and reach the same conclusions from it.
</p>
<p>
This also makes the whole system easier to reason about. When something happens, it happened at one exact sequence number. There is no ambiguity about whether one service saw an event before another service or whether two streams crossed in an unexpected way.
</p>
<p style="margin-top: 35px;">
<h3>The Sequencer Is The Source Of Truth</h3>
</p>
<p>
At the center of the architecture is the sequencer. Nodes send upstream messages to it, and the sequencer assigns the final order to the downstream messages. Once a message is sequenced and timestamped, it is distributed to every node and becomes part of the event-stream.
</p>
<p>
This does not mean every node has to do the same work. It means every node has the same input. An order port can care about client orders, a gateway can care about exchange routing, and a position monitor can care only about executions. They all read the same stream, but each one applies its own logic.
</p>
<p>
Because the sequencer defines the order, the rest of the system can run in parallel without losing consistency. Nodes can live on different CPU cores, different machines, or different data centers, while still sharing the same ordered reality.
</p>
<p style="margin-top: 35px;">
<h3>Replay Makes State Recoverable</h3>
</p>
<p>
A node that starts late does not need a special recovery path. It can rewind from the beginning of the session, or from a known committed sequence number plus snapshot, and rebuild its state from the same messages that every other node already processed.
</p>
<p>
This is why deterministic clusters become natural in this architecture. A backup gateway, backup position monitor, or backup sequencer can start, rewind, catch up, and then sit at the same state as the live instance. If one machine fails, another instance can already have the same state because it has been consuming the same ordered input.
</p>
<p>
The same idea is useful for debugging and auditing. The archiver saves the full event-stream to a binary session file, and that file can be replayed later. A trading day can be reprocessed offline, without the original network, sequencer, or live services. The session file becomes the exact record of what happened.
</p>
<p style="margin-top: 35px;">
<h3>The Supporting Components</h3>
</p>
<p>
The sequencer is the central ordering component, but the full architecture includes other important actors. Replayers provide gap filling and rewinding. If a node misses a packet, it asks a replayer for the missing message. If a node joins late, it rewinds from a replayer and catches up.
</p>
<p>
The archiver persists the full stream to a binary file. The logger writes a human-readable view of the same stream. The bridge allows nodes to connect over TCP, including across networks, data centers, or cloud environments. The dispatcher distributes messages locally through shared memory when many nodes run on the same machine.
</p>
<p>
These components keep the model practical. The sequencer stays focused on ordering, while replayers, archivers, bridges, loggers, and dispatchers handle recovery, persistence, connectivity, visibility, and local distribution.
</p>
<p style="margin-top: 35px;">
<h3>Transport Can Change Without Code Changes</h3>
</p>
<p>
CoralSequencer can run with reliable multicast UDP, TCP, shared memory, or combinations of transports. The application code does not need to change when the transport changes. A deployment can use multicast UDP on premises, TCP in the cloud, shared memory inside a machine, or bridge-to-bridge TCP between regions.
</p>
<p>
Reliable multicast UDP is the natural fit when the infrastructure supports it, because the sequencer can send a message once and let the network distribute it efficiently. When multicast is not available, CoralSequencer can use a sequenced TCP protocol so the same architecture can still run in cloud or external-client environments.
</p>
<p>
The important point is that transport is an infrastructure decision, not an application design rewrite. The same nodes can keep the same logic while the deployment changes underneath them.
</p>
<p style="margin-top: 35px;">
<h3>Production Features Matter</h3>
</p>
<p>
CoralSequencer includes features that are small individually but very important in production. Replayers can be discovered over multicast. If a chosen replayer disappears, a node can fall back to multicast discovery and choose another one. Large messages can be fragmented into multiple packets and reassembled transparently for the application.
</p>
<p>
Nodes can also rewind from a local session file instead of going through the network. When a local replayer file is available, startup can be much faster, especially for sessions with millions of messages. For some nodes, committing a sequence number can avoid unnecessary reprocessing after restart.
</p>
<p>
For very long sessions, a system can also combine snapshots with rewinding from a chosen sequence number. The preferred model is still to rebuild from the event-stream when possible, but CoralSequencer gives operators and developers options when the operational reality requires them.
</p>
<p style="margin-top: 35px;">
<h3>Fairness, Batching, And Deterministic Time</h3>
</p>
<p>
The sequencer architecture needs to be fast, but it also needs to be fair. CoralSequencer lets nodes send multiple messages in flight, but it limits how many unacknowledged messages a node can place into the sequencer. This prevents one node from flooding the input path and taking over the system.
</p>
<p>
At the same time, CoralSequencer batches aggressively. When many messages are flowing, it can place multiple application messages into the same packet. This improves throughput and can reduce the effective cost of sending each individual message.
</p>
<p>
Time is also deterministic. Instead of every machine using its own local clock and creating different results during replay, the clock advances through the event-stream itself. If a message was timestamped during the live session, replaying the same session later can produce the same timestamp and the same state again.
</p>
<p style="margin-top: 35px;">
<h3>Why Financial Firms Like This Architecture</h3>
</p>
<p>
Financial systems often have many moving parts: order entry, gateways, matching engines, risk checks, position monitors, loggers, reporting nodes, and backup instances. These components need to react in real time, but they also need to stay consistent with each other.
</p>
<p>
The sequencer architecture gives those systems a single ordered history. Nodes can be added, restarted, upgraded, or moved without changing the rest of the system. A slow node can lag and recover without stopping faster nodes. A new node can join, rewind, and become useful without requiring a special synchronization protocol.
</p>
<p>
That is the main reason the architecture has remained relevant for financial systems for so long. It gives low latency, parallel execution, operational visibility, replayable history, and deterministic recovery without forcing every service to coordinate directly with every other service.
</p>
<p style="margin-top: 35px;">
<h3>Why CoralSequencer?</h3>
</p>
<p>
CoralSequencer was designed for high performance and determinism. The sequencer is single-threaded by design, so the critical path avoids locks, context switches, cache contention, and coordination between worker threads. That single thread can be pinned to one isolated CPU core and allowed to spin for ultra low latency.
</p>
<p>
The same single-threaded model also helps determinism. With multiple threads, the kernel can time-slice execution in different ways, creating different combinations of execution paths. With one critical thread, the order of execution is explicit, predictable, and tied to the event-stream. Even the clock and the timers are deterministic in order to accomplish a true deterministic finite state machine.
</p>
<p>
Its reliable multicast transport is based on the MoldUDP protocol, a established protocol in the industry for this kind of architecture and a protocol that has been used for decades in financial markets. MoldUDP keeps the normal path extremely efficient while still allowing missed packets to be recovered through replayers. At the same time, CoralSequencer is not limited to multicast: the same application code can be deployed over TCP (SoupBinTCP), shared memory, or combined TCP and UDP transports, with this choice made at the deployment/configuration level, never at the code level. For more information you can check this article: <a href="https://www.coralblocks.com/index.php/on-premises-and-cloud-infrastructure-with-coralsequencer/" target="_blank">On-Premises and Cloud Infrastructure with CoralSequencer</a>.
</p>
<p>
CoralSequencer has been battle-tested in production for more than 10 years and has been used by investment banks, market makers, hedge funds, and other demanding financial institutions. Many of them are big and famous names recognized by the industry.
</p>
<p>
That combination is what matters: a simple single-threaded hot path, an industry-proven reliable multicast protocol, deployment-level transport flexibility, deterministic replay, and a long production history under real financial workloads.
</p>
<p style="margin-top: 35px;">
<h3>Conclusion</h3>
</p>
<p>
CoralSequencer turns a distributed system into a set of deterministic nodes reading the same ordered stream. The result is a system where state can be rebuilt, failures can be handled with clusters, components can evolve independently, and the full session can be archived and investigated later.
</p>
<p>
The architecture is powerful because it keeps the rule simple: all messages, all nodes, same order. Around that rule, CoralSequencer adds the production pieces required to make it practical: reliable transports, replayers, archivers, bridges, dispatchers, batching, fairness, deterministic clocks, large messages and flexible deployment options.
</p>
</div>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/the-sequencer-architecture-and-why-financial-firms-love-it/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Why Manual Failover for the Sequencer is a Design Choice?</title>
		<link>https://www.coralblocks.com/index.php/why-manual-failover-is-a-design-choice/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=why-manual-failover-is-a-design-choice</link>
		<comments>https://www.coralblocks.com/index.php/why-manual-failover-is-a-design-choice/#comments</comments>
		<pubDate>Tue, 30 Jun 2026 12:08:06 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[elections]]></category>
		<category><![CDATA[failover]]></category>
		<category><![CDATA[leader]]></category>
		<category><![CDATA[primary]]></category>
		<category><![CDATA[raft]]></category>
		<category><![CDATA[secondary]]></category>
		<category><![CDATA[Sequencer]]></category>

		<guid isPermaLink="false">https://www.coralblocks.com/index.php/?p=3305</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<style>
.article_failover {
font-size: 112%;
line-height: 1.55;
}</p>
<p>.article_failover h3 {
font-size: 130%;
line-height: 1.3;
margin-top: 50px;
margin-bottom: 18px;
}</p>
<p>.article_failover p {
margin-bottom: 26px;
}</p>
<p>.li_facts { margin: 0 0 17px 0; }</p>
</style>
<div class="article_failover">
<p style="margin-top: 20px;">
The CoralSequencer architecture uses manual failover for the primary sequencer by design. That choice is not an omission and it is not a limitation. It is a deliberate trade-off. It keeps the normal message path as fast, simple, and deterministic as possible, and handle the rare leader failure with an informed operational decision, rather than with something that adds coordination cost to every message. In this article we&#8217;ll explain why manual failover can be preferable to automatic leader election for the high-performance primary sequencer. <span id="more-3305"></span>
</p>
<p style="margin-top: 35px;">
<h3>The Normal Path Must Stay Fast</h3>
</p>
<p>
In an automatic leader election architecture, the leader normally has to replicate each log entry to a majority quorum before the entry can be considered committed. This is the correct design for that kind of distributed consensus system, but it also places a costly coordination procedure on the critical path.
</p>
<p>
For a low-latency sequencer, that cost matters. The sequencer should be able to assign the next sequence number and immediately distribute the message to all nodes. It should not have to first send the message to backup leaders and wait for acknowledgements from a majority before the message can move forward.
</p>
<p>
By using manual failover, CoralSequencer keeps the backups out of the hot path. They still receive the sequenced messages, maintain their current position, and are ready to be activated if necessary, but they do not add a quorum round trip to every message. The result is lower latency, higher throughput, and a simpler runtime model.
</p>
<p style="margin-top: 35px;">
<h3>Financial Systems Require Human Supervision</h3>
</p>
<p>
Regardless of whether a sequencer failover is ever needed, financial systems involving money and risk should not operate without human supervision. In this environment, a failover is not merely a technical event. It can affect orders, executions, positions, risk limits, market connectivity, client flows, and downstream systems.
</p>
<p>
For that reason, failover should not happen automatically just because a timeout was triggered. The nature of the business requires judgment, verification, and accountability. There should always be an operator monitoring the system, watching the logs, understanding what is happening, and deciding when it is safe and appropriate to activate a backup sequencer.
</p>
<p style="margin-top: 35px;">
<h3>The Operator Has Better Context</h3>
</p>
<p>
When a leader appears to be having problems, the right action depends on the situation. Is the leader process down? Is the machine unreachable? Is there packet loss? Is a network link unstable? Are all backups equally up to date? These are operational questions, and the aggregated logs in front of the operator usually contain the answer.
</p>
<p>
A human operator can look at the logs, the network status, the machine state, and the current sequence numbers reported by the backups. With that information, the operator can better choose the best backup to activate, instead of blindly accepting the first node that wins an automatic election.
</p>
<p>
Automatic election algorithms are very useful when the system must continue without human involvement. But they do not understand the whole operational picture. They react to timeouts and protocol rules. A human can assess the cause of the failure and make a more deliberate decision.
</p>
<p style="margin-top: 35px;">
<h3>Manual Failover Helps Avoid Split Brain</h3>
</p>
<p>
One of the most important failover rules is simple: do not activate a new leader while the old leader might still be alive and able to send messages. If two leaders are active at the same time, the system can split into two competing timelines.
</p>
<p>
Manual failover gives the operator a chance to verify that the problematic leader is really dead, offline, isolated, or really unable to continue. The operator can close it, kill its process, power off its machine if necessary and confirm that the failing primary sequencer has really gone offline before promoting a backup sequencer.
</p>
<p>
This extra verification is valuable because failover is not only about restoring service. It is also about preserving a single authoritative sequence of messages.
</p>
<p style="margin-top: 35px;">
<h3>Leader Failure Is Rare</h3>
</p>
<p>
In this architecture, a leader failover is expected to happen because of hardware or infrastructure failure, not because of bugs or crashes in the sequencer software. The sequencer is deterministic and all its backup instances are running its exact same code.
</p>
<p>
If there is a software bug that crashes the primary, the same bug will also crash the backups, because they are processing the same deterministic message flow. An automatic election would not solve that kind of failure. It would only move leadership to another instance running the same code and likely hitting the same problem, if there is any backup instance left at all.
</p>
<p>
For that reason, it does not make sense to pay a continuous performance and complexity cost for something that should happen rarely. The system should optimize for the common case: the leader is healthy, messages are flowing, and every node is receiving the same ordered stream.
</p>
<p style="margin-top: 35px;">
<h3>No Guessing About The Best Backup</h3>
</p>
<p>
A common concern with failover without backup message acknowledgements is that, during a failover, messages might be missed by some nodes but not by other nodes, creating an inconsistency. The important point is that the operator can easily address that problem too. The backup sequencers (as well as the replayers and archivers) always show their current sequence number in the logs when they time out. Therefore, during a failover procedure, the operator can look at the aggregated logs to activate the backup sequencer with the highest sequence number. That backup is the one that has seen the most recent sequenced message. This makes the failover decision explicit and observable.
</p>
<p>
Instead of hiding the decision inside an election protocol, the system exposes the information needed so that the operator can make the safest promotion. The latest sequence number, observable and confirmed in the aggregated logs, becomes the evidence for choosing the new leader.
</p>
<p style="margin-top: 35px;">
<h3>Conclusion</h3>
</p>
<p>
Manual failover is a deliberate part of the sequencer architecture. It has been successfully used by financial companies for almost 3 decades now. It removes quorum acknowledgements from the normal message path, keeps latency low, avoids unnecessary complexity, and lets an operator make an informed decision when a (very) rare primary sequencer failure actually happens.
</p>
<p>
For systems where automatic recovery is more important than raw performance, a consensus algorithm can be a good choice. But for a high-performance sequencer, the priority is different: keep the leader fast, avoid complexity in the hot path, keep ordering deterministic, and use the aggregated logs and sequence numbers in order to conscientiously promote the right backup sequencer for the rare occasions where failover is truly needed.
</p>
</div>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/why-manual-failover-is-a-design-choice/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>On-Premises and Cloud Infrastructure with CoralSequencer</title>
		<link>https://www.coralblocks.com/index.php/on-premises-and-cloud-infrastructure-with-coralsequencer/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=on-premises-and-cloud-infrastructure-with-coralsequencer</link>
		<comments>https://www.coralblocks.com/index.php/on-premises-and-cloud-infrastructure-with-coralsequencer/#comments</comments>
		<pubDate>Fri, 03 Apr 2026 14:47:09 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[multicast]]></category>
		<category><![CDATA[Sequencer]]></category>
		<category><![CDATA[tcp]]></category>
		<category><![CDATA[udp]]></category>

		<guid isPermaLink="false">https://www.coralblocks.com/index.php/?p=3212</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<style>
* {
font-size: 101%;
}</p>
<p>.li_facts { margin: 0 0 17px 0; }</p>
</style>
<p style="margin-top: 20px;">
CoralSequencer supports multiple transport protocols, offering flexibility when building on-premises and cloud infrastructures. You can use UDP, multicast or unicast, TCP, or a combination of both to design your infrastructure in the way that best fits your needs, whether on-premises, in the cloud, or across both. <span id="more-3212"></span> In this article we&#8217;ll see some examples through diagrams.
</p>
<h3>On-Premises with Multicast</h3>
<p>
The primary and recommended transport for CoralSequencer, using an industry established reliable multicast UDP protocol. It is not only more efficient for distributing messages across nodes, but it also offers useful features such as multicast discovery.
</p>
<p><center><img src="https://www.coralblocks.com/wp-content/uploads/2026/04/Screenshot-2026-04-03-at-9.35.44-AM.png" alt="OnPremMulticast" width="737" height="655" class="alignnone size-full wp-image-3216" /></center></p>
<p style="margin-top: 1px;">
The <font color="#6bdc7e">green</font> arrows represent multicast UDP connections, while the <font color="#72bcf9">blue</font> arrows represent TCP connections. The <strong>Bridge</strong> serves as a TCP entry point into the distributed system.</p>
<p/>
<p>
<strong><font color="#0d75c4">IMPORTANT:</font></strong> Later below, we’ll see how to run the entire sequencer in the cloud using only TCP, with no multicast at all.
</p>
<p>
To make this diagram smaller, let&#8217;s abbreviate some components: Replayer = <strong>R</strong>, Logger = <strong>L</strong>, Bridge = <strong>B</strong>, Archiver = <strong>A</strong> and Sequencer = <strong>SEQ</strong>.<br />
<center><img src="https://www.coralblocks.com/wp-content/uploads/2026/04/Screenshot-2026-04-03-at-9.55.14-AM.png" alt="OnPremSmall2" width="381" height="333" class="alignnone size-full wp-image-3231" /></center>
</p>
<h3>Extending the Distributed System to the Cloud</h3>
<p>
When multicast UDP is not available, we can use an industry established sequenced TCP protocol. Your choice for the CoralSequencer transport protocol does not affect your application code and logic in any way.
</p>
<p><center><img src="https://www.coralblocks.com/wp-content/uploads/2026/04/Screenshot-2026-04-03-at-10.07.45-AM.png" alt="ExtensionToCloud" width="760" height="652" class="alignnone size-full wp-image-3242" /></center></p>
<p>
Note that we are extending our distributed system to the cloud through a <strong>single bridge-to-bridge TCP connection</strong>. The bridge on the cloud side provides connectivity to all cloud instances. It can have a backup bridge ready to take over in case it fails, so it does not become a single point of failure. You can also deploy more than one bridge on the cloud side to better distribute the load. It is important to understand that <font color="#72bcf9">bridges can be chained together to build any network graph</font>, but simplicity is often the best approach.
</p>
<p>
Also note that <strong>there is <em>no</em> multicast UDP connectivity in the cloud</strong>, only TCP and shared memory. The <strong>Dispatcher</strong> provides shared memory connectivity to nodes on the same machine, in this case within the same cloud instance. It connects out to a bridge over TCP and distributes all messages locally to all nodes through shared memory, using the same memory mapped file. As a result, it does not have to open a different TCP connection to each of its local nodes. It only opens a single TCP connection out to its bridge.
</p>
<p>
<strong><font color="#0d75c4">IMPORTANT:</font></strong> Both the bridge and the dispatcher operate in <strong>full-duplex</strong>, handling both downstream messages and upstream commands.
</p>
<p style="margin-top: 35px;">
<h3>Extending to Data Centers and External Clients</h3>
</p>
<p><center><img src="https://www.coralblocks.com/wp-content/uploads/2026/04/Screenshot-2026-04-03-at-10.39.50-AM.png" alt="SmallDataCenter" width="709" height="575" class="alignnone size-full wp-image-3252" /></center></p>
<h3>Pure TCP Sequencer Infrastructure</h3>
<p><center><img src="https://www.coralblocks.com/wp-content/uploads/2026/04/Screenshot-2026-04-03-at-10.51.55-AM.png" alt="PureTcpSeq" width="728" height="649" class="alignnone size-full wp-image-3265" /></center></p>
<p>Note that there are <em>no</em> multicast UDP connections anywhere, only TCP connections.</p>
<p style="margin-top: 35px;">
<h3>Sequencer Deployed on the Cloud</h3>
</p>
<p><center><img src="https://www.coralblocks.com/wp-content/uploads/2026/04/Screenshot-2026-04-03-at-10.57.36-AM.png" alt="AllCloud" width="1060" height="794" class="alignnone size-full wp-image-3269" /></center></p>
<p>
There are <em>no</em> multicast UDP connections anywhere, only TCP. Different cloud regions are connected through bridge-to-bridge connections. Dispatchers reduce the number of TCP connections by using shared memory when running on the same cloud instance.
</p>
<p style="margin-top: 35px;">
<h3>CoralSequencer Transport Protocols</h3>
<p>CoralSequencer offers a variety of transport protocols that can be used without requiring any code changes. Simply change a config from UDP to TCP and your application is ready to be deployed with a totally different transport protocol. Below we list the available CoralSequencer transport protocols:
</p>
<ul>
<li style="margin-top: 15px;"><strong>Multicast UDP</strong>: The primary and recommended transport for CoralSequencer, using an industry established reliable multicast UDP protocol.</li>
<li style="margin-top: 15px;"><strong>TCP</strong>: The transport used by CoralSequencer when multicast UDP is not available, such as in the cloud, or not desirable, such as for external clients, using an industry established sequenced TCP protocol.</li>
<li style="margin-top: 15px;"><strong>Shared Memory</strong>: The transport used within the same machine or cloud instance to minimize the number of network connections. The Dispatcher is the component that provides connectivity through shared memory to nodes on the same machine or cloud instance.</li>
<li style="margin-top: 15px;"><strong>Dual (UDP + TCP)</strong>: Two redundant connections, one TCP and one unicast UDP, streaming identical messages. The receiving side processes whichever arrives first and discards the duplicate.</li>
<li style="margin-top: 15px;"><strong>Fuse (reliable UDP)</strong>: One unicast UDP connection streaming messages, along with an idle TCP connection used for retransmission of lost messages. The receiving side requests retransmission of any gaps through the TCP connection.</li>
</ul>
<p style="margin-top: 35px;">
<h3>Conclusion</h3>
<p>
CoralSequencer is designed to adapt to the infrastructure you have, rather than forcing you into a single network model. Whether your deployment is fully on premises, fully in the cloud, or split across both, you can combine multicast and unicast UDP, TCP, shared memory, bridges, and dispatchers to build a topology that matches your performance, reliability, high availability, and operational requirements. Most importantly, these transport choices do not require application code changes, enabling incremental evolution of your infrastructure while preserving deterministic behavior and message ordering across the system.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/on-premises-and-cloud-infrastructure-with-coralsequencer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HotSpot, JIT, AOT and Warm-Up</title>
		<link>https://www.coralblocks.com/index.php/hotspot-jit-aot-and-warm-up/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=hotspot-jit-aot-and-warm-up</link>
		<comments>https://www.coralblocks.com/index.php/hotspot-jit-aot-and-warm-up/#comments</comments>
		<pubDate>Mon, 04 Nov 2024 16:09:32 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[aot]]></category>
		<category><![CDATA[jit]]></category>
		<category><![CDATA[latency]]></category>
		<category><![CDATA[leyden]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[readynow]]></category>
		<category><![CDATA[warm-up]]></category>

		<guid isPermaLink="false">https://www.coralblocks.com/index.php/?p=3047</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<style>
* {
font-size: 101%;
}</p>
<p>.li_facts { margin: 0 0 17px 0; }</p>
</style>
<p style="margin-top: 20px;">
The HotSpot JVM takes some time to profile a running Java application for hot spots in the code and then optimizes by compiling (to assembly) and inlining (when possible) these hot spot methods. That&#8217;s great because the JIT (just-in-time) compiler can surgically and aggressively optimize the parts of your application that matter the most instead of taking the AOT (ahead-of-time) approach of compiling and trying to optimize the whole thing beforehand. <span id="more-3047"></span> For example, method inlining is an aggressive form of optimization that usually requires runtime profiling information since inlining everything is impractical/impossible.
</p>
<p style="margin-top: 20px; margin-bottom: 20px;">
In this article we explore the JVM option <code>-Xcomp -XX:-TieredCompilation</code> to compile every method right before its first invocation. The drawback is that without any profiling this optimization can be conservative. For example, even though some basic method inlining is still performed, a more aggressive inlining approach can not happen without runtime profiling. The advantage is that your application will be able to perform at a native/assembly level right when it starts (even if not with the most optimized code) without having to wait until the HotSpot JVM has gathered enough profiling to compile and optimize the hot methods.
</p>
<p style="margin-top: 20px; margin-bottom: 20px;">
We also explore <a href="https://docs.azul.com/prime/Use-ReadyNow" target="_blank">Azul Zing ReadyNow</a>, which allows the profiling information to be saved from a previous run and re-applied on startup to improve the warm-up time.
</p>
<p style="margin-top: 20px; margin-bottom: 20px;">
Finally we conclude by talking a bit about <a href="https://openjdk.org/projects/leyden/" target="_blank">Project Leyden</a> from Oracle.
</p>
<h2>CoralSequencer with -Xcomp -XX:-TieredCompilation</h2>
<p style="margin-bottom: 25px; margin-top: 20px;">
Below we explore the difference in performance that <code>-Xcomp -XX:-TieredCompilation</code> makes for the CoralSequencer benchmark latency numbers.
</p>
<h4>Benchmark Environment</h4>
<pre>
$ java -version
java version "21.0.1" 2023-10-17 LTS
Java(TM) SE Runtime Environment (build 21.0.1+12-LTS-29)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.1+12-LTS-29, mixed mode, sharing)

$ uname -a
Linux hivelocity 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/issue | head -n 1
Ubuntu 18.04.6 LTS \n \l

$ cat /proc/cpuinfo | grep "model name" | head -n 1 | awk -F ": " '{print $NF}'
Intel(R) Xeon(R) E-2288G CPU @ 3.70GHz
</pre>
<h4 style="margin-top: 25px;">Regular JIT <i>with</i> warm-up</h4>
<pre>
Iterations: 1,000 | Avg Time: <font color="blue">4.372 micros</font> | Min Time: 3.634 micros | Max Time: 137.246 micros | 75% = [avg: 3.958 micros, max: 4.391 micros] | 90% = [avg: 4.035 micros, max: 4.474 micros] | 99% = [avg: 4.127 micros, max: 6.152 micros] | 99.9% = [avg: 4.239 micros, max: 22.849 micros] | 99.99% = [avg: 4.372 micros, max: 137.246 micros] | 99.999% = [avg: 4.372 micros, max: 137.246 micros]
</pre>
<h4 style="margin-top: 25px;">Regular JIT <i>without</i> warm-up</h4>
<pre>
Iterations: 1,000 | Avg Time: 58.026 micros | Min Time: 26.206 micros | Max Time: 2.809 millis | 75% = [avg: 36.13 micros, max: 52.503 micros] | 90% = [avg: 40.976 micros, max: 80.534 micros] | 99% = [avg: 46.905 micros, max: 324.93 micros] | 99.9% = [avg: 55.272 micros, max: 2.23 millis] | 99.99% = [avg: 58.026 micros, max: 2.809 millis] | 99.999% = [avg: 58.026 micros, max: 2.809 millis]
</pre>
<h4 style="margin-top: 25px;">-Xcomp -XX:-TieredCompilation <i>with</i> warm-up</h4>
<pre>
Iterations: 1,000 | Avg Time: 6.803 micros | Min Time: 5.741 micros | Max Time: 97.289 micros | 75% = [avg: 6.443 micros, max: 6.737 micros] | 90% = [avg: 6.499 micros, max: 6.88 micros] | 99% = [avg: 6.6 micros, max: 10.323 micros] | 99.9% = [avg: 6.712 micros, max: 24.872 micros] | 99.99% = [avg: 6.802 micros, max: 97.289 micros] | 99.999% = [avg: 6.802 micros, max: 97.289 micros]
</pre>
<h4 style="margin-top: 25px;">-Xcomp -XX:-TieredCompilation <i>without</i> warm-up</h4>
<pre>
Iterations: 1,000 | Avg Time: <font color="blue">7.005 micros</font> | Min Time: 6.029 micros | Max Time: 126.315 micros | 75% = [avg: 6.545 micros, max: 6.994 micros] | 90% = [avg: 6.625 micros, max: 7.084 micros] | 99% = [avg: 6.737 micros, max: 11.505 micros] | 99.9% = [avg: 6.885 micros, max: 47.461 micros] | 99.99% = [avg: 7.005 micros, max: 126.315 micros] | 99.999% = [avg: 7.005 micros, max: 126.315 micros]
</pre>
<p style="margin-top: 25px; margin-bottom: 20px;">
As you can see from the latency numbers above, by using <code>-Xcomp -XX:-TieredCompilation</code> we can mitigate the warm-up time by paying a price in performance (average <font color="blue">7.005 micros</font> over average <font color="blue">4.372 micros</font>). <b>It emphasizes the advantages of runtime information (i.e. profiling) when it comes to the critical path optimizations performed by the HotSpot JIT compiler.</b> Without profiling, there is only so much an AOT compiler can do, and the most aggressive optimizations might not be able to be applied beforehand. Of course this conclusion cannot be generalized for every application as it will depend heavily on the characteristics and particularities of the source code and its critical path.
</p>
<h2>CoralSequencer with Azul Zing ReadyNow</h2>
<p style="margin-bottom: 25px; margin-top: 20px;">
We performed three training runs of our application to record three generations of the ReadyNow profile log as instructed by the <a href="https://docs.azul.com/prime/Use-ReadyNow-Training" target="_blank">ReadyNow guide</a>. On each training run we performed 3 million iterations of the critical path. The size of the final profile log was 4.5 megabytes.
</p>
<h4>Benchmark Environment</h4>
<pre>
$ java -version
openjdk version "21.0.4" 2024-09-27 LTS
OpenJDK Runtime Environment Zing24.09.0.0+5 (build 21.0.4+4-LTS)
Zing 64-Bit Tiered VM Zing24.09.0.0+5 (build 21.0.4-zing_24.09.0.0-b5-release-linux-X86_64, mixed mode)

$ uname -a
Linux hivelocity 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/issue | head -n 1
Ubuntu 18.04.6 LTS \n \l

$ cat /proc/cpuinfo | grep "model name" | head -n 1 | awk -F ": " '{print $NF}'
Intel(R) Xeon(R) E-2288G CPU @ 3.70GHz
</pre>
<h4 style="margin-top: 25px;">Regular Zing JIT <i>with</i> warm-up</h4>
<pre>
Iterations: 1,000 | Avg Time: <font color="#203fad">4.175 micros</font> | Min Time: <font color="#203fad">3.317 micros</font> | Max Time: 90.359 micros | 75% = [avg: 3.841 micros, max: 4.095 micros] | 90% = [avg: 3.923 micros, max: 4.418 micros] | 99% = [avg: 3.997 micros, max: 5.768 micros] | 99.9% = [avg: 4.088 micros, max: 20.067 micros] | 99.99% = [avg: 4.175 micros, max: 90.359 micros] | 99.999% = [avg: 4.175 micros, max: 90.359 micros]
</pre>
<h4 style="margin-top: 25px;">Regular Zing JIT <i>without</i> warm-up</h4>
<pre>
Iterations: 1,000 | Avg Time: <font color="blue">55.314 micros</font> | Min Time: 21.089 micros | Max Time: 5.628 millis | 75% = [avg: 33.077 micros, max: 53.887 micros] | 90% = [avg: 38.055 micros, max: 75.808 micros] | 99% = [avg: 43.08 micros, max: 133.613 micros] | 99.9% = [avg: 49.735 micros, max: <font color="blue">2.563 millis</font>] | 99.99% = [avg: 55.314 micros, max: 5.628 millis] | 99.999% = [avg: 55.314 micros, max: 5.628 millis]
</pre>
<h4 style="margin-top: 25px;">Zing ReadyNow <i>with</i> warm-up</h4>
<pre>
Iterations: 1,000 | Avg Time: <font color="#203fad">4.273 micros</font> | Min Time: <font color="#203fad">3.396 micros</font> | Max Time: 94.648 micros | 75% = [avg: 3.905 micros, max: 4.126 micros] | 90% = [avg: 3.987 micros, max: 4.501 micros] | 99% = [avg: 4.066 micros, max: 6.042 micros] | 99.9% = [avg: 4.182 micros, max: 21.433 micros] | 99.99% = [avg: 4.272 micros, max: 94.648 micros] | 99.999% = [avg: 4.272 micros, max: 94.648 micros]
</pre>
<h4 style="margin-top: 25px;">Zing ReadyNow <i>without</i> warm-up</h4>
<pre>
Iterations: 1,000 | Avg Time: <font color="blue">29.47 micros</font> | Min Time: 18.966 micros | Max Time: 279.449 micros | 75% = [avg: 24.666 micros, max: 32.724 micros] | 90% = [avg: 26.66 micros, max: 42.978 micros] | 99% = [avg: 28.679 micros, max: 71.379 micros] | 99.9% = [avg: 29.219 micros, max: <font color="blue">141.065 micros</font>] | 99.99% = [avg: 29.469 micros, max: 279.449 micros] | 99.999% = [avg: 29.469 micros, max: 279.449 micros]
</pre>
<p style="margin-top: 25px; margin-bottom: 20px;">
As you can see from the latency numbers above, by using <code>ReadyNow</code> we were able to obtain a 50% improvement in performance without warm-up (average <font color="blue">29.470 micros</font> over average <font color="blue">55.314 micros</font>). We were also able to limit the outliers above the 99.9 percentile (max <font color="blue">141.065 micros</font> over max <font color="blue">2.563 millis</font>) before warming up. After warming up, the results with ReadyNow were similar to the results without ReadyNow (average <font color="#203fad">4.273 micros</font> over average <font color="#203fad">4.175 micros</font> and min <font color="#203fad">3.396 micros</font> over min <font color="#203fad">3.317 micros</font>).
</p>
<h2>Project Leyden from Oracle</h2>
<p style="margin-bottom: 18px; margin-top: 20px;">
At the time of this article (Nov/2024) <a href="https://openjdk.org/projects/leyden/" target="_blank">Project Leyden</a> from Oracle is fairly new (May/2022) but it has been making some great progress in the Java warm-up area. Our opinion, based on our own experiments with <a href="https://www.coralblocks.com/coralsequencer" target="_blank">CoralSequencer</a> and <a href="https://www.graalvm.org/" target="_blank">GraalVM</a>, is that AOT is not as fast as JIT (after the code has warmed up) so real-time and past-time (archived) profiling information becomes crucial for achieving AOT + JIT maximum performance together with minimum time-to-peak (i.e. quick warm-up). It is important to emphasize that this AOT x JIT conclusion is targeted to CoralSequencer in particular and cannot be generalized for every application as it will depend heavily on the characteristics of the source code and its critical path. Also it is important to clarify that by <i>fast</i> we mean the ability to accomplish the lowest possible latency for the critical path. We are <i>not</i> referring to throughput, JVM start-up time and application start-up time.
</p>
<p>
That said, we are particularly excited about the following JEPs:
</p>
<ul>
<li style="margin-bottom: 18px;">
<a href="https://openjdk.org/jeps/8325147" target="_blank">JEP draft 8325147</a>: <i>Ahead-of-Time Method Profiling</i> => Method profiles from training runs are stored in the CDS archive, thereby enabling the JIT to begin compiling earlier during warmup. As a result, Java applications can reach peak performance faster. This feature is enabled by the VM flags <code>-XX:+RecordTraining</code> and <code>-XX:+ReplayTraining</code>.
</li>
<li>
<a href="https://openjdk.org/jeps/8335368" target="_blank">JEP draft 8335368</a>: <i>Ahead-of-Time Code Compilation</i> => Methods that are frequently used during the training run can be compiled and stored along with the CDS archive. As a result, as soon as the application starts up in the production run, its methods can be can be natively executed. This feature is enabled by the VM flags <code>-XX:+StoreCachedCode</code>, <code>-XX:+LoadCachedCode</code>, and <code>-XX:CachedCodeFile</code>.
</li>
</ul>
<p style="margin-bottom: 25px; margin-top: 25px;">
<font color="#2449c1">We are currently working to be able to test CoralSequencer with the <a href="https://jdk.java.net/leyden/" target="_blank">early-access builds of Project Leyden</a> and we&#8217;ll report our findings soon.</font></p>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/hotspot-jit-aot-and-warm-up/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Exploring the Sequencer Architecture through our SimSequencer</title>
		<link>https://www.coralblocks.com/index.php/exploring-the-sequencer-architecture-through-our-simsequencer/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=exploring-the-sequencer-architecture-through-our-simsequencer</link>
		<comments>https://www.coralblocks.com/index.php/exploring-the-sequencer-architecture-through-our-simsequencer/#comments</comments>
		<pubDate>Thu, 08 Feb 2024 02:33:09 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[Sequencer]]></category>
		<category><![CDATA[SimSequencer]]></category>
		<category><![CDATA[simulator]]></category>

		<guid isPermaLink="false">https://www.coralblocks.com/index.php/?p=2986</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<style>
* {
font-size: 101%;
}</p>
<p>.li_facts { margin: 0 0 17px 0; }</p>
</style>
<p style="margin-top: 20px;">
SimSequencer is a framework that lets you simulate through code the majority of the aspects of the sequencer architecture <b><i>without any networking involved</i></b>. It can be very useful for prototyping, testing and learning purposes. With SimSequencer you can code your nodes to test all the moving parts of your application as they were running and interacting in a real distributed system. And it has the same API of CoralSequencer. <span id="more-2986"></span>
</p>
<p style="margin-top: 30px;">
Some of the main premises of the sequencer architecture are:
</p>
<ul>
<li style="padding: 5px; margin: 5px 0; color: darkblue;"><em>All nodes receive all messages in the exact same order, always</em></li>
<li style="padding: 5px; margin: 5px 0; color: darkblue;"><em>Nodes become deterministic finite-state machines</em></li>
<li style="padding: 5px; margin: 5px 0; color: darkblue;"><em>Clusters for high-availability and failover become trivial</em></li>
</ul>
<p style="margin-top: 30px;">
You should also check <a href="https://www.youtube.com/watch?v=DyktSiBTCdk" target="_blank">the YouTube video below</a> where we present the main characteristics of the sequencer architecture together with some advanced features of CoralSequencer. <b>Note:</b> Contrary to the YouTube video, the video below has <font color="red"><b>no ads</b></font>.<br />
<center><br />
<!-- iframe width="560" height="315" src="https://www.youtube.com/embed/DyktSiBTCdk?si=Rp-mN6kQYQ9PAkIX" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe --><br />
<div style="width: 600px; max-width: 100%;" class="wp-video"><video class="wp-video-shortcode" id="video-2986-2" width="600" height="338" preload="metadata" controls="controls"><source type="video/mp4" src="/wp-content/uploads/videos/CoralSequencer.mp4?_=2" /><a href="/wp-content/uploads/videos/CoralSequencer.mp4">/wp-content/uploads/videos/CoralSequencer.mp4</a></video></div><br />
</center>
</p>
<p><br/></p>
<p>
Below we demonstrate the sequencer architecture main premises through working SimSequencer code:
</p>
<p><br/></p>
<h4 class="coral"><b><i>All nodes receive all messages in the exact same order, always</i></b></h4>
<p><br/></p>
<pre class="brush: java; title: ; notranslate">
  Sequencer sequencer = new PassThroughSequencer(&quot;SEQ&quot;);
  
  final List&lt;Message&gt; messages1 = new LinkedList&lt;Message&gt;();
  final List&lt;Message&gt; messages2 = new LinkedList&lt;Message&gt;();
  
  Node node1 = new Node(&quot;NODE1&quot;) {
    @Override
    protected void handleMessage(boolean isMine, Message msg) {
      messages1.add(msg);
    }
  };
  
  Node node2 = new Node(&quot;NODE2&quot;) {
    @Override
    protected void handleMessage(boolean isMine, Message msg) {
      messages2.add(msg);
    }
  };
  
  sequencer.addNode(node1, node2);
  
  sequencer.open().activate();
  node1.open().activate();
  node2.open().activate();
  
  Random rand = new Random();
  
  final int messagesToSend = 200;
  
  for(int i = 0; i &lt; messagesToSend; i++) {
    Node n = rand.nextBoolean() ? node1 : node2;
    n.sendCommand(&quot;Hi&quot; + rand.nextInt(1000));
  }
  
  Assert.assertEquals(messagesToSend, messages1.size());
  Assert.assertEquals(messagesToSend, messages2.size());
  
  Iterator&lt;Message&gt; iter1 = messages1.iterator();
  Iterator&lt;Message&gt; iter2 = messages2.iterator();
  
  while(iter1.hasNext() &amp;&amp; iter2.hasNext()) {
    Message m1 = iter1.next();
    Message m2 = iter2.next();
    Assert.assertTrue(m1.isEqualTo(m2));
  }
  
  node1.close();
  node2.close();
  sequencer.close();
</pre>
<p><br/></p>
<h4 class="coral"><b><i>Nodes become deterministic finite-state machines</i></b></h4>
<p><br/></p>
<pre class="brush: java; title: ; notranslate">
import static com.coralblocks.simsequencer.util.Log.*;

import com.coralblocks.simsequencer.Message;
import com.coralblocks.simsequencer.Node;

public class CounterNode extends Node {
	
	private long counter;

	public CounterNode(String name) {
		super(name);
	}
	
	@Override
	protected void handleOpened() {
		this.counter = 1;
		Info.log(name, &quot;Counter was reset to 1&quot;);
	}
	
	public long getCounter() {
		return counter;
	}
	
	@Override
	protected void handleMessage(boolean isMine, Message msg) {
		
		String[] tokens = msg.getDataAsString().split(&quot;\\|&quot;);
		
		String type = tokens[0];
		
		if (type.equals(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;)) {
			counter += currentSequencerTime();
		} else if (type.equals(&quot;ADD_VALUE&quot;)) {
			counter += Integer.parseInt(tokens[1]);
		}
	}
}
</pre>
<pre class="brush: java; title: ; notranslate">
  Sequencer sequencer = new PassThroughSequencer(&quot;SEQ&quot;);
  
  CounterNode node = new CounterNode(&quot;NODE1&quot;);
  
  sequencer.addNode(node);

  sequencer.open().activate();
  node.open().activate();
  
  Random rand = new Random();

  final int messagesToSend = 200;

  for(int i = 0; i &lt; messagesToSend; i++) {
      int type = rand.nextInt(2);
      if (type == 0) {
        node.sendCommand(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;);
      } else if (type == 1) {
        node.sendCommand(&quot;ADD_VALUE|&quot; + rand.nextInt(300000));
      }
  }
  
  final long counter = node.getCounter();

  for(int i = 0; i &lt; 5; i++) {
    node.close();
    node.open(); // resets the counter to 1...
    // node then rewinds, receiving all messages again...
    Assert.assertEquals(counter, node.getCounter());
  }

  node.close();
  sequencer.close();
</pre>
<p><br/></p>
<h4 class="coral"><b><i>Clusters for high-availability and failover become trivial</i></b></h4>
<p><br/></p>
<pre class="brush: java; title: ; notranslate">
  // Hot-Warm (Active-Passive) Cluster
  
  Sequencer sequencer = new PassThroughSequencer(&quot;SEQ&quot;);
  
  // Two nodes with the same account &quot;NODE1&quot; for a cluster
  CounterNode nodeA = new CounterNode(&quot;NODE1&quot;);
  CounterNode nodeB = new CounterNode(&quot;NODE1&quot;);
  
  sequencer.addNode(nodeA, nodeB);
  
  sequencer.open().activate();
  
  nodeA.open().activate(); // hot (active)
  nodeB.open(); // warm (passive)

  Random rand = new Random();

  final int messagesToSend = 200;

  for(int i = 0; i &lt; messagesToSend; i++) {
    int type = rand.nextInt(2);
    if (type == 0) {
      nodeA.sendCommand(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;);
    } else if (type == 1) {
      nodeA.sendCommand(&quot;ADD_VALUE|&quot; + rand.nextInt(300000));
    }
  }
  
  Assert.assertEquals(nodeA.getCounter(), nodeB.getCounter());
  
  // fail over to the warm node
  nodeA.deactivate(); // now it is warm
  nodeB.activate(); // now it is hot
  
  for(int i = 0; i &lt; messagesToSend; i++) {
    int type = rand.nextInt(2);
    if (type == 0) {
      nodeB.sendCommand(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;);
    } else if (type == 1) {
      nodeB.sendCommand(&quot;ADD_VALUE|&quot; + rand.nextInt(300000));
    }
  }
  
  Assert.assertEquals(nodeA.getCounter(), nodeB.getCounter());

  nodeA.close();
  nodeB.close();
  sequencer.close();
</pre>
<pre class="brush: java; title: ; notranslate">
  // Hot-Hot (Active-Active) Cluster

  Sequencer sequencer = new PassThroughSequencer(&quot;SEQ&quot;);

  // Two nodes with the same account &quot;NODE1&quot; for a cluster
  CounterNode nodeA = new CounterNode(&quot;NODE1&quot;);
  CounterNode nodeB = new CounterNode(&quot;NODE1&quot;);
  
  sequencer.addNode(nodeA, nodeB);
  
  sequencer.open().activate();

  nodeA.open().activate(); // hot (active)
  nodeB.open().activate(); // hot (active)
  
  Random rand = new Random();

  final int messagesToSend = 200;
  
  for(int i = 0; i &lt; messagesToSend; i++) {
    int type = rand.nextInt(2);
    if (type == 0) {
      if (rand.nextBoolean()) { // order does not matter
        nodeA.sendCommand(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;);
        nodeB.sendCommand(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;);
      } else {
        nodeB.sendCommand(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;);
        nodeA.sendCommand(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;);
      }
    } else if (type == 1) {
      int value = rand.nextInt(300000);
      if (rand.nextBoolean()) { // order does not matter
        nodeA.sendCommand(&quot;ADD_VALUE|&quot; + value);
        nodeB.sendCommand(&quot;ADD_VALUE|&quot; + value);
      } else {
        nodeB.sendCommand(&quot;ADD_VALUE|&quot; + value);
        nodeA.sendCommand(&quot;ADD_VALUE|&quot; + value);
      }
    }
  }

  Assert.assertEquals(nodeA.getCounter(), nodeB.getCounter());

  // pull the plug from one of the nodes
  nodeA.close(); // now it is dead
  nodeA = null;

  for(int i = 0; i &lt; messagesToSend; i++) {
    int type = rand.nextInt(2);
    if (type == 0) {
      nodeB.sendCommand(&quot;ADD_DETERMINISTIC_TIMESTAMP&quot;);
    } else if (type == 1) {
      nodeB.sendCommand(&quot;ADD_VALUE|&quot; + rand.nextInt(300000));
    }
  }

  // bring another node to the cluster...
  CounterNode nodeC = new CounterNode(&quot;NODE1&quot;);
  sequencer.addNode(nodeC);
  nodeC.open().activate(); // hot (active)

  Assert.assertEquals(nodeB.getCounter(), nodeC.getCounter());

  nodeB.close();
  nodeC.close();
  sequencer.close();
</pre>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/exploring-the-sequencer-architecture-through-our-simsequencer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Writing a C++ CoralSequencer Node</title>
		<link>https://www.coralblocks.com/index.php/writing-a-c-coralsequencer-node/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=writing-a-c-coralsequencer-node</link>
		<comments>https://www.coralblocks.com/index.php/writing-a-c-coralsequencer-node/#comments</comments>
		<pubDate>Wed, 07 Feb 2024 11:43:06 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[c]]></category>
		<category><![CDATA[jni]]></category>
		<category><![CDATA[native]]></category>
		<category><![CDATA[node]]></category>
		<category><![CDATA[Sequencer]]></category>

		<guid isPermaLink="false">https://www.coralblocks.com/index.php/?p=2942</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<style>
* {
font-size: 101%;
}</p>
<p>.li_facts { margin: 0 0 17px 0; }
</style>
<p>
Writing some C++ code that gets called by your Java code is trivial through shared libraries but a more interesting project is to do the inverse: <strong>to call Java code from a C++ system</strong>. In this article we write a C++ CoralSequencer node to perform a latency benchmark test. <span id="more-2942"></span>
</p>
<p><br/></p>
<h3 class="coral">Overview</h3>
<p>
To effectively call Java from C++, your C++ code must instantiate and start a Java Virtual Machine to execute the Java code. The same JVM will then call your C++ system back through a shared library. The goal is to allow any C++ subsystem to directly interact with the CoralSequencer distributed system.
</p>
<p><br/></p>
<h3 class="coral">The Code</h3>
<p>
Below a simple CNode CoralSequencer node that your C++ system can use to receive a callback with the CoralSequencer event-stream message.
</p>
<pre class="brush: java; title: ; notranslate">
public class CNode extends Node {
	
	static {
        	System.loadLibrary(&quot;CNode&quot;); // the shared library used to send the callback to C++
	}

	public CNode(NioReactor nio, String name, Configuration config) {
		super(nio, name, config);
	}
	
	public native void handleMessageC(boolean isMine, Message msg);
	
	@Override
	protected void handleMessage(boolean isMine, Message msg) {
		handleMessageC(isMine, msg); // call C++
	}

}
</pre>
<p>
To run this node we use the simple mq file below: (the file path is ./mqs/cnode.mq)
</p>
<pre class="brush: plain; title: ; notranslate">
VM addAdmin telnet 57
VM newNode NODE7 com.coralblocks.coralsequencer.mq.CNode
NODE7 open
NODE7 activate
</pre>
<p>Now we write the C++ code to instantiate a JVM and run this mq file to start our CNode.</p>
<pre class="brush: cpp; title: ; notranslate">
    #include &lt;jni.h&gt;
    // Many other includes here (omitted for clarity)
    #include &quot;com_coralblocks_coralsequencer_mq_CNode.h&quot;
    using namespace std;

    static const int ITERATIONS = 2000000;
    static const int WARMUP = 1000000;
    static const int MSG_SIZE = 256;

    long get_nano_ts(timespec* ts) {
        clock_gettime(CLOCK_MONOTONIC, ts);
        return ts-&gt;tv_sec * 1000000000 + ts-&gt;tv_nsec;
    }

    struct mi {
        long value;
    };

    void add_perc(stringstream&amp; ss, int size, double perc, map&lt;int, mi*&gt;* map) {
    
        // omitted for clarity
    }

    char* createRandomCharArray(int size) {
        // omitted for clarity
    }

    int main(int argc, char **argv) {

        JavaVM *jvm;                       // Pointer to the JVM (Java Virtual Machine)
        JNIEnv *env;                       // Pointer to native interface
        JavaVMInitArgs vm_args;            // JVM initialization arguments
        JavaVMOption options[24];          // JVM options

        // add the 25 JVM options here (omitted for clarity)
        
        vm_args.version = JNI_VERSION_1_6;                      // Set the JNI version
        vm_args.nOptions = 24;                                  // Set the number of options
        vm_args.options = options;                              // Set the options to the JVM
        
        // Load and initialize the JVM
        JNI_CreateJavaVM(&amp;jvm, (void**)&amp;env, &amp;vm_args);

        cout &lt;&lt; &quot;JVM created!!!&quot; &lt;&lt; endl;

        jvm-&gt;AttachCurrentThread((void**)&amp;env, NULL);

        jclass startJavaClass = env-&gt;FindClass(&quot;com/coralblocks/coralsequencer/Start&quot;);
        jmethodID findAppMethod = env-&gt;GetStaticMethodID(startJavaClass, &quot;findApplication&quot;, &quot;(Ljava/lang/String;)Lcom/coralblocks/coralsequencer/app/Application;&quot;);
        jclass nodeClass = env-&gt;FindClass(&quot;com/coralblocks/coralsequencer/mq/CNode&quot;);
        jmethodID sendCommandMethod = env-&gt;GetMethodID(nodeClass, &quot;sendCommand&quot;, &quot;(Ljava/lang/CharSequence;)Z&quot;);

        jstring str1 = env-&gt;NewStringUTF(&quot;mqs/cnode.mq&quot;);
        jclass stringClass = env-&gt;FindClass(&quot;java/lang/String&quot;);
        jobject args = env-&gt;NewObjectArray(1, stringClass, str1);
        jmethodID mainMethod = env-&gt;GetStaticMethodID(startJavaClass, &quot;main&quot;, &quot;([Ljava/lang/String;)V&quot;);
        jmethodID isActiveMethod = env-&gt;GetMethodID(nodeClass, &quot;isActive&quot;, &quot;()Z&quot;);

        cout &lt;&lt; &quot;About to call Java main method...&quot; &lt;&lt; endl;

        env-&gt;CallStaticVoidMethod(startJavaClass, mainMethod, args);

        cout &lt;&lt; &quot;Returned from Java main method!&quot; &lt;&lt; endl;

        // Get the node (NODE7)
        jobject node = env-&gt;CallStaticObjectMethod(startJavaClass, findAppMethod, env-&gt;NewStringUTF(&quot;NODE7&quot;));

        cout &lt;&lt; &quot;Waiting for node to become active...&quot; &lt;&lt; endl;

        // Sleep until node becomes active
        while(env-&gt;CallBooleanMethod(node, isActiveMethod) == JNI_FALSE) sleep(1);

        cout &lt;&lt; &quot;isActive() returned true!&quot; &lt;&lt; endl;

        jstring msgToSend = env-&gt;NewStringUTF(createRandomCharArray(MSG_SIZE));

        cout &lt;&lt; &quot;About to send first message!&quot; &lt;&lt; endl;

        env-&gt;CallObjectMethod(node, sendCommandMethod, msgToSend);

        cout &lt;&lt; &quot;First message sent!&quot; &lt;&lt; endl;

        jvm-&gt;DetachCurrentThread();

        // Release the JVM
        jvm-&gt;DestroyJavaVM(); // this will wait for Java threads to die...

        cout &lt;&lt; &quot;JVM Destroyed!!!&quot; &lt;&lt; endl;

        return 0;
    }

    struct timespec ts;
    long startTime = 0;
    long endTime = 0;
    map&lt;int, mi*&gt;* results;
    int iterations = 0;

    jobject node;
    jmethodID sendCommandMethod;
    jmethodID isRewindingMethod;
    jstring msgToSend;

    JNIEXPORT void JNICALL Java_com_coralblocks_coralsequencer_mq_CNode_handleMessageC
    (JNIEnv *env, jobject obj, jboolean isMine, jobject msg) {

        endTime = get_nano_ts(&amp;ts);

        if (node == NULL) {
            jclass startJavaClass = env-&gt;FindClass(&quot;com/coralblocks/coralsequencer/Start&quot;);
            jmethodID findAppMethod = env-&gt;GetStaticMethodID(startJavaClass, &quot;findApplication&quot;, &quot;(Ljava/lang/String;)Lcom/coralblocks/coralsequencer/app/Application;&quot;);
            jclass nodeClass = env-&gt;FindClass(&quot;com/coralblocks/coralsequencer/mq/CNode&quot;);
            sendCommandMethod = env-&gt;GetMethodID(nodeClass, &quot;sendCommand&quot;, &quot;(Ljava/lang/CharSequence;)Z&quot;);
            isRewindingMethod = env-&gt;GetMethodID(nodeClass, &quot;isRewinding&quot;, &quot;()Z&quot;);

            node = env-&gt;CallStaticObjectMethod(startJavaClass, findAppMethod, env-&gt;NewStringUTF(&quot;NODE7&quot;));

            node = env-&gt;NewGlobalRef(node);

            msgToSend = env-&gt;NewStringUTF(createRandomCharArray(MSG_SIZE));

            results = new map&lt;int, mi*&gt;();
        }

        if (env-&gt;CallBooleanMethod(node, isRewindingMethod) == JNI_TRUE) return;

        int res = startTime &gt; 0 ? (endTime - startTime) : 1; // 1 only for first message/pass

        if (res &lt;= 0) res = 1;

        if (iterations++ &gt;= WARMUP) {
            
            // add the result (omitted for clarity)
        }

        if (iterations == ITERATIONS) {

            // print the results (omitted for clarity) 

        } else {

            startTime = get_nano_ts(&amp;ts);

            env-&gt;CallObjectMethod(node, sendCommandMethod, msgToSend);

        }

    }
</pre>
<p><strong>NOTE:</strong> The full source code can be seen <a href="https://gist.github.com/coralblocks/0c9a7ce73bc345a318ecace42b285fe9" target="_blank">here</a>.</p>
<p>
The trick is to compile this C++ code <strong>twice</strong>: as the main C++ program to be executed (the one that will start the JVM) and as the shared library that will be used by CNode.java (the one that will receive the callback):</p>
<pre class="brush: plain; title: ; notranslate">
# Using Java 21 and clang 14.0.6

# Compile the main C++ program to start the JVM
clang++ -I&quot;$JAVA_HOME/include&quot; -I&quot;$JAVA_HOME/include/linux&quot; -o bin/linux/Bench src/main/c/linux/Bench.cpp -L&quot;$JAVA_HOME/lib/server&quot; -ljvm -Wno-write-strings

# Generate the com_coralblocks_coralsequencer_mq_CNode.h header file
javac -h src/main/c/linux -d target/classes -sourcepath src/main/java -cp target/coralsequencer-all.jar src/main/java/com/coralblocks/coralsequencer/mq/CNode.java

# Compile the shared library to be used by CNode.java
clang++ -shared -fPIC -I&quot;$JAVA_HOME/include&quot; -I&quot;$JAVA_HOME/include/linux/&quot; src/main/c/linux/Bench.cpp -o lib/libCNode.so -L&quot;$JAVA_HOME/lib/server&quot; -ljvm -Wno-write-strings
</pre>
<p>Now when we execute our C++ application with the command-line below:</p>
<pre class="brush: plain; title: ; notranslate">
$ LD_LIBRARY_PATH=$JAVA_HOME/lib/server ./bin/linux/Bench
</pre>
<p>We get the following latency benchmark results:</p>
<pre>
Message Size: 256 bytes
Messages: 1,000,000
Avg Time: <font color="blue"><strong>4.808 micros</strong></font>
Min Time: 3.754 micros
Max Time: 717.729 micros
75% = [avg: 4.522 micros, max: 5.055 micros]
90% = [avg: 4.618 micros, max: 5.159 micros]
99% = [avg: 4.681 micros, max: 6.339 micros]
99.9% = [avg: 4.717 micros, max: 20.547 micros]
99.99% = [avg: 4.773 micros, max: 268.823 micros]
99.999% = [avg: 4.803 micros, max: 427.988 micros]
</pre>
<p>As expected this is <strong>very close</strong> to our official CoralSequencer latency numbers, as described <a href="https://www.coralblocks.com/index.php/coralmq-performance-numbers/" target="_blank">here</a>.<br />
<br/></p>
<h3 class="coral">Conclusion</h3>
<p>
It is straightforward (but not trivial) to write C++ applications that use the CoralSequencer infra-structure to interact with your distributed system. The performance cost to cross the native to JVM border and back is very small as the benchmark results in this article demonstrate.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/writing-a-c-coralsequencer-node/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Performance Analysis: comparing C++ and Java</title>
		<link>https://www.coralblocks.com/index.php/performance-a-c-and-java-comparison/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=performance-a-c-and-java-comparison</link>
		<comments>https://www.coralblocks.com/index.php/performance-a-c-and-java-comparison/#comments</comments>
		<pubDate>Thu, 23 Jun 2022 19:40:12 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralBits]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[c]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[performance]]></category>

		<guid isPermaLink="false">https://www.coralblocks.com/index.php/?p=2831</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>
In this article we write two equivalent programs in C++ and in Java, in exactly the same way to do exactly the same thing: the (in)famous bubble sort algorithm. Then we proceed to measure the latency. On this experiment, Java was faster than C++ even with the <code>-O3</code> compiler option.<span id="more-2831"></span>
</p>
<p>
<strong>Note:</strong> We used the same isolated cpu core for all tests through thread pinning.
</p>
<h3 class="coral">Java Version</h3>
<pre>
java version "17.0.1" 2021-10-19 LTS
Java(TM) SE Runtime Environment (build 17.0.1+12-LTS-39)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.1+12-LTS-39, mixed mode, sharing)
</pre>
<h3 class="coral">Java</h3>
<pre>
Iterations: 9,000,000
Avg Time: <font color="blue"><b>825.97</b> nanos</font>
StDev: 156.4 nanos
Min Time: <font color="blue">781 nanos</font>
Max Time: 107335 nanos
75% (6,750,000) = [avg: 801, stdev: 5.2, max: 876] - 25% (2,250,000) = [avg: 899, stdev: 301.03, min: 876]
90% (8,100,000) = [avg: 816, stdev: 32.78, max: 893] - 10% (900,000) = [avg: 915, stdev: 475.5, min: 893]
99% (8,910,000) = [avg: 823, stdev: 39.0, max: 904] - 1% (90,000) = [avg: 1077, stdev: 1493.9, min: 904]
<font color="blue">99.9% (8,991,000) = [avg: 824, stdev: 39.62, max: 915] - 0.1% (9,000) = [avg: 2610, stdev: 4439.05, min: 915]</font>
99.99% (8,999,100) = [avg: 824, stdev: 47.73, max: 13448] - 0.01% (900) = [avg: 15264, stdev: 3653.04, min: 13484]
99.999% (8,999,910) = [avg: 825, stdev: 141.2, max: 16186] - 0.001% (90) = [avg: 19503, stdev: 10169.29, min: 16187]
99.9999% (8,999,991) = [avg: 825, stdev: 149.83, max: 25498] - 0.0001% (9) = [avg: 38328, stdev: 24606.03, min: 25787]
99.99999% (8,999,999) = [avg: 825, stdev: 152.32, max: 34315] - 0.00001% (1) = [avg: 107335, stdev: 0.0, min: 107335]
</pre>
<h3 class="coral">C++ (-O3)</h3>
<pre>
Iterations: 9,000,000 
Avg Time: <font color="red"><b>1229</b> nanos</font>
Stdev: 157.94 nanos
Min Time: <font color="red">1141 nanos</font>
Max Time: 35318 nanos
75% (6,750,000) = [avg: 1183, stdev: 7.59, max: 1197] - 25% (2,250,000) = [avg: 1364, stdev: 273.80, min: 1197]
90% (8,100,000) = [avg: 1203, stdev: 63.25, max: 1431] - 10% (900,000) = [avg: 1458, stdev: 393.68, min: 1431]
99% (8,910,000) = [avg: 1225, stdev: 91.58, max: 1462] - 1% (90,000) = [avg: 1595, stdev: 1236.27, min: 1462]
<font color="red">99.9% (8,991,000) = [avg: 1227, stdev: 94.04, max: 1484] - 0.1% (9,000) = [avg: 2732, stdev: 3721.22, min: 1484]</font>
99.99% (8,999,100) = [avg: 1227, stdev: 95.25, max: 2305] - 0.01% (900) = [avg: 12314, stdev: 5985.26, min: 2305]
99.999% (8,999,910) = [avg: 1228, stdev: 146.17, max: 16629] - 0.001% (90) = [avg: 19763, stdev: 3783.30, min: 16631]
99.9999% (8,999,991) = [avg: 1229, stdev: 155.39, max: 23087] - 0.0001% (9) = [avg: 29166, stdev: 4156.72, min: 24677]
99.99999% (8,999,999) = [avg: 1229, stdev: 157.53, max: 34189] - 0.00001% (1) = [avg: 35318, stdev: 0.00, min: 35318]
</pre>
<h3 class="coral">Java Source Code</h3>
<pre class="brush: java; title: ; notranslate">
package com.coralblocks.coralthreads.sample;

import java.util.Arrays;

import com.coralblocks.coralbits.bench.Benchmarker;
import com.coralblocks.coralbits.util.OSUtils;
import com.coralblocks.coralbits.util.SystemUtils;
import com.coralblocks.coralthreads.Affinity;

public class TestPerformance {
	
	// java -server -verbose:gc -cp ./target/classes:./target/coralthreads-all.jar:coralthreads-all.jar -DcoralThreadsVerbose=false -DbenchWorstPercs=true -DbenchTotals=true -DbenchStdev=true -DbenchMorePercs=true -DdetailedBenchmarker=true -DprocToBind=1 -DexcludeNanoTimeCost=true com.coralblocks.coralthreads.sample.TestPerformance 10000000 1000000 60
	
	private static int[] HEAP_ARRAY;
	
	public static void main(String[] args) {
		
		int iterations = Integer.parseInt(args[0]);
		int warmup = Integer.parseInt(args[1]);
		int arraySize = Integer.parseInt(args[2]);
		int procToBind = SystemUtils.getInt(&quot;procToBind&quot;, -1);
		
		if (procToBind != -1 &amp;&amp; OSUtils.isLinux()) {
			Affinity.set(procToBind);
		}
		
		HEAP_ARRAY = new int[arraySize];
		
		Benchmarker bench = Benchmarker.create(warmup);
		
		long x = 0;
		
		for(int i = 0; i &lt; iterations; i++) {
			
			bench.mark();
			
			doSomething(HEAP_ARRAY, HEAP_ARRAY.length);
			
			bench.measure();
			
			for(int j = 0; j &lt; HEAP_ARRAY.length; j++) {
				x += HEAP_ARRAY[j];
			}
		}
		
		System.out.println(&quot;Value computed: &quot; + x);
		System.out.println(&quot;Array: &quot; + Arrays.toString(HEAP_ARRAY));
		bench.printResults();
	}
	
	private static void swapping(int[] array, int x, int y) {
		int temp = array[x];
		array[x] = array[y];
		array[y] = temp;
	}
	
	private static void bubbleSort(int[] array, int size) {
		for(int i = 0; i &lt; size; i++) {
			int swaps = 0; // flag to detect any swap is there or not
			for(int j = 0; j &lt; size - i - 1; j++) {
				if (array[j] &gt; array[j + 1]) { // when the current item is bigger than next
					swapping(array, j, j + 1);
					swaps = 1;
				}
			}
			if (swaps == 0) break; // No swap in this pass, so array is sorted
		}
	}
	
	/*
	 * For speed, it is important to extract the hot code (i.e. the code executed in a loop) to its own method so the JIT can inline/optimize/compile.
	 * 
	 * Note that the main() method above is executed only once.
	 */
	private final static void doSomething(int[] array, int size) {
		
		for(int z = 0; z &lt; size; z++) {
			array[z] = size - z;
		}

		bubbleSort(array, size);
	}
}
</pre>
<h3 class="coral">C++ Source Code</h3>
<pre class="brush: java; title: ; notranslate">
#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;random&gt;
#include &lt;cmath&gt;
#include &lt;algorithm&gt;
#include &lt;limits&gt;
#include &lt;sys/time.h&gt;
#include &lt;map&gt;
#include &lt;sched.h&gt;
#include &lt;sstream&gt;
#include &lt;iomanip&gt;

using namespace std;

// TO COMPILE: g++ TestPerformance.cpp -o TestPerformance -std=c++11 -O3
// TO EXECUTE: ./TestPerformance 10000000 1000000 60 1

static const bool MORE_PERCS = true;
static const bool INCLUDE_WORST_PERCS = true;
static const bool INCLUDE_TOTALS = true;
static const bool INCLUDE_RATIOS = false;
static const bool INCLUDE_STDEV = true;

static const bool EXCLUDE_NANO_TS_COST = true;

long get_nano_ts(timespec* ts) {
	clock_gettime(CLOCK_MONOTONIC, ts);
	return ts-&gt;tv_sec * 1000000000 + ts-&gt;tv_nsec;
}

static const long NANO_COST_ITERATIONS = 10000000;

static long calc_nano_ts_cost() {

	struct timespec ts;

   	long start = get_nano_ts(&amp;ts);

    long finish = start;

    for (long i = 0; i &lt; NANO_COST_ITERATIONS; i++) {
    	finish = get_nano_ts(&amp;ts);
   	}

    finish = get_nano_ts(&amp;ts);
        
    return (finish - start) / NANO_COST_ITERATIONS;
}

struct mi {
   long value;
};

void add_perc(stringstream&amp; ss, int size, double perc, map&lt;int, mi*&gt;* map) {

	if (map-&gt;empty()) return;
	
	int max = -1;
	int minBottom = -1;
	
	long x = round(perc * size);
	long i = 0;
	long iBottom = 0;
	
	long sum = 0;
	long sumBottom = 0;
	
	bool trueForTopFalseForBottom = true;
	bool flag = false;
	
	const int arraySize = 1024 * 1024 * 10;
	int* tempData = new int[arraySize];
	double stdevTop = -1;
	
	for(auto iter = map-&gt;begin(); iter != map-&gt;end(); iter++) {
	
		if (flag) break;
	
		int time = iter-&gt;first;
		long count = (iter-&gt;second)-&gt;value;
		
		for(int a = 0; a &lt; count; a++) {
		
			if (trueForTopFalseForBottom) {
		
				tempData[i] = time;
		
				i++;
				sum += time;
				
				if (i == x) {
					
					max = time;
					
					if (INCLUDE_STDEV) {
    						
						double avg = (double) sum / (double) i;
						double temp = 0;
						
						for(int b = 0; b &lt; i; b++) {
							int t = tempData[b];
							temp += (avg - t) * (avg - t);
						}
						
						stdevTop = sqrt(((double) temp / (double) i));
					}
				
					if (INCLUDE_WORST_PERCS) {
    					trueForTopFalseForBottom = false;	
    				} else {
    					flag = true;
						break;
    				}
				}
				
			} else {
			
				tempData[iBottom] = time;
			
				iBottom++;
				sumBottom += time;
				if (minBottom == -1) {
					minBottom = time;
				}
			}
		}
	}
	
	ss &lt;&lt; &quot; | &quot; &lt;&lt; fixed &lt;&lt; setprecision(5) &lt;&lt; (perc * 100) &lt;&lt; &quot;%&quot;;
	if (INCLUDE_TOTALS) ss &lt;&lt; &quot; (&quot; &lt;&lt; i &lt;&lt; &quot;)&quot;;
	ss &lt;&lt; &quot; = [avg: &quot; &lt;&lt; (sum / i);
	if (INCLUDE_STDEV) ss &lt;&lt; &quot;, stdev: &quot; &lt;&lt; fixed &lt;&lt; setprecision(2) &lt;&lt; stdevTop;
	ss &lt;&lt; &quot;, max: &quot; &lt;&lt; max &lt;&lt; &quot;]&quot;;
	if (INCLUDE_WORST_PERCS) {
		ss &lt;&lt; &quot; - &quot; &lt;&lt; fixed &lt;&lt; setprecision(5) &lt;&lt; ((1 - perc) * 100) &lt;&lt; &quot;%&quot;;
		if (INCLUDE_TOTALS) ss &lt;&lt; &quot; (&quot; &lt;&lt; (iBottom &gt; 0 ? iBottom : 0) &lt;&lt; &quot;)&quot;;
		ss &lt;&lt; &quot; = [avg: &quot; &lt;&lt; (iBottom &gt; 0 ? (sumBottom / iBottom) : -1);
		
		if (INCLUDE_STDEV) {
		
			ss &lt;&lt; &quot;, stdev: &quot;;
		
			if (iBottom &lt;= 0) {
				ss &lt;&lt; &quot;?&quot;;
			} else {
			
				double avgBottom = (sumBottom / iBottom);
				
				double temp = 0;
				
				for(int b = 0; b &lt; iBottom; b++) {
					long t = tempData[b];
					temp += (avgBottom - t) * (avgBottom - t);
				}
				
				double stdevBottom = sqrt((double) temp / (double) iBottom);
			
				ss &lt;&lt; fixed &lt;&lt; setprecision(2) &lt;&lt; stdevBottom;
			}
		
		}
		
		ss &lt;&lt; &quot;, min: &quot; &lt;&lt; (minBottom != -1 ? minBottom : -1) &lt;&lt; &quot;]&quot;;
		if (INCLUDE_RATIOS) ss &lt;&lt; &quot; R: &quot; &lt;&lt; fixed &lt;&lt; setprecision(2) &lt;&lt; (iBottom &gt; 0 ? ( ((double) (sumBottom / iBottom) / (double) (sum / i) ) - 1) * 100 : -1) &lt;&lt; &quot;%&quot;;
	}
	
	delete[] tempData;
}

void swapping(int &amp;a, int &amp;b) {      //swap the content of a and b
   int temp;
   temp = a;
   a = b;
   b = temp;
}
void display(int *array, int size) {
   for(int i = 0; i&lt;size; i++)
      cout &lt;&lt; array[i] &lt;&lt; &quot; &quot;;
   cout &lt;&lt; endl;
}
void bubbleSort(int *array, int size) {
   for(int i = 0; i&lt;size; i++) {
      int swaps = 0;         //flag to detect any swap is there or not
      for(int j = 0; j&lt;size-i-1; j++) {
         if(array[j] &gt; array[j+1]) {       //when the current item is bigger than next
            swapping(array[j], array[j+1]);
            swaps = 1;    //set swap flag
         }
      }
      if(!swaps)
         break;       // No swap in this pass, so array is sorted
   }
}

void doSomething(int *array, int size) {

	for(int z = 0; z &lt; size; z++) {
		array[z] = size - z;
	}

	bubbleSort(array, size);
}

int main(int argc, char* argv[]) {
	
	int iterations = stoi(argv[1]);
	int warmup = stoi(argv[2]);
	int arraySize = stoi(argv[3]);
	int proc = stoi(argv[4]);
	
	cpu_set_t my_set;
	CPU_ZERO(&amp;my_set);
	CPU_SET(proc, &amp;my_set);
	sched_setaffinity(0, sizeof(cpu_set_t), &amp;my_set);
	
	long nanoTimeCost = EXCLUDE_NANO_TS_COST ? calc_nano_ts_cost() : 0;
	
	struct timespec ts;
	
	long long x = 0;
	long long totalTime = 0;
	int minTime = numeric_limits&lt;int&gt;::max();
	int maxTime = numeric_limits&lt;int&gt;::min();
	
	map&lt;int, mi*&gt;* results = new map&lt;int, mi*&gt;();
	
	int * array = (int*) malloc(arraySize * sizeof(int));
	
	for(int i = 0; i &lt; iterations; i++) {
	
	 	long start = get_nano_ts(&amp;ts);

		doSomething(array, arraySize);	
		
		long end = get_nano_ts(&amp;ts);
		
		for(int j = 0; j &lt; arraySize; j++) {
			x += array[j];
		}

		int res = end - start - nanoTimeCost;
		
		if (res &lt;= 0) res = 1;
		
		if (i &gt;= warmup) {
			totalTime += res;
			minTime = min(minTime, res);
			maxTime = max(maxTime, res);
			
			auto iter = results-&gt;find(res);
			
			if (iter != results-&gt;end()) {
			
				(iter-&gt;second)-&gt;value = (iter-&gt;second)-&gt;value + 1;
				
			} else {
			
				mi* elem = new mi();
				elem-&gt;value = 1;
				(*results)[res] = elem;
			}
			
		}
	}
	
	int count = iterations - warmup;
	
	double avg = totalTime / count;
	
	cout &lt;&lt; &quot;Value computed: &quot; &lt;&lt; x &lt;&lt; endl;
	display(array, arraySize);
	cout &lt;&lt; &quot;Nano timestamp cost: &quot; &lt;&lt; nanoTimeCost &lt;&lt; endl;
	free(array);
	
	stringstream ss;
	
	ss &lt;&lt; &quot;Iterations: &quot; &lt;&lt; count &lt;&lt; &quot; | Avg Time: &quot; &lt;&lt; avg;


	if (INCLUDE_STDEV) {
	
		long temp = 0;
		long x = 0;
	
		for(auto iter = results-&gt;begin(); iter != results-&gt;end(); iter++) {
	
			int time = iter-&gt;first;
			long count = (iter-&gt;second)-&gt;value;
			
			for(int a = 0; a &lt; count; a++) {
				temp += (avg - time) * (avg - time);
				x++;
			}
		}
		
		double stdev = sqrt( temp / x );
		
		ss &lt;&lt; &quot; | Stdev: &quot; &lt;&lt; fixed &lt;&lt; setprecision(2) &lt;&lt; stdev;
	}
	
	if (count &gt; 0) {
		ss &lt;&lt; &quot; | Min Time: &quot; &lt;&lt; minTime &lt;&lt; &quot; | Max Time: &quot; &lt;&lt; maxTime;
	}
	
	add_perc(ss, count, 0.75, results);
	add_perc(ss, count, 0.90, results);
	add_perc(ss, count, 0.99, results);
	add_perc(ss, count, 0.999, results);
	add_perc(ss, count, 0.9999, results);
	add_perc(ss, count, 0.99999, results);
	
	if (MORE_PERCS) {
		add_perc(ss, count, 0.999999, results);
		add_perc(ss, count, 0.9999999, results);
	}

	cout &lt;&lt; ss.str() &lt;&lt; endl &lt;&lt; endl;
		
	delete results;
	
	return 0;
}
</pre>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/performance-a-c-and-java-comparison/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Shared Memory Transport x Multicast Transport</title>
		<link>https://www.coralblocks.com/index.php/shared-memory-transport-x-multicast-transport/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=shared-memory-transport-x-multicast-transport</link>
		<comments>https://www.coralblocks.com/index.php/shared-memory-transport-x-multicast-transport/#comments</comments>
		<pubDate>Thu, 14 Oct 2021 12:43:35 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[fan-out]]></category>
		<category><![CDATA[multicast]]></category>
		<category><![CDATA[Sequencer]]></category>
		<category><![CDATA[shared memory]]></category>

		<guid isPermaLink="false">http://www.coralblocks.com/index.php/?p=2637</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>A CPU core is a scarce resource in high demand from all the different process running on a computer machine. When you are choosing how to allocate your nodes to available CPU cores, CoralSequencer gives you the option to run several nodes inside the same NioReactor thread, which in turn will be pinned to an isolated CPU core. As the number of nodes inside the same CPU core grows, fan-out might become an issue affecting latency, as now the thread has to cycle through all nodes as it reads multicast messages from the event-stream. In this article we explain (with diagrams) how you can deal with that scenario by choosing CoralSequencer&#8217;s shared-memory transport instead of multicast at the node machine level.<span id="more-2637"></span></p>
<p><br/></p>
<h4>Multicast Fan-Out</h4>
<p>So let&#8217;s say you have 20 nodes running on the same thread. After Node 1 reads its multicast message, it will have to wait for all other 19 nodes to read the multicast message before it can proceed to read the next message. The network card receiving the multicast message has to make 20 copies of the message, placing each copy in the individual <em>underlying native receive socket buffer</em> of each node. The diagram below illustrates this scenario:</p>
<p><a href="http://www.coralblocks.com/wp-content/uploads/2021/10/Screen-Shot-2021-10-14-at-9.32.58-AM.png"><img src="http://www.coralblocks.com/wp-content/uploads/2021/10/Screen-Shot-2021-10-14-at-9.32.58-AM.png" alt="Screen Shot 2021-10-14 at 9.32.58 AM" width="1824" height="1274" class="alignnone size-full wp-image-2659" /></a></p>
<p>
So that begs the question: <em><strong>How will an individual node latency be affected as we keep adding more nodes to its thread?</strong></em> Below the results of the benchmark tests:
</p>
<p><br/></p>
<pre>
1 Node: <font color="blue"><em>(baseline or zero fan-out)</em></font>

20:00:56.170513-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">4.503 micros</font></strong> | Min Time: 3.918 micros | Max Time: 704.095 micros | 75% = [avg: 4.272 micros, max: 4.69 micros] | 90% = [avg: 4.35 micros, max: 4.799 micros] | 99% = [avg: 4.41 micros, max: 5.783 micros] | 99.9% = [avg: 4.425 micros, max: 7.037 micros] | 99.99% = [avg: 4.468 micros, max: 264.298 micros] | 99.999% = [avg: 4.497 micros, max: 436.127 micros]

2 Nodes:

20:22:48.562317-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">4.556 micros</font></strong> | Min Time: 3.983 micros | Max Time: 650.027 micros | 75% = [avg: 4.227 micros, max: 4.696 micros] | 90% = [avg: 4.38 micros, max: 5.213 micros] | 99% = [avg: 4.462 micros, max: 5.706 micros] | 99.9% = [avg: 4.478 micros, max: 6.871 micros] | 99.99% = [avg: 4.519 micros, max: 274.402 micros] | 99.999% = [avg: 4.55 micros, max: 490.207 micros]

3 Nodes:

20:21:33.572903-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">5.614 micros</font></strong> | Min Time: 3.943 micros | Max Time: 691.881 micros | 75% = [avg: 5.224 micros, max: 5.954 micros] | 90% = [avg: 5.355 micros, max: 6.098 micros] | 99% = [avg: 5.503 micros, max: 7.64 micros] | 99.9% = [avg: 5.527 micros, max: 9.422 micros] | 99.99% = [avg: 5.582 micros, max: 253.727 micros] | 99.999% = [avg: 5.608 micros, max: 398.772 micros]

4 Nodes:

20:20:08.231927-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">6.728 micros</font></strong> | Min Time: 4.555 micros | Max Time: 464.322 micros | 75% = [avg: 6.562 micros, max: 6.681 micros] | 90% = [avg: 6.587 micros, max: 6.76 micros] | 99% = [avg: 6.619 micros, max: 7.348 micros] | 99.9% = [avg: 6.631 micros, max: 9.444 micros] | 99.99% = [avg: 6.702 micros, max: 208.45 micros] | 99.999% = [avg: 6.724 micros, max: 331.632 micros]

5 Nodes:

20:04:31.776798-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">7.982 micros</font></strong> | Min Time: 5.433 micros | Max Time: 364.291 micros | 75% = [avg: 7.667 micros, max: 8.355 micros] | 90% = [avg: 7.795 micros, max: 8.509 micros] | 99% = [avg: 7.872 micros, max: 9.093 micros] | 99.9% = [avg: 7.886 micros, max: 11.372 micros] | 99.99% = [avg: 7.959 micros, max: 190.152 micros] | 99.999% = [avg: 7.978 micros, max: 278.21 micros]

10 Nodes:

20:10:03.527401-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">13.555 micros</font></strong> | Min Time: 7.098 micros | Max Time: 266.888 micros | 75% = [avg: 12.773 micros, max: 15.157 micros] | 90% = [avg: 13.201 micros, max: 15.634 micros] | 99% = [avg: 13.436 micros, max: 16.061 micros] | 99.9% = [avg: 13.475 micros, max: 58.272 micros] | 99.99% = [avg: 13.539 micros, max: 132.831 micros] | 99.999% = [avg: 13.552 micros, max: 189.882 micros]

15 Nodes:

20:14:16.156609-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">16.647 micros</font></strong> | Min Time: 11.668 micros | Max Time: 189.303 micros | 75% = [avg: 15.769 micros, max: 17.76 micros] | 90% = [avg: 16.205 micros, max: 19.094 micros] | 99% = [avg: 16.522 micros, max: 20.563 micros] | 99.9% = [avg: 16.58 micros, max: 55.764 micros] | 99.99% = [avg: 16.636 micros, max: 107.489 micros] | 99.999% = [avg: 16.645 micros, max: 143.581 micros]

20 Nodes:

20:18:00.346389-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">21.842 micros</font></strong> | Min Time: 13.524 micros | Max Time: 170.239 micros | 75% = [avg: 20.484 micros, max: 24.261 micros] | 90% = [avg: 21.237 micros, max: 25.691 micros] | 99% = [avg: 21.711 micros, max: 27.423 micros] | 99.9% = [avg: 21.788 micros, max: 59.232 micros] | 99.99% = [avg: 21.832 micros, max: 92.211 micros] | 99.999% = [avg: 21.84 micros, max: 132.365 micros]
</pre>
<p><br/></p>
<h4>Shared Memory Fan-Out through the Dispatcher node</h4>
<p>When you use CoralSequencer&#8217;s <strong>Dispatcher</strong> node, only the dispatcher node will read the multicast messages from the network card. It will proceed to write the messages to shared memory. Then any other node running on that machine can choose to read the messages from the same shared memory (from the dispatcher) instead of multicast (from the network card). The diagram below illustrates this scenario:</p>
<p><a href="http://www.coralblocks.com/wp-content/uploads/2021/10/Screen-Shot-2021-10-14-at-9.37.05-AM.png"><img src="http://www.coralblocks.com/wp-content/uploads/2021/10/Screen-Shot-2021-10-14-at-9.37.05-AM.png" alt="Screen Shot 2021-10-14 at 9.37.05 AM" width="1720" height="1144" class="alignnone size-full wp-image-2663" /></a></p>
<p><em><strong>Is reading from shared memory any faster than reading from the network, even when we are using non-blocking networks reads?</strong></em> Let&#8217;s repeat the same benchmark tests to find out:<br />
<br/></p>
<pre>
1 Node: <font color="blue"><em>(baseline or zero fan-out)</em></font>

06:28:56.524909-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">5.136 micros</font></strong> | Min Time: 4.173 micros | Max Time: 594.837 micros | 75% = [avg: 4.903 micros, max: 5.171 micros] | 90% = [avg: 4.954 micros, max: 5.46 micros] | 99% = [avg: 5.021 micros, max: 6.042 micros] | 99.9% = [avg: 5.034 micros, max: 7.875 micros] | 99.99% = [avg: 5.102 micros, max: 268.368 micros] | 99.999% = [avg: 5.131 micros, max: 419.933 micros]

2 Nodes:

06:30:40.480024-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">5.42 micros</font></strong> | Min Time: 4.246 micros | Max Time: 581.791 micros | 75% = [avg: 5.156 micros, max: 5.591 micros] | 90% = [avg: 5.239 micros, max: 5.754 micros] | 99% = [avg: 5.303 micros, max: 6.321 micros] | 99.9% = [avg: 5.316 micros, max: 7.977 micros] | 99.99% = [avg: 5.385 micros, max: 292.002 micros] | 99.999% = [avg: 5.415 micros, max: 459.128 micros]

3 Nodes:

06:32:19.770820-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">5.649 micros</font></strong> | Min Time: 4.174 micros | Max Time: 1.023 millis | 75% = [avg: 5.366 micros, max: 5.793 micros] | 90% = [avg: 5.457 micros, max: 6.059 micros] | 99% = [avg: 5.527 micros, max: 6.491 micros] | 99.9% = [avg: 5.54 micros, max: 8.244 micros] | 99.99% = [avg: 5.614 micros, max: 282.519 micros] | 99.999% = [avg: 5.643 micros, max: 453.64 micros]

4 Nodes:

06:35:39.689059-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">5.77 micros</font></strong> | Min Time: 4.26 micros | Max Time: 645.956 micros | 75% = [avg: 5.479 micros, max: 5.948 micros] | 90% = [avg: 5.577 micros, max: 6.195 micros] | 99% = [avg: 5.65 micros, max: 6.781 micros] | 99.9% = [avg: 5.664 micros, max: 8.362 micros] | 99.99% = [avg: 5.736 micros, max: 264.038 micros] | 99.999% = [avg: 5.764 micros, max: 437.225 micros]

5 Nodes:

06:40:42.531026-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">6.291 micros</font></strong> | Min Time: 4.361 micros | Max Time: 753.637 micros | 75% = [avg: 5.929 micros, max: 6.636 micros] | 90% = [avg: 6.068 micros, max: 6.912 micros] | 99% = [avg: 6.164 micros, max: 7.488 micros] | 99.9% = [avg: 6.18 micros, max: 9.14 micros] | 99.99% = [avg: 6.256 micros, max: 272.228 micros] | 99.999% = [avg: 6.285 micros, max: 451.461 micros]

10 Nodes:

06:44:12.332180-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">6.722 micros</font></strong> | Min Time: 4.478 micros | Max Time: 509.009 micros | 75% = [avg: 6.245 micros, max: 7.258 micros] | 90% = [avg: 6.455 micros, max: 7.787 micros] | 99% = [avg: 6.599 micros, max: 8.502 micros] | 99.9% = [avg: 6.618 micros, max: 10.021 micros] | 99.99% = [avg: 6.693 micros, max: 235.364 micros] | 99.999% = [avg: 6.718 micros, max: 370.217 micros]

15 Nodes:

06:48:31.743422-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">7.531 micros</font></strong> | Min Time: 4.759 micros | Max Time: 499.443 micros | 75% = [avg: 7.028 micros, max: 7.884 micros] | 90% = [avg: 7.202 micros, max: 8.31 micros] | 99% = [avg: 7.373 micros, max: 10.167 micros] | 99.9% = [avg: 7.402 micros, max: 12.745 micros] | 99.99% = [avg: 7.499 micros, max: 251.521 micros] | 99.999% = [avg: 7.526 micros, max: 403.692 micros]

20 Nodes:

06:53:27.062402-INFO NODE1 Finished latency test! msgSize=16 results=Iterations: 1,000,000 | Avg Time: <strong><font color="blue">9.506 micros</font></strong> | Min Time: 4.866 micros | Max Time: 438.516 micros | 75% = [avg: 9.003 micros, max: 10.057 micros] | 90% = [avg: 9.213 micros, max: 10.482 micros] | 99% = [avg: 9.356 micros, max: 11.654 micros] | 99.9% = [avg: 9.381 micros, max: 17.448 micros] | 99.99% = [avg: 9.479 micros, max: 217.556 micros] | 99.999% = [avg: 9.502 micros, max: 348.865 micros]
</pre>
<p><br/></p>
<h4>Graph Comparison</h4>
<p><a href="http://www.coralblocks.com/wp-content/uploads/2021/10/Screen-Shot-2021-10-14-at-8.49.14-AM.png"><img src="http://www.coralblocks.com/wp-content/uploads/2021/10/Screen-Shot-2021-10-14-at-8.49.14-AM-1024x513.png" alt="Screen Shot 2021-10-14 at 8.49.14 AM" width="600" height="300" class="alignnone size-large wp-image-2647" /></a></p>
<p><br/></p>
<h4>FAQ:</h4>
<style>
.li_faq { margin: 0 0 30px 0; }
</style>
<ol style="padding: 12px 40px">
<li class="li_faq"><font color="#26619b"><strong>Does the dispatcher use a circular buffer as shared memory?</strong></font><br />
Yes, and because it is shared-memory through a memory-mapped file, it can be made as large as your disk space can handle. Furthermore, the circular buffer provides safety guards so that a consumer is not reading a message that is currently being written by the producer.
</li>
<li class="li_faq"><font color="#26619b"><strong>Do the nodes read from the same shared-memory space?</strong></font><br />
Yes, the dispatcher writes once to the shared-memory space. All nodes read from this same shared-memory space.
</li>
<li class="li_faq"><font color="#26619b"><strong>Can nodes in different threads/JVMs be reading from the same dispatcher at the same time?</strong></font><br />
Yes, the dispatcher can handle multiple threads consuming the same messages concurrently. They do not need to be in the same thread as they are in this example. The reason they are placed in the same thread in this example is to illustrate and address the fan-out issue. If they are reading from multiple threads then the fan-out issue goes away. But you would still have gains by removing the load from the network card so that it does not have to copy the same message to multiple underlying socket buffers. With the dispatcher, the network card copies the messages only once to the shared-memory store and all the nodes read the messages from that same shared-memory store.
</li>
</ol>
<p><br/></p>
<h4>Conclusion</h4>
<p>Adding a shared-memory dispatcher improves latency in fan-out scenarios, when for some reason you need/want to run several nodes inside the same NioReactor thread (i.e. inside the same CPU core). The dispatcher takes the load off the network card as now the network card has to deliver the message only once (to the dispatcher) instead of 20 times (to each of the 20 nodes).</p>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/shared-memory-transport-x-multicast-transport/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CoralSequencer’s structured data serialization framework (Version 2.0)</title>
		<link>https://www.coralblocks.com/index.php/coralsequencers-structured-data-serialization-framework-version-2-0/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=coralsequencers-structured-data-serialization-framework-version-2-0</link>
		<comments>https://www.coralblocks.com/index.php/coralsequencers-structured-data-serialization-framework-version-2-0/#comments</comments>
		<pubDate>Sat, 14 Aug 2021 16:52:48 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[avro]]></category>
		<category><![CDATA[protobuf]]></category>
		<category><![CDATA[protocol]]></category>
		<category><![CDATA[serialization]]></category>

		<guid isPermaLink="false">http://www.coralblocks.com/index.php/?p=2584</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>CoralSequencer uses its own binary and garbage-free serialization framework to read and write its internal messages. For your application messages, you are free to use any serialization library or binary data model you choose. The fact that CoralSequencer is message agnostic gives you total flexibility in that decision. But you can also consider using CoralSequencer’s native serialization framework described in this article.<span id="more-2584"></span></p>
<p>To define the schema of a message you can use the simple and self-explanatory <a href="https://en.wikipedia.org/wiki/Interface_description_language" target="_blank">IDL</a> described below. Note that it supports <strong>optional fields</strong> through the exclamation mark following the type and, most importantly, it also supports <strong>repeating groups</strong> and repeating groups of repeating groups (i.e. <strong>nested repeating groups</strong>).</p>
<pre class="brush: java; title: ; notranslate">
			TYPE = S
			SUBTYPE = D

			reqId: int
			exchangeSession: chars(10)
			comments: varchars(1024)!

			securities:
			    secId: int
			    secSymbol: varchars(64)
			    legs:
			      secId: int
			      secSymbol: varchars(64)
			    secDesc: varchars(1024)!
</pre>
<p>To generate the Java code for the IDL above, you can do:</p>
<pre class="brush: java; title: ; notranslate">
package com.coralblocks.coralsequencer.node;

import java.io.IOException;
import com.coralblocks.coralsequencer.protocol.*;
import com.coralblocks.coralsequencer.protocol.field.*;

public class SampleProtoMessage extends AbstractProto {
	
	private static final String TEXT = String.join(&quot;\n&quot;,
			
			&quot;TYPE = S&quot;,
			&quot;SUBTYPE = D&quot;,
			&quot;&quot;,
			&quot;reqId: int&quot;,
			&quot;exchangeSession: chars(10)&quot;,
			&quot;comments: varchars(1024)!&quot;,
			&quot;&quot;,
			&quot;securities:&quot;,
			&quot;    secId: int&quot;,
			&quot;    secSymbol: varchars(64)&quot;,
			&quot;    legs:&quot;,
			&quot;      secId: int&quot;,
			&quot;      secSymbol: varchars(64)&quot;,
			&quot;    secDesc: varchars(1024)!&quot;,
			
			&quot;&quot;);
	
	public static void main(String[] args) throws IOException {
		IDL idl = new IDL(TEXT);
		String code = idl.getCode();
		String pathToJavaCode = &quot;src/main/java/com/coralblocks/coralsequencer/node/SampleProtoMessage.java&quot;;
		IDL.replaceAutoGeneratedCode(pathToJavaCode, code);
	}

	// Auto-generated code. Do not edit or change anything below here
	
	// BEGIN_AUTO_GENERATED_CODE

        // (Auto-generated code will go here)

	// END_AUTO_GENERATED_CODE

}
</pre>
<p>After you run the class above, the following source code will be automatically generated and added to the Java file so that you don&#8217;t need to type anything:</p>
<pre class="brush: java; title: ; notranslate">
    public final static char TYPE = 'S';
    public final static char SUBTYPE = 'D';

    public final TypeField typeField = new TypeField(this, TYPE);
    public final SubtypeField subtypeField = new SubtypeField(this, SUBTYPE);

    public final IntField reqId = new IntField(this);

    public final CharsField exchangeSession = new CharsField(this, 10);

    public final VarCharsField comments = new VarCharsField(this, 1024, true);

    public static class SecuritiesRepeatingGroup extends RepeatingGroupField {

        public IntField secId;

        public VarCharsField secSymbol;

        public static class LegsRepeatingGroup extends RepeatingGroupField {

            public IntField secId;

            public VarCharsField secSymbol;

            public LegsRepeatingGroup(AbstractProto proto) {
                this(proto, new IntField(), new VarCharsField(64));
            }

            public LegsRepeatingGroup(AbstractProto proto, ProtoField ... protoFields) {
                super(proto, protoFields);
            }

            @Override
            public GroupField nextElement() {
                GroupField groupField = super.nextElement();
                this.secId = (IntField) groupField.internalArray()[0];
                this.secSymbol = (VarCharsField) groupField.internalArray()[1];
                return groupField;
            } 

            @Override
            public GroupField iterNext() {
                GroupField groupField = super.iterNext();
                if (groupField != null) {
                    this.secId = (IntField) groupField.internalArray()[0];
                    this.secSymbol = (VarCharsField) groupField.internalArray()[1];
                }
                return groupField;
            } 

            @Override
            protected final RepeatingGroupField newInstance(ProtoField[] protoFields) {
                return new SecuritiesRepeatingGroup.LegsRepeatingGroup(null, protoFields);
            } 

        }

        SecuritiesRepeatingGroup.LegsRepeatingGroup legs;

        public VarCharsField secDesc;

        public SecuritiesRepeatingGroup(AbstractProto proto) {
            this(proto, new IntField(), new VarCharsField(64), new SecuritiesRepeatingGroup.LegsRepeatingGroup(null), new VarCharsField(1024));
        }

        public SecuritiesRepeatingGroup(AbstractProto proto, ProtoField ... protoFields) {
            super(proto, protoFields);
        }

        @Override
        public GroupField nextElement() {
            GroupField groupField = super.nextElement();
            this.secId = (IntField) groupField.internalArray()[0];
            this.secSymbol = (VarCharsField) groupField.internalArray()[1];
            this.legs = (SecuritiesRepeatingGroup.LegsRepeatingGroup) groupField.internalArray()[2];
            this.secDesc = (VarCharsField) groupField.internalArray()[3];
            return groupField;
        } 

        @Override
        public GroupField iterNext() {
            GroupField groupField = super.iterNext();
            if (groupField != null) {
                this.secId = (IntField) groupField.internalArray()[0];
                this.secSymbol = (VarCharsField) groupField.internalArray()[1];
                this.legs = (SecuritiesRepeatingGroup.LegsRepeatingGroup) groupField.internalArray()[2];
                this.secDesc = (VarCharsField) groupField.internalArray()[3];
            }
            return groupField;
        } 

        @Override
        protected final RepeatingGroupField newInstance(ProtoField[] protoFields) {
            return new SecuritiesRepeatingGroup(null, protoFields);
        } 

    }

    SecuritiesRepeatingGroup securities = new SecuritiesRepeatingGroup(this);
</pre>
<p>Below a simple node to send and receive the SampleProtoMessage defined above:</p>
<pre class="brush: java; title: ; notranslate">
package com.coralblocks.coralsequencer.node;

import static com.coralblocks.corallog.Log.*;

import java.nio.ByteBuffer;

import com.coralblocks.coralreactor.admin.AdminCommand;
import com.coralblocks.coralreactor.nio.NioReactor;
import com.coralblocks.coralreactor.util.Configuration;
import com.coralblocks.coralsequencer.message.Message;
import com.coralblocks.coralsequencer.mq.Node;
import com.coralblocks.coralsequencer.protocol.Proto;

public class SampleProtoNode extends Node {
	
	private final SampleProtoMessage sampleProtoMessage = new SampleProtoMessage();
	private final SampleProtoParser protoParser = new SampleProtoParser();
	
	private final ByteBuffer bb = ByteBuffer.allocate(2048);

	public SampleProtoNode(NioReactor nio, String name, Configuration config) {
		super(nio, name, config);
	}
	
	@Override
	protected void initAdminCommands() {
		
		super.initAdminCommands();
		
		addAdminCommand(new AdminCommand(&quot;sendSampleProtoMessage&quot;) {

			@Override
			public boolean execute(CharSequence args, StringBuilder results) {
				
				try {

				sampleProtoMessage.reqId.set(23223);
				sampleProtoMessage.exchangeSession.set(getSession());
				sampleProtoMessage.comments.markAsNotPresent();

				sampleProtoMessage.securities.clear();
				
				sampleProtoMessage.securities.nextElement();
				sampleProtoMessage.securities.secId.set(1);
				sampleProtoMessage.securities.secSymbol.set(&quot;IBM2-IBM3&quot;);
				sampleProtoMessage.securities.legs.nextElement();
				sampleProtoMessage.securities.legs.secId.set(2222);
				sampleProtoMessage.securities.legs.secSymbol.set(&quot;IBM2&quot;);
				sampleProtoMessage.securities.legs.nextElement();
				sampleProtoMessage.securities.legs.secId.set(3333);
				sampleProtoMessage.securities.legs.secSymbol.set(&quot;IBM3&quot;);
				sampleProtoMessage.securities.secDesc.set(&quot;This is a spread for IBM2-IBM3&quot;);
				
				sampleProtoMessage.securities.nextElement();
				sampleProtoMessage.securities.secId.set(2);
				sampleProtoMessage.securities.secSymbol.set(&quot;IBM4-IBM5&quot;);
				sampleProtoMessage.securities.legs.nextElement();
				sampleProtoMessage.securities.legs.secId.set(4444);
				sampleProtoMessage.securities.legs.secSymbol.set(&quot;IBM4&quot;);
				sampleProtoMessage.securities.legs.nextElement();
				sampleProtoMessage.securities.legs.secId.set(5555);
				sampleProtoMessage.securities.legs.secSymbol.set(&quot;IBM5&quot;);
				sampleProtoMessage.securities.secDesc.set(&quot;This is a spread for IBM4-IBM5&quot;);
				
				sendCommand(sampleProtoMessage);
				
				} catch(Exception e) {
					e.printStackTrace();
				}
				
				results.append(&quot;SampleProtoMessage sent!&quot;);
				return true;
			}
		});
	}
	
	@Override
	protected void handleMessage(boolean isMine, Message msg) {
		
		Proto proto = protoParser.parse(msg);
		
		if (proto == null) {
			Error.log(name, &quot;Could not parse message:&quot;, msg.toString());
			return;
		}
		
		char type = proto.getType();
		char subtype = proto.getSubtype();
		
		if (type == SampleProtoMessage.TYPE &amp;&amp; subtype == SampleProtoMessage.SUBTYPE) {
			
			SampleProtoMessage spm = (SampleProtoMessage) proto;

			Info.log(name, &quot;reqId=&quot;, spm.reqId, &quot;exchangeSession=&quot;, spm.exchangeSession, &quot;comments=&quot;, spm.comments);
			
			// Get one by one...
			int reqId = spm.reqId.get();
			CharSequence exchangeSession = spm.exchangeSession.get();
			
			// Remember comments is an optional field...
			CharSequence comments = spm.comments.isPresent() ? spm.comments.get() : null;
			
			Info.log(name, &quot;reqId=&quot;, reqId, &quot;exchangeSession=&quot;, exchangeSession, &quot;comments=&quot;, comments);
			
			// Iterator through repeating groups...
			
			spm.securities.beginIteration();
			
			while(spm.securities.iterHasNext()) {
				
				spm.securities.iterNext();
				
				Info.log(name, &quot;secId=&quot;, spm.securities.secId, &quot;secSymbol=&quot;, spm.securities.secSymbol, &quot;secDesc=&quot;, spm.securities.secDesc);
				
				// Iterator through a repeating group inside a repeating group...
				
				spm.securities.legs.beginIteration();
				
				while(spm.securities.legs.iterHasNext()) {
					
					spm.securities.legs.iterNext();
					
					Info.log(name, &quot;secId=&quot;, spm.securities.legs.secId, &quot;secSymbol=&quot;, spm.securities.legs.secSymbol);
				}
				
				Info.log(name, &quot;legs=&quot;, spm.securities.legs);
			}
			
			Info.log(name, &quot;securities=&quot;, spm.securities);
			
		} else {
			
			Error.log(name, &quot;Unreconizable proto message!&quot;, &quot;type=&quot;, type, &quot;subtype=&quot;, subtype);
		}
		
		bb.clear();
		proto.writeAscii(bb);
		bb.flip();
		Info.log(name, &quot;Full ProtoMessage received:&quot;, bb);
	}
}
</pre>
<p>Don&#8217;t forget to specify a parser to receive your proto messages:</p>
<pre class="brush: java; title: ; notranslate">
public class SampleProtoParser extends AbstractMessageProtoParser {
	
	@Override
    protected Proto[] createProtoMessages() {
		return new Proto[] { 
				new SampleProtoMessage()
			};
	}
}
</pre>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/coralsequencers-structured-data-serialization-framework-version-2-0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Building a first-class exchange architecture with CoralSequencer</title>
		<link>https://www.coralblocks.com/index.php/building-a-first-class-exchange-architecture-with-coralsequencer/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=building-a-first-class-exchange-architecture-with-coralsequencer</link>
		<comments>https://www.coralblocks.com/index.php/building-a-first-class-exchange-architecture-with-coralsequencer/#comments</comments>
		<pubDate>Wed, 30 Jun 2021 22:17:24 +0000</pubDate>
		<dc:creator><![CDATA[cb]]></dc:creator>
				<category><![CDATA[CoralSequencer]]></category>
		<category><![CDATA[ECN]]></category>
		<category><![CDATA[exchange]]></category>
		<category><![CDATA[matching engine]]></category>
		<category><![CDATA[messaging middleware]]></category>
		<category><![CDATA[MQ]]></category>
		<category><![CDATA[Sequencer]]></category>

		<guid isPermaLink="false">http://www.coralblocks.com/index.php/?p=2512</guid>
		<description><![CDATA[ [&#8230;]]]></description>
				<content:encoded><![CDATA[<style>
* {
font-size: 101%;
}</p>
<p>.li_facts { margin: 0 0 17px 0; }
</style>
<p style="margin-top: 20px;">In this article we will explore an architecture employed by some of the most sophisticated electronic exchanges, the ones that need to handle millions of orders per day with ultra-low-latency and high-availability. Most of the exchange main architecture components will be presented as <strong>CoralSequencer nodes</strong> and discussed through diagrams. The goal of this article is to demonstrate how a <strong><a href="https://en.wikipedia.org/wiki/Atomic_broadcast" target="_blank">total-ordered messaging middleware</a></strong> such as CoralSequencer naturally enables the implementation and control of complex distributed system through a tight integration of all its moving parts. This article doesn’t mention specifics about any exchange&#8217;s internal systems and instead talks about the big picture and the general concepts, which will vary from exchange to exchange.<span id="more-2512"></span>
</p>
<p style="margin-top: 20px; margin-bottom: 20px; font-size: 1.1em;">
<pre>
You should also check <a href="https://www.youtube.com/watch?v=DyktSiBTCdk" target="_blank"><b>our Sequencer Architecture YouTube video</b></a> where we present the main characteristics of the sequencer architecture
together with some advanced features of CoralSequencer. The video link is <a href="https://www.youtube.com/watch?v=DyktSiBTCdk" target="_blank">https://www.youtube.com/watch?v=DyktSiBTCdk</a>.
</pre>
<pre>
You should also check our open-source matching engine =&gt; <a href="https://www.github.com/coralblocks/CoralME" target="_blank"><b>CoralME</b> (https://www.github.com/coralblocks/CoralMe)</a>.
</pre>
</p>
<h3 class="coral" style="margin-top: 40px;">TCP Order Port</h3>
<p><img src="http://www.coralblocks.com/wp-content/uploads/2021/06/OrderPorts3.png" alt="OrderPorts3" width="2052" height="1546" class="aligncenter size-full wp-image-2574" /></p>
<p>Although it is technically possible to run more than one Order Port server inside the same node, each node will usually run a single Order Port server (FIX, BINARY or <em>XXXX</em>) capable of accepting several gateway connections from customers. The same JVM can be configured to run several nodes, each node running an Order Port server, each server listening to incoming gateway connections on its own network port. <strong>And all that is run by a single critical high-performance thread</strong>, pinned to an isolated CPU core. When we say single-thread, we actually mean it. The handling of the messaging middleware (sending and receiving messages to and from CoralSequencer) is also done by this same critical thread, through non-blocking network I/O operations.</p>
<h3 class="coral">Order Port High-Availability and Failover</h3>
<p><img src="http://www.coralblocks.com/wp-content/uploads/2021/06/OrderPortsFailover3.png" alt="OrderPortsFailover3" width="2000" height="1504" class="aligncenter size-full wp-image-2575" /></p>
<p>With <strong>hot-hot</strong> failover, the customer can literally <em>pull the plug</em> from one of his clustered gateway machines, for a zero-downtime failover. Note that the same is true on the exchange side: the exchange can <em>pull the plug</em> from any Order Port instance for a zero-downtime failover.</p>
<p>With <strong>hot-warm</strong> failover, there is a small down-time as the backup Order Port node needs to be activated (manually or automatically) and the customer gateway needs to connect to the new machine, either though a virtual IP change on the exchange side or through a destination IP change on the customer side.</p>
<h3 class="coral">Matching Engine</h3>
<p><img src="http://www.coralblocks.com/wp-content/uploads/2021/06/MatchingEngine.png" alt="MatchingEngine" width="1924" height="1410" class="aligncenter size-full wp-image-2532" /></p>
<p>The matching engine is the <em>brain</em> of the exchange, where buy orders are matching sell orders (and vice-versa). It builds and maintains a double-sided (bids and asks) liquidity order book for each symbol, and matches incoming orders accordingly.</p>
<p>The matching engine is critical (i.e. can never stop) and needs to be as fast as possible (i.e. can never block) as all other exchange components depend on it.</p>
<p>Instead of using one matching engine for all symbols, it makes a lot of sense to use a <em>sharding strategy</em> to spread the load across several matching engines. For example, an exchange may choose to have one matching engine for symbol names beginning with A-G, another one for H-N and another one for O-Z. Of course a good sharding strategy will depend on how active each symbol is so that to spread the load as evenly as possible.</p>
<h3 class="coral">TCP Market Data</h3>
<p><img src="http://www.coralblocks.com/wp-content/uploads/2021/06/MarketDataTCP.png" alt="MarketDataTCP" width="1862" height="1432" class="aligncenter size-full wp-image-2536" /></p>
<p>TCP market data is slower but has some advantages over Multicast UDP market data. First the customer market data feed can subscribe to receive only the symbols it is interested in. With Multicast UDP market data, all symbols are pushed to the customer. Not the entire number of symbols from the exchange, but all symbols present in that Multicast UDP channel as defined by the sharding strategy. Second, upon subscription, the customer usually receives a snapshot of the entire market data book. With Multicast UDP only incremental updates are sent, never snapshots. And third, because the customer can identify himself over the TCP connection, it is usually easy for the exchange to flag the market data orders belonging to that customer. That’s useful information that the exchange customer can use to prevent <em>trading to himself</em>, which a compliant exchange might choose to disallow through order rejects.</p>
<h3 class="coral">Multicast UDP Market Data</h3>
<p><img src="http://www.coralblocks.com/wp-content/uploads/2021/06/MarketDataUDP2.png" alt="MarketDataUDP2" width="2014" height="1622" class="aligncenter size-full wp-image-2542" /></p>
<p>An exchange can (and will) have thousands of customers interested in connectivity for market data. Maintaining a TCP market data channel for each one of them can quickly become impractical. The solution is to push out market data through multicast UDP to whoever wants to listen. That&#8217;s usually done by private network lines and collocation, never though the Internet due to the unreliability of UDP and because multicast is not supported over the public Internet.</p>
<p>It is important to notice that a multicast udp market data node will only broadcast incremental market data updates, never book snapshots. Therefore, some special logic needs to be implemented on the customer side to: first listen to the multicast UDP market data channel; buffer the initial incremental update messages; make a request for the initial market data book (i.e. snapshot) from a TCP market data channel; close the TCP connection; apply the buffered messages; then start applying the live incremental updates from the UDP channel.</p>
<p>Because of the unreliability nature of UDP, the exchange will usually multicast the market data over two redundant channels. The customer market data feed will listen to both at the same time and consume the packet that arrives first, discarding the slower duplicate. That minimizes the chances of gaps in the line (i.e. lost packets), as losing the same packet on both lines becomes less likely but still possible. Some exchanges will provide retransmission servers, specially for those customers that want/need to have all incremental updates for historical reference. If a retransmission server is not provided and/or the customer does not want to go through the trouble of implementing the retransmission request logic, the customer market data feed can just start from scratch by requesting a new book snapshot after a packet loss.</p>
<p>Last but not least, the customer can use the order id present in the market data incremental updates and check against its open orders to find out whether that order is his or not. As already said, that&#8217;s useful information that the exchange customer can use to prevent illegal <em>trades-to-self</em>.</p>
<h3 class="coral">TCP Drop Copy &nbsp;<font size="4px;"><i>( position, position, position )</i></font></h3>
<p><img src="http://www.coralblocks.com/wp-content/uploads/2021/06/DropCopy.png" alt="DropCopy" width="2014" height="1106" class="aligncenter size-full wp-image-2549" /></p>
<p>For an exchange customer to trust solely his gateways to be trading and maintaining a certain position can be very dangerous. It is always possible for a gateway bug to slip into production and create large losses without him even knowing about it. Therefore it is important to always match the position the gateway is reporting with the position that the drop copy is reporting. A mismatch there is a serious indication that something is very wrong and needs to be addressed by the exchange customer immediately.</p>
<h3 class="coral">And Much More</h3>
<p><img src="http://www.coralblocks.com/wp-content/uploads/2021/06/MuchMore.png" alt="MuchMore" width="1680" height="1216" class="aligncenter size-full wp-image-2564" /></p>
<p>Because all CoralSequencer nodes see the exact same set of messages, in the exact same order, <strong>always</strong>, the exchange can keep evolving by adding more nodes to perform any sort of work/action/logic/control without disrupting any other part of the distributed system.</p>
<p>To summarize, CoralSequencer provides the following features which are critical for a first-class electronic exchange: <strong>parallelism</strong> (nodes can truly run in parallel); <strong>tight integration</strong> (all nodes see the same messages in the same order); <strong>decoupling</strong> (nodes can evolve independently); <strong>high-availability/failover</strong> (when a node fails, another one can be running and building state to take over immediately); <strong>scalability/load balancing</strong> (just add more nodes); <strong>elasticity</strong> (nodes can lag during activity peaks without affecting the system as a whole); and <strong>resiliency</strong> (nodes can fail / stop working without taking the whole system down).</p>
]]></content:encoded>
			<wfw:commentRss>https://www.coralblocks.com/index.php/building-a-first-class-exchange-architecture-with-coralsequencer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
