<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>The Onyx Platform</title>
    <description>Distributed, masterless, high performance, fault tolerant data processing
</description>
    <link>https://onyx-platform.github.io/</link>
    <atom:link href="https://onyx-platform.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 12 Sep 2026 07:09:27 +0000</pubDate>
    <lastBuildDate>Sat, 12 Sep 2026 07:09:27 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Onyx 0.10.0: Asynchronous Barrier Snapshotting</title>
        <description>&lt;p&gt;Distributed Masonry is proud to announce Onyx 0.10.0, a major step forward for Onyx’s stream
processing engine. Version 0.10 brings enormous performance improvements, support
for in-order message processing, and enhancements for writing stateful streaming applications.
In this post, we’ll take a look at the recent activity on the project, summarize what’s been
delivered, and look forward to what’s ahead.&lt;/p&gt;

&lt;h2 id=&quot;new-streaming-engine&quot;&gt;New streaming engine&lt;/h2&gt;

&lt;p&gt;Onyx 0.10 rewrites the messaging and fault tolerance layer using the best in academic research - &lt;a href=&quot;https://github.com/onyx-platform/onyx/blob/0.10.x/doc/user-guide/architecture-low-level-design.adoc#asynchronous-barrier-snapshotting&quot;&gt;Asynchronous Barrier Snapshotting
(ABS)&lt;/a&gt; &lt;a href=&quot;https://arxiv.org/abs/1506.08603&quot;&gt;algorithm&lt;/a&gt;. 
ABS is an industry-proven method for periodically injecting barrier markers into a message stream to increase performance to achieve cluster wide consistent snapshots, while allowing for fast recovery capability.&lt;/p&gt;

&lt;p&gt;ABS is the core upgrade that clears the way for numerous advantages over Onyx’s previous mechanism using
message lineage tracking. We’ll examine just a few of these and talk about why they’re now possible.&lt;/p&gt;

&lt;h3 id=&quot;automatic-exactly-once-aggregations&quot;&gt;Automatic exactly-once aggregations&lt;/h3&gt;

&lt;p&gt;Onyx 0.9 supported exactly once aggregations, but was only able to do so if
provided with a user-provided key in each message. That meant that each
message flowing through Onyx needed to be tracked for fault handling purposes,
and deduplicated after checking whether a message with that key has been seen
before.  With 0.10, Onyx is able to automatically provides exactly once
aggregations without any preexisting knowledge of the structure of incoming
messages. Moreover, this approach is far more performant since checkpointing
happens at consistent intervals dictated by the barriers, rather than durably
checkpointing aggregation results for each individual message.&lt;/p&gt;

&lt;h3 id=&quot;in-order-processing&quot;&gt;In-order processing&lt;/h3&gt;

&lt;p&gt;With 0.9, in-order processing wasn’t possible because of our record-at-a-time
replay approach. Thanks to the properties of ABS, 0.10 is able to process
messages in the strict order that they’re received. Here again, barrier
injection is able to delineate the stream of messages into “zones”, and replay
happens at the granular of each barrier mark.  Peers are thus able to see the
same messages in the same order - even in the face of a recovery event.  In the
case of a recovery event, the stream will be rewound, and state recovered to
the consistent job wide-snapshot, ensuring that any state reducers will be
processed in-order. It is important to state that any side-effects performed by
tasks may be processed twice/out of order, however any accreted
state/reductions will be correctly processed.&lt;/p&gt;

&lt;p&gt;This ends up being a big deal as a growing number of applications depend on
order. These often lean on architectures that perform pattern detection
or provenance tracking e.g. &lt;a href=&quot;https://martinfowler.com/bliki/CQRS.html&quot;&gt;Command Query Responsibility Segregation
(CQRS)&lt;/a&gt;, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Complex_event_processing&quot;&gt;Complex Event
Processing (CEP)&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;improved-performance&quot;&gt;Improved performance&lt;/h3&gt;

&lt;p&gt;Finally, and perhaps most importantly for some workloads, Onyx 0.10 comes with
a serious performance upgrade.  Since 0.10 injects barrier markers periodically
into the message stream, we’re able to reduce the overhead of message
acknowledgments. Given the decrease in overhead, we’re generally seeing a 3-10x
throughput and latency improvement for workloads dominated by Onyx
communication and processing overhead.&lt;/p&gt;

&lt;h2 id=&quot;non-engine-improvements&quot;&gt;Non-engine improvements&lt;/h2&gt;

&lt;p&gt;Onyx 0.10 also introduces numerous important features and improvements, including the following:&lt;/p&gt;

&lt;h3 id=&quot;resume-points&quot;&gt;Resume Points&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/onyx-platform/onyx/blob/0.10.x/doc/user-guide/resume-points.adoc&quot;&gt;Resume points&lt;/a&gt;
are a new feature that make it simple to manage state between job runs. Resume
points allow for state to be transitioned from running or killed jobs. This
allows for simplified deployment and upgrades of long running streaming jobs,
refactoring of jobs e.g. split state from one job into two smaller jobs, as
well as hot swapped state transitions - made possible due to the immutability of the state snapshots.&lt;/p&gt;

&lt;h3 id=&quot;trigger-state-to-downstream-tasks&quot;&gt;Trigger state to downstream tasks&lt;/h3&gt;

&lt;p&gt;Triggers can now emit aggregates to downstream tasks via
&lt;a href=&quot;https://onyx-platform.github.io/docs/cheat-sheet/latest/#trigger-entry/:trigger/emit&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:trigger/emit&lt;/code&gt;&lt;/a&gt;.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:trigger/emit&lt;/code&gt; allows for multiple levels of aggregation and output
aggregation results to Onyx output mediums and more. Emitting trigger results
makes it much easier to make multiple Onyx jobs act as a cohesive whole.&lt;/p&gt;

&lt;h3 id=&quot;improved-resiliency&quot;&gt;Improved Resiliency&lt;/h3&gt;

&lt;p&gt;Peers now include improved heartbeating, and perform peer to peer &lt;a href=&quot;https://onyx-platform.github.io/docs/cheat-sheet/latest/#peer-config/:onyx.peer/subscriber-liveness-timeout-ms&quot;&gt;liveness
checks&lt;/a&gt;
on each other in addition to the existing ZooKeeper watches. Stuck or dead peers will
now be more readily detected by Onyx, ensuring tolerance to additional classes
of faults.&lt;/p&gt;

&lt;h3 id=&quot;simplified-plugin-interfaces&quot;&gt;Simplified plugin interfaces&lt;/h3&gt;

&lt;p&gt;The input and output plugin interfaces have been greatly simplified. The primary problem
with 0.9’s plugin interface is that the implementation details of how to asynchronously poll
for messages and handle failure bled out from the internals and into every plugin.
It also required developers to manually checkpoint state, which become untenable with each
new storage integration.&lt;/p&gt;

&lt;p&gt;0.10 provides this checkpointing for free as result of its ABS upgrade. All a plugin needs is to return the data
to be snapshotted, and the snapshot will be asynchronously recorded and restored by Onyx. Plugin projects
have seen a reduction of 2-6x code as a result.&lt;/p&gt;

&lt;h3 id=&quot;s3-checkpointing-backend&quot;&gt;S3 checkpointing backend&lt;/h3&gt;

&lt;p&gt;State checkpointing now supports durable writes to Amazon S3. BookKeeper is no
longer required for windowing/trigger support, greatly reducing operational
complexity of your Onyx cluster. Any interesting repercussion of writing entire
checkpoint blobs to S3 is that they’re now individually recoverable, instead of incrementally
restoring state out of a BookKeeper log. This is useful for directly accessing window state,
even when an Onyx cluster is offline.&lt;/p&gt;

&lt;h3 id=&quot;new-metrics&quot;&gt;New metrics&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus metrics&lt;/a&gt; endpoints are now provided via
&lt;a href=&quot;https://github.com/onyx-platform/onyx-peer-http-query&quot;&gt;onyx-peer-http-query&lt;/a&gt; in Onyx core,
rather than requiring an external project integration.
Numerous additional metrics are now also supported, giving even greater
introspection into your cluster.&lt;/p&gt;

&lt;h2 id=&quot;going-forward&quot;&gt;Going forward&lt;/h2&gt;

&lt;p&gt;It’s been about 15 months since the release of Onyx 0.9.0, and we’d like to thank the community
for its patience. Our release cycle was highly atypical this year, as we launched a commercial
&lt;a href=&quot;http://pyroclast.io&quot;&gt;streaming platform you may have heard about&lt;/a&gt;, built on top of Onyx.&lt;/p&gt;

&lt;p&gt;As we moved through growing our business, making sure we made Onyx’s streaming engine as resilient
and performant as possible was our highest priority, which is why we took a long time between releases
to nail down. We’ve also been running each Onyx release candidate in production through Pyroclast
for a few weeks before we put it out in the public to ensure that we pick off any bugs that might
have been missed through normal testing. We’ll continue to operate in this manner.&lt;/p&gt;

&lt;p&gt;Thanks for your continued support and for using Onyx!&lt;/p&gt;
</description>
        <pubDate>Mon, 10 Jul 2017 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2017/07/10/Onyx-Asynchronous-Barrier-Snapshotting.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2017/07/10/Onyx-Asynchronous-Barrier-Snapshotting.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>onyx-java: A Comprehensive Java API for Onyx</title>
        <description>&lt;p&gt;I’m excited to announce the official release of the &lt;a href=&quot;https://github.com/onyx-platform/onyx-java&quot;&gt;Java API for Onyx&lt;/a&gt;, closing &lt;a href=&quot;https://github.com/onyx-platform/onyx/issues/1&quot;&gt;Onyx’s oldest GitHub issue&lt;/a&gt;:
2 years, 9 months, 4 days after its creation. onyx-java is a comprehensive API that opens up access to virtually
every Onyx feature from Java - without writing a single line of Clojure.&lt;/p&gt;

&lt;p&gt;onyx-java is a significant advancement as it marks the first language other than Clojure to have end-to-end
support. The Java API is particularly interesting because it’s largely compatible with any version of Onyx.
By offering a set of interfaces that mimic the generic construction of an Onyx job’s constituent components:
workflow, catalog, lifecycles, and so forth - we avoid compatibility mismatches that result from binary level
integration. onyx-java acts as a translator from a fluent style API into the backing set of data structures
that Onyx understands.&lt;/p&gt;

&lt;p&gt;What’s most notable about the new API is its continued allowance to dynamically create and submit Onyx jobs
from runtime, something that Clojure makes easy, but is relatively challenging to implement in other languages.
Onyx’s architecture requires that runtime job creation need only communicate with ZooKeeper. By rigorously
conforming to the Clojure API for Onyx, onyx-java is able to achieve the same capabilities with ease.&lt;/p&gt;

&lt;p&gt;I’d like to thank George Kierstein and Ryan Berkheimer for contributing onyx-java to the Onyx Platform. Their hard work
will be appreciated by a large number of teams who have been requesting a Java API for a long, long time.&lt;/p&gt;

&lt;p&gt;Finally, I’d like to note that while it’s been a long time since Onyx has had a major public update, we’re right around the corner from the 0.10.0 official release. 0.10 is a complete rewrite of Onyx’s internal messaging and fault tolerance engine. While it’s been production ready for a number of months, we’re spending just a little while longer hammering out any remaining issues with the beta. We’ll likely have the official release out in the next 1-2 weeks.&lt;/p&gt;
</description>
        <pubDate>Mon, 15 May 2017 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2017/05/15/Onyx-Java.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2017/05/15/Onyx-Java.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>A Preview of Pyroclast: Enhanced Big Data Processing with Simulation</title>
        <description>&lt;p&gt;Distributed Masonry is pleased to offer the first public look at our newest product: &lt;a href=&quot;http://pyroclast.io/&quot;&gt;Pyroclast&lt;/a&gt;. Pyroclast is a fully-hosted cloud PaaS that extends the Onyx Platform’s core mission: to make large scale distributed data processing more economical for businesses. Working with high volumes of data typically involves curating a highly skilled, high salaried team of engineers, a significant investment in tooling, and a lengthy delivery schedule between the project start date and return on investment.&lt;/p&gt;

&lt;p&gt;Today’s post will dive into one of Pyroclast’s novel technical innovations for reducing the cost of working with big data: Simulation. If you’re interested in what you see in this post, you can &lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSfdUSjnM5yquDXRbGfDBwkzbF9ICQhOvkFm3E1fj74cVTnJxA/viewform?entry.1097412617&amp;amp;entry.1462136937&amp;amp;entry.927657393=To+request+a+trial+account.&amp;amp;entry.2043252775&amp;amp;entry.667239829&amp;amp;entry.130287182&quot;&gt;apply for early access&lt;/a&gt; to Pyroclast or &lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSfdUSjnM5yquDXRbGfDBwkzbF9ICQhOvkFm3E1fj74cVTnJxA/viewform?entry.1097412617&amp;amp;entry.1462136937&amp;amp;entry.927657393=A+guided+tour.&amp;amp;entry.2043252775&amp;amp;entry.667239829&amp;amp;entry.130287182&quot;&gt;request a demo&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-status-quo&quot;&gt;The Status Quo&lt;/h3&gt;

&lt;p&gt;When we take a look at what makes any activity expensive, the first thing to do is understand where we’re spending the majority of our time. In the construction and verification of high volume data pipelines, we assert that companies spend a large fraction of their time waiting. A number of activities involved in the creation of data pipelines are inherently slow, thus causing dead time.&lt;/p&gt;

&lt;p&gt;We categorize the prototypical stages of developing distributed data pipelines into the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Configuration and code authoring&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Compilation&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Testing&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Deployment&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Measurement&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As in the usual model of single-threaded, localized programming, engineers write, test, and compile code in a number of iterations and loops as they make progress. In terms of dead time, these activities are relatively efficient—particularly in dynamic environments that support read-eval-print loops.&lt;/p&gt;

&lt;p&gt;The catch with writing &lt;em&gt;distributed&lt;/em&gt; data pipelines is that compilation and deployment are painfully slow stages to pass through. In JVM environments, for example, compilation typically requires assembling the application and its dependencies into an uberjar. For non-trivial applications, this creates a lag on the order of minutes. Moreover, because we’re working in a distributed context, copies of the application package must be synced to all the nodes in the cluster—producing a lag of similar magnitude. Because the latter depends on the former, these phases cannot be pipelined. Hence: engineers working with big data tools end up spending a lot of their day checking Twitter waiting for something to finish.&lt;/p&gt;

&lt;h3 id=&quot;what-can-possibly-go-wrong&quot;&gt;What Can Possibly Go Wrong?&lt;/h3&gt;

&lt;p&gt;From a process perspective, the deterministic expense of a full compile/deploy cycle of a data pipeline application might seem like a one-time cost—something that is only done once we are absolutely sure our application is correct. In the real world, we tend to perform this cycle far more frequently. We believe that we have a correct program, only to repeatedly watch it fail in its natural habitat - a networked environment. A common reason for defects sneaking into applications is that, much like single threaded programming, bugs tend to exist at the &lt;em&gt;edges&lt;/em&gt; of components. Integration testing is a notoriously difficult exercise, and this effect is exacerbated when a localized execution environment differs from its fully distributed runtime. What seems a reasonable facsimile for development may prove to be chimerical, at the cost of further development cycles at best, and production defects at worst.&lt;/p&gt;

&lt;p&gt;Pyroclast chooses to overcome this difficulty by utilizing &lt;em&gt;Simulation&lt;/em&gt;. Simulation, as a general concept, allows us to iterate on a small scale, yet accurate, model of a full scale operation. In effect, Pyroclast’s Simulation offers a significantly shorter feedback cycle that makes it easier to explore scenarios and correct defects at the time of their creation.&lt;/p&gt;

&lt;h3 id=&quot;taking-feedback-from-minutes-to-milliseconds&quot;&gt;Taking Feedback from Minutes to Milliseconds&lt;/h3&gt;

&lt;p&gt;Short feedback cycles means increased visibility and less wasted time. In Pyroclast, data pipelines are expressed and visualized as directed, acyclic graphs of inputs, functions, and outputs.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/pipeline.png&quot; /&gt;
&lt;em&gt;A data pipeline that reads historical Wikipedia page view information from S3 and rolls up parsed search terms.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;With a pipeline created in Pyroclast, either through the in-browser editor, or uploaded programmatically, you have access to the &lt;em&gt;Simulator&lt;/em&gt;. The Simulator is a visualization of the &lt;em&gt;lineage&lt;/em&gt;, also known as &lt;em&gt;provenance&lt;/em&gt;, of records of data that pass through your data pipeline. When a single record is fed through the Simulator, the corresponding inputs and outputs of every node in the pipeline will be displayed. As we discussed earlier, defects tend to situate themselves at the &lt;em&gt;edges&lt;/em&gt; of a components. Pyroclast’s simulator visualizes and updates each edge on every keystroke.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/data-typeahead.gif&quot; /&gt;
&lt;em&gt;Typing data in the simulator updates its lineage trace in real time.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;This behavior applies in a reciprocal manner: changes to the data pipeline itself will instantaneously be reflected to the data inside the Simulator. In practice, one begins feel as if data is being &lt;em&gt;grown&lt;/em&gt; from its origins.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/pipeline-typeahead.gif&quot; /&gt;
&lt;em&gt;Adjusting the data pipeline automatically reruns the Simulator on every change.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Feedback is instant - often on the order of single-digit milliseconds. Pyroclast is able to achieve this by virtualizing distributed activity inside the browser. When the Simulation is run on each keystroke, &lt;strong&gt;no internet activity takes place&lt;/strong&gt;. Pyroclast’s Simulation makes informal reasoning a breeze, but has paved the way to formal verification features like mass data scenario testing, dynamic specifications, and invariant assertions.&lt;/p&gt;

&lt;h3 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h3&gt;

&lt;p&gt;Pyroclast is able to offer a fluid simulation experience by building on top of Onyx’s programming model. Onyx programs are modeled by EDN data structures - and nothing else. Leaving simulation aside for the moment, as data pipelines are crafted inside Pyroclast, a backing Onyx representation is maintained in memory. On every keystroke and mouse click, &lt;strong&gt;Pyroclast dynamically recompiles an underlying distributed program&lt;/strong&gt;. The compilation is fast - typically a couple of milliseconds. This, however, isn’t enough to achieve a fully interactive experience. The backing Onyx model needs an execution environment.&lt;/p&gt;

&lt;p&gt;Onyx “proper” runs on the Java Virtual Machine - a server side technology. onyx-local-rt is a stateless, threadless implementation of Onyx that can run on either the JVM or in JavaScript thanks to the Clojure programming language. onyx-local-rt shares virtually all of its code with Onyx through Clojure’s conditional reader, meaning that it is negligible cost to ensure that both environments are lock-step in their behavior.&lt;/p&gt;

&lt;p&gt;Wielding Pyroclast’s on-the-fly compiler, we can capture changes in both data and code to trigger a fresh simulation cycle near-instantly. Some additional steps have been taken to cull intermediate data values (e.g. edge display) and allow backtracking from output data back through to its inputs.&lt;/p&gt;

&lt;h3 id=&quot;what-if-demo-day-was-day-1&quot;&gt;What if Demo Day was Day 1?&lt;/h3&gt;

&lt;p&gt;In this post, we explored the most basic features of Pyroclast’s Simulator. Simulation is a powerful technique for cutting the feedback cycle that engineers traditionally endure from minutes down to milliseconds. Instantaneous feedback behind a unified data processing application means that members of all roles your business can transform and explore high volumes of real time data. Instead of mobilizing an engineering team and waiting 3 months for the first demo, simulation lets you demo on Day 1.&lt;/p&gt;

&lt;p&gt;We’ve barely scratched the surface in today’s discussion. In future posts, we’ll dive deeper into more Pyroclast features, including a realtime visualization of the Google Dataflow/Apache Beam model, seamless transition between realtime and batch computation, deep parametric deployments, and more. If you liked what you saw in this post, you can &lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSfdUSjnM5yquDXRbGfDBwkzbF9ICQhOvkFm3E1fj74cVTnJxA/viewform?entry.1097412617&amp;amp;entry.1462136937&amp;amp;entry.927657393=To+request+a+trial+account.&amp;amp;entry.2043252775&amp;amp;entry.667239829&amp;amp;entry.130287182&quot;&gt;apply for early access for your company&lt;/a&gt; or &lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSfdUSjnM5yquDXRbGfDBwkzbF9ICQhOvkFm3E1fj74cVTnJxA/viewform?entry.1097412617&amp;amp;entry.1462136937&amp;amp;entry.927657393=A+guided+tour.&amp;amp;entry.2043252775&amp;amp;entry.667239829&amp;amp;entry.130287182&quot;&gt;request a demo&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 08 Feb 2017 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2017/02/08/Pyroclast-Preview-Simulation.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2017/02/08/Pyroclast-Preview-Simulation.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>Onyx Local Runtime</title>
        <description>&lt;p&gt;I’m pleased to announce the release of a new library for Onyx: &lt;a href=&quot;https://github.com/onyx-platform/onyx-local-rt&quot;&gt;onyx-local-rt&lt;/a&gt;.
onyx-local-rt is an alternate runtime for Onyx that is pure and deterministic. This runtime, as the name suggests,
differs from the distributed runtime in that it only runs locally. onyx-local-rt has no dependencies aside from Onyx itself,
supports nearly the full functional API, and is capable of running in ClojureScript.&lt;/p&gt;

&lt;p&gt;Backing up for a moment, we can break Onyx down into two pieces: a programming model, and a runtime. The programming model (workflows,
catalogs, lifecycles, flow conditions, windows, and triggers) is itself an information model. That is, the means by which
Onyx programs communicate their instructions are solely through data structures. Onyx’s programming model, by consequence,
makes it easy to execute jobs on any platform that adheres to the published information model.&lt;/p&gt;

&lt;p&gt;For most users, the distributed runtime works fine. The majority of developers that are using Onyx require a solution that is
very high performance, resilient to faults, and widely extensible to other databases and storage engines. However,
we are increasingly seeing larger number of users who would like to use Onyx’s programming model without engaging
it in a distributed context. For use cases where fault tolerance isn’t requirement, onyx-local-rt offers a &lt;em&gt;very&lt;/em&gt; simple
way to run Onyx programs as easily as any other Clojure program.&lt;/p&gt;

&lt;p&gt;One particularly interesting use case for onyx-local-rt is testing. Executing a test with set up and tear down on the distributed, in-memory Onyx
components takes ~800 milliseconds on my MacBook Pro. Executing that same test takes under 15 milliseconds with onyx-local-rt.
In some sense, onyx-local-rt is the ultimate repl tool. Iterate on your Onyx programs for functional correctness using
the local runtime, then switch to the distributed runtime when you’re ready to tune parallelism and integrate with distributed storage.&lt;/p&gt;

&lt;p&gt;onyx-local-rt is dead easy to use. Call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; with an Onyx job. Add new segments to an input with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-segment&lt;/code&gt;. Transition
the runtime step-by-step into the future by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tick&lt;/code&gt;, or as a convenience, invoke &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drain&lt;/code&gt;, which will call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tick&lt;/code&gt;
on your behalf until all inflight messages have completed their path through the job. Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new-segment&lt;/code&gt; in conjunction
with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drain&lt;/code&gt; allows for integrating with custom, user-implemented fault handling. Call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tick&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drain&lt;/code&gt; as frequently
(or infrequently) as you’d like - thus giving you maximum control of resource consumption for performance.&lt;/p&gt;

&lt;p&gt;Try it out (Can I say it again? It works in ClojureScript, too!):&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:require&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;onyx-local-rt.api&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:as&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;;; ^:export the function if using in ClojureScript.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;defn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:export&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my-inc&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;segment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update-in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;segment&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;inc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:workflow&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:inc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:inc&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:catalog&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:input&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/batch-size&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
             &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:inc&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/fn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;::my-inc&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/batch-size&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
             &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:out&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:output&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/batch-size&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:lifecycles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pprint&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;api/init&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;api/new-segment&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;41&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;api/new-segment&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;84&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;api/drain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;api/stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;api/env-summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;;; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;;; {:next-action :lifecycle/start-task?,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;;;  :tasks&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;;;  {:in {:inbox []},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;;;   :inc {:inbox []},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;;;   :out {:inbox [], :outputs [{:n 42} {:n 85}]}}}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We hope that onyx-local-rt will serve as a fundamental tool for developing Onyx programs in the future. Keep rocking!&lt;/p&gt;
</description>
        <pubDate>Tue, 18 Oct 2016 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2016/10/18/Local-Runtime.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2016/10/18/Local-Runtime.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>Distributed Masonry Raises $500,000 Seed Round for Onyx Platform</title>
        <description>&lt;p&gt;2016 has been an exciting time for Onyx. Over the last year, Onyx has been widely adopted by companies with high volumes of data and strict latency requirements. Distributed Masonry, the entity that commercially supports Onyx, has had the privilege of seeing the platform used to solve a wide variety of problems. After more than a year of providing consulting services and training, Distributed Masonry has taken the next step and has begun build a new product on top of Onyx. We are pleased to announce that we have raised an initial seed round of $500,000, which will accelerate development of this product as well as Onyx core.&lt;/p&gt;

&lt;p&gt;So, what &lt;em&gt;exactly&lt;/em&gt; are we building? To answer that question, we have to go back to the primary goal that Onyx was built to solve. Onyx was intended to substantially reduce the cost of building distributed computation pipelines. In many ways, this has succeeded beyond what we could have hoped for. We’ve seen teams build complex systems requiring highly dynamic behavior at accelerated speeds. Onyx was architected to handle modern data processing problems by embracing the principles that many already understand help at the language level: immutability, simple data types, and pure functions.&lt;/p&gt;

&lt;p&gt;Over time, we have learned that there are areas of this problem that would benefit from an integrated solution. Particularly complex problems are innate to building reliable, consistent, and fault tolerant data systems. In particular, networking, dev ops, and monitoring stand out as impediments when building fast, reliable data pipelines. While terms such as “serverless” have been on the rise, we are going in a different direction. We do not wish to pretend that there are not servers involved, and that your code does not affect outcomes. Instead, we wish to improve your ability to deploy, manage, and scale the servers operating your data pipelines, monitor them, and meet your outcomes. This will be achieved by giving users insights into how their pipelines are behaving, and how to solve their  problems.&lt;/p&gt;

&lt;p&gt;Onyx’s design model has intrinsically decreased time-to-market and engineering effort delivering data pipelines. We’re happy to share that we’ve begun work on a next-generation service of a combined streaming IDE, and Platform as a Service built on top of Onyx.&lt;/p&gt;

&lt;p&gt;Harnessing Onyx’s design model, we’re able to do something that few other technologies can do - offer a trade-off between lower levels of abstraction, such as the medium e.g. SQL, Kafka, Redis, HTTP, S3, all the way to APIs built on these mediums, e.g. Mailchimp, S3 serialization formats, etc. To facilitate higher abstractions, we recently introduced “task bundles” which reduce incidental complexity for developers. In addition to a fully hosted environment, users will be able to select from a library of task bundles - concise computational blocks that can be linked to each other.&lt;/p&gt;

&lt;p&gt;We’re thrilled to reveal what’s in progress in the coming months. Onyx’s salient goal is to reduce complexity and decrease engineering costs, and with our up-and-coming services, you’ll have a unique path to move from zero to production faster than ever.&lt;/p&gt;

&lt;p&gt;Finally, we’d like to clearly address a number of concerns:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Onyx will remain open source.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Onyx will continue to be actively worked on, and will form a key part of our managed offering, and will be dogfooded as such.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Onyx will not be relicensed, and will remain protected under the Eclipse Public License.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Commercial support for companies using Onyx in-house remains available.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general, everything will remain exactly as is. Our product is built directly on top of all of the existing Onyx infrastructure, and will continue to be the workhorse that powers our latest creations.&lt;/p&gt;
</description>
        <pubDate>Wed, 17 Aug 2016 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2016/08/17/Funding.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2016/08/17/Funding.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>Onyx 0.9.7: How We Handle High Risk Design Changes</title>
        <description>&lt;p&gt;I’m pleased to announce the release of &lt;a href=&quot;https://github.com/onyx-platform/onyx&quot;&gt;Onyx&lt;/a&gt; 0.9.7. This release features a major design upgrade to improve cluster-wide scalability. We’ve also shipped a number of highly requested usability fixes that will make developing with Onyx much more pleasant. See the full &lt;a href=&quot;https://github.com/onyx-platform/onyx/blob/0.9.x/changes.md#097&quot;&gt;changelog&lt;/a&gt; for all patches to 0.9.7. We’re frequently asked about the maturity of Onyx, so in this post, I’m going to discuss the design changes that we made - specifically in the context of our testing strategy. Onyx is built to run in production-grade environments, and we treat every aspect of a release with that in mind.&lt;/p&gt;

&lt;h2 id=&quot;the-log-based-architecture&quot;&gt;The Log-based Architecture&lt;/h2&gt;

&lt;p&gt;Onyx’s masterless, log-centric architecture has been widely &lt;a href=&quot;https://github.com/onyx-platform/onyx/blob/5f25b6373c6840e185db07ebc90ee6046f69bef0/doc/user-guide/architecture-low-level-design.md#onyxs-architecture-and-low-level-design&quot;&gt;written about&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=KVByn_kp2fQ&quot;&gt;discussed&lt;/a&gt;. To summarize quickly, Onyx is built around the idea of using completely independent worker processes, called peers, that interact strictly through a sequentially ordered sequence of messages. This architecture eschews the notion of a centralized coordinator, and consequently offers developers two important primitives that few other distributed data processors provide - atomic broadcast, and cluster-wide event subscription. Each of these primitives, exposed first-class through Onyx’s API, have helped companies build powerful tools to extend Onyx’s capabilities.&lt;/p&gt;

&lt;p&gt;In this design, all peer processes maintain a local copy of a &lt;em&gt;replica&lt;/em&gt;, which contains structural information about the cluster as of a particular value of a logical clock. Peers use the replica to circumvent the need for a centralized coordinator, thus reducing a shared dependency, and a problematic area of design for many leader/followers architectures.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;80%&quot; src=&quot;/assets/images/097/1.svg&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;We started working on this design about a year and a half ago, and it’s since solidified. We’re happy with it - we’ve been able to skip over problems that more traditional architectures encounter, and offer a unique feature set on top of it. We hadn’t solved every problem, though. Trade-off’s were made to achieve this architecture, and it was time to address a critical bottleneck with this release.&lt;/p&gt;

&lt;p&gt;The most pressing challenge is interestingly not in the design itself, but in its implementation. As things stood before 0.9.7, Onyx took a strong shared-nothing approach between virtual peers on the same machine. Some performance intensive components, specifically those related to &lt;a href=&quot;https://github.com/real-logic/Aeron&quot;&gt;Aeron&lt;/a&gt; had been multiplexed long ago, but by and large state has traditionally not been shared across virtual peers. These things include socket connections to ZooKeeper, replicas, I/O core.async channels, and asynchronously threaded maintainance loops. By not sharing these things, we were increasing memory footprint, wasting CPU cycles updating a redundant log, and needlessly maintaining multiple ZooKeeper connections open.&lt;/p&gt;

&lt;p&gt;None of these things were shared because it was &lt;em&gt;easy&lt;/em&gt;. Sharing state is hard, and if you can avoid it, you should. The further we push Onyx, though, the more atypical our needs become. In order to scale better, we needed to carefully share these stateful components across all virtual peers, and maintain them only once per machine. We knew that if we could pull this off, we’d reduce localized overhead for most deployments by a factor of 8-32x.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;100%&quot; src=&quot;/assets/images/097/2.svg&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;design-change&quot;&gt;Design Change&lt;/h2&gt;

&lt;p&gt;The design change, on the surface, looks straight forward. We pulled all the aforementioned shared state into a single component and established an interprocess communication channel to propagate updates in a multiplexed manner.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;100%&quot; src=&quot;/assets/images/097/3.svg&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;This &lt;em&gt;looks&lt;/em&gt; like a simple change, and I think this is where a lot of distributed systems get burned. Any time you’ve reduced isolation in a design, the number of bugs that can creep in during the refactoring grows because you’re implicitly introducing new relationships between components. We went through two separate redesigns to make this change happen, and then put it through the ringer of our extensive testing suite.&lt;/p&gt;

&lt;h2 id=&quot;making-changes-with-confidence&quot;&gt;Making Changes with Confidence&lt;/h2&gt;

&lt;p&gt;Onyx’s testing process is an intriguing one that uses advanced techniques to sniff out problems. We knew that this was a high risk design change and wanted to make sure that we covered as many correctness and resiliency scenarios as possible. Our two primary tools were test.check and Jepsen. Using both of these techniques together is brutal. We were able to find bugs that likely wouldn’t have manifested themselves for months after the release. Using test.check and Jepsen together was spectacularly effective to the point where we ended up pushing our release out 4 weeks further than estimated to fix problematic areas of the design. Time well spent.&lt;/p&gt;

&lt;h3 id=&quot;testcheck&quot;&gt;test.check&lt;/h3&gt;

&lt;p&gt;Onyx’s core tests include a large number of property based tests to verify everything from our cluster membership algorithm, to windowing, and even our work scheduler. We have a tradition of writing a handful of property-based tests that span a large number of scenarios in one shot. This allows us to look at complex linearizations of tasks that most development teams would have a hard time tracking down. True to our style, we added another module to our test.check suite which exercised the socket and replica multiplex changes that we described above.&lt;/p&gt;

&lt;p&gt;The unique thing about test.check is that it’s one of the few testing techniques that can quickly &lt;em&gt;teach you&lt;/em&gt; about your own design. By producing a sequence of events you hadn’t thought of and seeing how your system behaves under test, you learn where your blind spots are in the analysis phase of your work. We truly believe test.check is a game-changing skill if you can get good at it.&lt;/p&gt;

&lt;h3 id=&quot;jepsen&quot;&gt;Jepsen&lt;/h3&gt;

&lt;p&gt;After iterating on test.check for a while, we put the candidate release through our &lt;a href=&quot;https://github.com/onyx-platform/onyx-jepsen&quot;&gt;Jepsen testing harness&lt;/a&gt;. As thorough as test.check is, Jepsen immediately tore our changes apart. We often bounce back and forth between test.check and Jepsen. The former offers rapid feedback about the general logic of a system, and the latter shows you how the edges of your system holds up in real failure scenarios. We ended up not needing to add much to our Jepsen harness for the design change. We paid the price of writing a Jepsen harness once and reap the benefits multiple times over down the road.&lt;/p&gt;

&lt;h2 id=&quot;looking-forward&quot;&gt;Looking Forward&lt;/h2&gt;

&lt;p&gt;After multiple design, implementation, and test iterations, we’re confident that Onyx has been shipped in a stable state. Now that we’ve tackled our largest outstanding scalability issue, we’re returning to the performance front. We’ll soon be revealing our next generation streaming engine. We’ve made some novel advancements on top of industry-tested research, and expect to see significantly higher messaging speeds.&lt;/p&gt;

&lt;p&gt;Thanks for all the contributions! It continues to be a pleasure for Distributed Masonry to &lt;a href=&quot;https://onyx-platform.github.io/support/&quot;&gt;support Onyx&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 06 Jul 2016 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2016/07/06/Onyx-0.9.7-Refactoring.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2016/07/06/Onyx-0.9.7-Refactoring.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>Task Bundles</title>
        <description>&lt;p&gt;Onyx’s strength has always been it’s data-driven nature thanks to
its data based API. This API can be thought of as a low-level language for your
distributed computation.&lt;/p&gt;

&lt;p&gt;A data structure based API forces you to understand and decide all-the-things
upfront. A common approach to conquer this problem is to include a sort of
meta-language in the data structure. In all but the simplest cases, this leads
to recreating a new programming language in your data structures.
It’s much better to use what we already have.&lt;/p&gt;

&lt;p&gt;We have wonderful tools for manipulating data structures, they are
built into Clojure! We routinely use these tools to build abstractions for
ourselves. They allow us to move up and down conceptual levels, all the way
from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(start-server ...)&lt;/code&gt; down to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(.readLine (io/reader (ServerSocket. 80)))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In order for the abstractions we build to be useful for others, or useful in the
in the context of a larger system, they must share a common contract for
composition. By doing this, the overall abstraction does not matter as what it
produces can just snap into place.&lt;/p&gt;

&lt;p&gt;In other words, &lt;strong&gt;Legos for Onyx&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Over the last few months, we’ve been refining our set of abstractions over Onyx’s
data API. The pattern is referred to as “task bundles”, and the core of it exists
in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onyx.job&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3 id=&quot;task-bundle-map&quot;&gt;Task Bundle Map&lt;/h3&gt;
&lt;p&gt;Task bundles operate on “task bundle maps”, a shared contract.
A task bundle map is just a plain clojure map of the shape&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:task-map&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:inc&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/fn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;::inc-viewcount&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/batch-size&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:myproject/special-value&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/batch-timeout&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:lifecycles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:flow-conditions&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:windows&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:triggers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:schema&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:task-map&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:myproject/special-value&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s/Num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:lifecycles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:myproject/special-state&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s/Str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:windows&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:triggers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A “task bundle map” made of two parts. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:task&lt;/code&gt; key represents Onyx
declarations. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:schema&lt;/code&gt; key represents constraints on those declarations.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-task&lt;/code&gt; will check the constraints against the declarations.&lt;/p&gt;

&lt;p&gt;The overarching idea with task bundles, is to take everything required for a
task type, from the task map, to the flow conditions, or windows, and bundle
them together in a way that promotes re-use of task types that you may build.&lt;/p&gt;

&lt;h3 id=&quot;tasks&quot;&gt;Tasks&lt;/h3&gt;

&lt;p&gt;We package up “task bundles” into “tasks”, which are just functions that build the data in the required form.
We do this so that we have an easy entry-point to change the “task bundle map”.&lt;/p&gt;

&lt;p&gt;See below where we allow changing the key sequence (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ks&lt;/code&gt;) that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inc-in-segment&lt;/code&gt;
function operates on.&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;s/defn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inc-key&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s/Keyword&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
   &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:task-map&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                             &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                             &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/fn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;::inc-in-segment&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                             &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/params&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;::inc-key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:schema&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:task-map&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IncKeyTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s/Keyword&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ks&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s/Keyword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
   &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inc-key&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;::inc-key&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The two-arity form allows users of the task to see what they &lt;em&gt;need&lt;/em&gt; to provide
to have a valid task bundle. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::inc-in-segment&lt;/code&gt; will not work unless we specify
a key sequence to the number we want to increment. To signal this, we both
provide constraints on it and include it in the second arity.&lt;/p&gt;

&lt;h3 id=&quot;task-bundle-modifiers&quot;&gt;Task Bundle Modifiers&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onyx.job/add-task&lt;/code&gt; provides a second variadic arity, taking a function of the
form:&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-bundle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-bundle-map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This allows us to bundle meta behavior  and apply it
to task bundles before they are schema checked and merged. This is useful for
things like adding logging, triggers, or further restricting schema.
An example of adding a trigger to send window state to MySQL is below.&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;s/defn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;with-trigger-to-sql&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window-id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s/Keyword&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection-uri&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s/Str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-definition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task-definition&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update-in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:triggers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;conj&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/window-id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window-id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/refinement&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx.refinements/accumulating&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/on&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx.triggers/segment&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/threshold&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:elements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/sync&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:twit.persist.sql/upsert-trending&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:sql/connection-uri&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:connection-uri&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection-uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update-in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:schema&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:triggers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;conj&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:sql/connection-uri&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:connection-uri&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s/Str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}))))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with-trigger-to-sql&lt;/code&gt; add’s new Onyx declarations to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:task&lt;/code&gt; key (triggers),
and new constraints. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-task&lt;/code&gt; will check these new constraints just the same.
Convention is to name task bundle modifier functions &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with-*&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;jobs&quot;&gt;Jobs&lt;/h2&gt;
&lt;p&gt;“tasks” and “task bundle modifiers” are all about encapsulating functionality to
assist with reuse. Right now, most of the Onyx plugins provide a task bundle
interface under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onyx.tasks.&amp;lt;plugin-name&amp;gt;&lt;/code&gt;. This allows us to avoid dealing
directly with the Onyx job map, making our jobs &lt;a href=&quot;https://github.com/onyx-twitter-sample/twit/blob/master/src/twit/jobs/trending.clj&quot;&gt;look like this&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:workflow&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:reshape-segment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:reshape-segment&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:split-hashtags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:split-hashtags&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:catalog&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:lifecycles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:windows&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:triggers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:flow-conditions&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:task-scheduler&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx.task-scheduler/balanced&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add-task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;twitter/stream&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:createdAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch-settings&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;twitter-config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add-task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reshape/reshape-segment&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:reshape-segment&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch-settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add-task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tweet/emit-hashtag-ids&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:split-hashtags&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch-settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add-task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tweet/window-trending-hashtags&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:out&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:hashtag-window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add-task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;core-async-task/output&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:out&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch-settings&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/group-by-key&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:hashtag&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                                                                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/flux-policy&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:recover&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                                                                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/min-peers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                                                                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/max-peers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                                                                  &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx/uniqueness-key&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add-task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tweet/with-trigger-to-sql&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:hashtag-window&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection-uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/onyx-platform/onyx-twitter-sample&quot;&gt;Onyx Twitter Sample&lt;/a&gt;
holds a testing namespace for each job showcasing the benefits of this DSL
approach. It also demonstrates other related concepts like job registration and
submission. We intend this to be a community showcase of Onyx functionality that
will grow over time. Feel free to add extra jobs demonstrating other Onyx
features, or suggestions for job ideas.&lt;/p&gt;
</description>
        <pubDate>Mon, 13 Jun 2016 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2016/06/13/Task-Bundles.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2016/06/13/Task-Bundles.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>Onyx 0.9.6: Advanced Static Analysis</title>
        <description>&lt;p&gt;I’m excited to reveal one of the most exciting usability enhancements yet to the &lt;a href=&quot;https://github.com/onyx-platform/onyx&quot;&gt;Onyx Platform&lt;/a&gt;. Onyx version 0.9.6 contains a revamped module for performing static analysis on job submission. In this post, we’ll look at how Onyx uses static analysis, what the benefits are, and how we’ve significantly advanced upon our previous work.&lt;/p&gt;

&lt;h3 id=&quot;data-driven-apis-a-double-edged-sword&quot;&gt;Data Driven APIs: A Double Edged Sword&lt;/h3&gt;

&lt;p&gt;Onyx’s dominant quality is that it uses a purely data driven information model as its first class API. That is, developers use data structures to describe the vast majority of the distributed programming model that Onyx exposes. This attribute, coupled with judicious use of Clojure code to express behavior, is a tremendously powerful weapon for creating ultra-flexible distributed applications. Onyx’s qualities make it malleable enough to feel like a Lisp for programming distributed systems.&lt;/p&gt;

&lt;p&gt;Flexibility, of course, always comes with a cost. One strong advantage of using a code-level API is that the implementer can make use of the programming language’s type checker to statically look for some classes of errors. For languages with sophisticated type systems, end users instantly benefit from the work that the language designers already put in place.&lt;/p&gt;

&lt;p&gt;Interfaces for pure data typically don’t receive this perk. There are a limited number of data structures - maps, sets, lists, vectors, to name a few. A dynamic runtime, particularly Clojure’s, gives you very little in the way of static analysis - by design. Developers often compensate for this by using &lt;a href=&quot;https://github.com/plumatic/schema&quot;&gt;Schema&lt;/a&gt;. Schema can be thought of as a light-weight type checker. It allows users to adorn data with a contractual specification of types and semantics, and generally enforces that the “shape” of a data structure is consistent throughout its usage at run-time.&lt;/p&gt;

&lt;p&gt;Shortly after Onyx’s open source release, we, too, adapted Schema. Schema is a great step forward for analyzing Onyx jobs for syntactic and semantic errors, such as a malformed workflow, or an incomplete catalog. Schema’s error messages, however, leave much to be desired. When a constraint is violated, Schema throws an exception packed with a data structure describing the failure. You surely recognize something like this:&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;100%&quot; src=&quot;/assets/images/better-errors/schema-error.png&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;I’ve been using Schema for a long time. I can decipher what this means, and I’ve gotten used to it. But that doesn’t mean we &lt;em&gt;should&lt;/em&gt; get used to it. Onyx has been designed to be the luxury vehicle for distributed data processing. We knew we could improve the usability of displaying and explaining errors if we were careful.&lt;/p&gt;

&lt;p&gt;Onyx 0.9.6 is bundled with a new module for static analysis that overlays Schema’s error messages with ones that are significantly more readable. Instead of a stack trace, you’ll now receive a print-out as seen below (and a reference the original exception object that Schema threw, too):&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/type-error.png&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;Now when you make an error, you’ll receive a print out of the erroneous data structure, console highlighting with a pointer directly to the problematic area, an explanation of what went wrong, and, if possible, the corresponding documentation for the malformed information.&lt;/p&gt;

&lt;p&gt;Here are a few more examples. Notice that under some circumstances, we’ll try to make a guess about what you meant when things are &lt;em&gt;close&lt;/em&gt; to right:&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/invalid-key-error.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/workflow-type-error.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/workflow-arity-error.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/semantic-error.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/relational-error.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/missing-key-error.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/multi-key-error.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/better-errors/deprecated-error.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;All of Onyx’s job data structures use the new static analyzer module. We’ll soon be transitioning all configuration options over too, which include the environment and peer configuration data structures. We also have plans to backport all of this work into a stand-alone library that can be used against arbitrary data structures.&lt;/p&gt;

&lt;p&gt;Thanks for using Onyx. We hope you thoroughly enjoy the upgrade as much as we do.&lt;/p&gt;
</description>
        <pubDate>Thu, 05 May 2016 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2016/05/05/Onyx-0.9.6-Advanced-Static-Analysis.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2016/05/05/Onyx-0.9.6-Advanced-Static-Analysis.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>Engraver: A Tool for Managing Onyx Clusters</title>
        <description>&lt;p&gt;&lt;strong&gt;Deprecation notice: We’ve discontinued support for this product. We recommend directly using tools like Kubernetes for Onyx deployment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re pleased to announce the release of our newest creation - &lt;a href=&quot;https://github.com/onyx-platform/engraver&quot;&gt;Engraver&lt;/a&gt;. Engraver is a tool for managing and deploying &lt;a href=&quot;https://github.com/onyx-platform/onyx&quot;&gt;Onyx&lt;/a&gt; cluster infrastructure. We’ve developed Engraver to address &lt;em&gt;operational concerns&lt;/em&gt;, one of the most painful aspects of dealing with large distributed applications in any ecosystem. Engraver is our recommended approach for building production-grade clusters, though its usage remains entirely optional. In this post, we’ll discuss what Engraver is, why we built it, and how you can get up and running in just a few minutes.&lt;/p&gt;

&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/engraver/engraver_describe.png&quot; /&gt;
&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;/h3&gt;

&lt;p&gt;One of the most difficult challenges in software engineering is taking your system from the developer’s laptop all the way to the data center with minimal disruptions and surprises along the way. When we look at the plethora of tools that are available to us to ease the journey, we can label them as either being more &lt;em&gt;general&lt;/em&gt; or &lt;em&gt;specific&lt;/em&gt; dev-ops tools.&lt;/p&gt;

&lt;p&gt;DevOps tools like &lt;a href=&quot;https://www.ansible.com/&quot;&gt;Ansible&lt;/a&gt;, &lt;a href=&quot;https://www.chef.io/&quot;&gt;Chef&lt;/a&gt;, and &lt;a href=&quot;https://puppetlabs.com/&quot;&gt;Puppet&lt;/a&gt; have become sufficiently advanced and are now common-place among most teams. Containers have begun to mature, opening up a brand new world of portability ruled by &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt;. Execution environments such as &lt;a href=&quot;http://mesos.apache.org/&quot;&gt;Mesos&lt;/a&gt; and &lt;a href=&quot;http://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; continue to stabilize, offering large-scale management capabilities previously known only to big-house engineering names. These tools are agnostic to any particular implementation language, operating system, or development platform - hence making them generalized.&lt;/p&gt;

&lt;p&gt;At the other end of the spectrum, we can talk about tools that aid in the deployment of specific types of technologies. &lt;a href=&quot;clojars.org/&quot;&gt;Clojars&lt;/a&gt; is a perfect example. We don’t think about it often, but Clojars is a fit-to-purpose tool for moving jars to a binary repository in the cloud that we use all the time for release orchestration. Combined with Leiningen’s ability to search a set of repositories, this set up works so well that most people don’t think about it anymore.&lt;/p&gt;

&lt;p&gt;There has been significantly less progress on the front of creating good technology-specific deployment tooling. While we’ve seen a blaze of activity on the generalized direction, the situation remains virtually stagnant at the other end of the spectrum. Since Engraver is a tool to aid in building Onyx cluster infrastructure, we’ll drill in on the tooling that’s beneficial for distributed computation networks.&lt;/p&gt;

&lt;p&gt;Since the early days of Hadoop, the defacto way of moving your application specific code (an uberjar, most of the time) onto worker machines has been to “submit” your application to a master node, which in turn orchestrates the transfer of your binary artifact to the worker machines through something similar to SCP. This method of release coordination was mainly driven by the desire to colocate “code” with “data” in the Hadoop world - so it made sense at the time to take control over the deployment process by baking it straight into the platform. Fast forward 15 years to 2016. Network speeds in data centers have become fast enough to make the difference between local and remote disks transparent.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;50%&quot; src=&quot;/assets/images/engraver/scp.svg&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;Despite this fundamental pivot in hardware performance, the “SCP-your-jar” approach to release orchestration remains the dominant process for deployment. Hadoop has not moved past this ancient practice, but more than that - we also see the same pattern in Storm, Spark, Flink, and other technologies. Onyx was designed from day 1 to be used as a library, thus overcoming the dangers of locking development teams into a One True deployment methodology. Since Onyx’s creation more than 2 years ago, the decision to omit an internal deployer has paid off in spades. Cloud environments have become &lt;em&gt;more&lt;/em&gt; ephemeral and 3rd party interactions even &lt;em&gt;more&lt;/em&gt; pervasive. Containers completely sealed the deal.&lt;/p&gt;

&lt;h3 id=&quot;general-tools-arent-enough&quot;&gt;General Tools Aren’t Enough&lt;/h3&gt;

&lt;p&gt;It’s fair to question whether a whole new tool, such as Engraver, is necessary. Can’t we simply use existing generalized tools like Ansible and Chef and get along okay? For the most part, we can be relatively successful about getting 80% of our system smoothly deployed. The remaining 20%, which is typically specific to the application platform that’s being used, can become brutally painful without the right tooling in place. Having run Onyx (and similar systems) in production a number of times, similar situations inevitably arise that unnecessarily skyrocket the cost of these projects.&lt;/p&gt;

&lt;h4 id=&quot;networking&quot;&gt;Networking&lt;/h4&gt;

&lt;p&gt;Services and applications need to have all of their ports properly forwarded on the correct network interfaces over their correct protocols. No one enjoys playing the game of running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netcat&lt;/code&gt; over machines in your cluster to brute-force hunt down port exposure problems. Network configuration virtually always falls on the developer to handle - even if the set of services being deployed is the same each time. Engraver ships a number of modular services that we’ve preconfigured ahead of time to automatically adjust network settings for smooth communication.&lt;/p&gt;

&lt;h4 id=&quot;security&quot;&gt;Security&lt;/h4&gt;

&lt;p&gt;Did you lock down all of your ports? Can outside machines talk to boxes in your data center? Are your CIDR blocks correct? It’s all too easy to temporarily modify a security setting to temporarily enable network traffic, only to forget about it later. Again - network level security continues to be the burden of the developer, even when we can anticipate a service’s network availability needs. Engraver locks everything down by default, and automatically adjusts security settings based on the services that you use in your project.&lt;/p&gt;

&lt;h4 id=&quot;highly-available-configuration&quot;&gt;Highly Available Configuration&lt;/h4&gt;

&lt;p&gt;Going to freak out if you have to configure ZooKeeper or Kafka for high availability one more time? Me too. Docker doesn’t entirely solve this problem because public container images are frequently lower quality than one would hope, and because of variations in network topologies. Engraver services are preconfigured in clustered, highly available mode. Your failover is ready.&lt;/p&gt;

&lt;h4 id=&quot;machine-level-scaling&quot;&gt;Machine-level Scaling&lt;/h4&gt;

&lt;p&gt;It’s become easy enough these days to scale the amount of resources that a single application receives, but how about automatically provisioning new machines from a cloud environment and adding them to the mix? How easily can you fetch more physical resources and notify all existing services? It’s not always as smooth as we’d hope. Engraver lets you scale your cluster with one simple command. Everything else just works when you scale up or down (thanks to a combination of Ansible and Docker under the hood!)&lt;/p&gt;

&lt;h3 id=&quot;marathon-and-kubernetes&quot;&gt;Marathon and Kubernetes&lt;/h3&gt;

&lt;p&gt;At this point, a seasoned devops engineer will notice that products like &lt;a href=&quot;https://mesosphere.github.io/marathon/&quot;&gt;Mesos Marathon&lt;/a&gt; and Kubernetes actually are capable of dealing with many of the pain points that we’ve outlined. We think Kubernetes and the related tools in this space are &lt;em&gt;fantastic&lt;/em&gt;, but we’ve come to realize that getting sophisticated tools set up is a chore in itself. For a handful of teams, this just a little &lt;em&gt;too much&lt;/em&gt; new tech to handle all at once. Engraver is aimed at giving developers the &lt;em&gt;feel&lt;/em&gt; of these kinds of tools - in a static sense - without having to actually run more advanced technology stacks to support it. If you’re already a pro with Kubernetes/related, you might not need Engraver! Our target audience for this tool are teams that want to get up and running from scratch with minimum hassle, and are willing to embrace some of the opinions we’ve injected into it for supporting serious production systems.&lt;/p&gt;

&lt;h3 id=&quot;the-best-of-both-worlds&quot;&gt;The Best of Both Worlds&lt;/h3&gt;

&lt;p&gt;Engraver overcomes the challenges we’ve discussed by walking a careful line between being a generalized tool for cluster infrastructure management and a specific tool for having tight control over Onyx itself. Engraver provides an API that wraps around Ansible, thus leveraging all the benefits of an existing tool without having to recreate it from scratch. It exposes a small number of commands that take an application from its infancy all the way to running in the cloud. Engraver freely let’s you tap into Ansible itself without getting in your way.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;60%&quot; src=&quot;/assets/images/engraver/lifecycle.svg&quot; /&gt;
&lt;/p&gt;

&lt;h4 id=&quot;preconfigured-services&quot;&gt;Preconfigured Services&lt;/h4&gt;

&lt;p&gt;Engraver manages a set of modular, preconfigured services that run out of the box as you’d want to to at scale. When you create a new Engraver project, you can instantly start using ZooKeeper, Kafka, and Onyx in a highly available mode. We’ve introduced a higher level of abstraction called a &lt;em&gt;machine profile&lt;/em&gt; that acts as a central point of scalability. Machine profiles let you seamlessly colocate services and unify the pieces of your architecture to be more manageable.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/engraver/concepts.svg&quot; /&gt;
&lt;/p&gt;

&lt;h4 id=&quot;network-security-done-right&quot;&gt;Network Security Done Right&lt;/h4&gt;

&lt;p&gt;Finally, Engraver understands production-grade networking in AWS out of the box. We lock everything down by default, and intelligently widen security groups as services are introduced. Ports are auto-discovered at the provisioning stage, removing a major impediment to teams that want to use scalable container technology.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img width=&quot;70%&quot; src=&quot;/assets/images/engraver/profiles.svg&quot; /&gt;
&lt;/p&gt;

&lt;h3 id=&quot;trying-it-out&quot;&gt;Trying it Out&lt;/h3&gt;

&lt;p&gt;Want to take Engraver for a spin? Follow along with &lt;a href=&quot;https://github.com/onyx-platform/engraver#tutorial&quot;&gt;our tutorial&lt;/a&gt; and you’ll have a production-grade Onyx cluster running in AWS in just a few minutes.&lt;/p&gt;

&lt;h3 id=&quot;the-future&quot;&gt;The Future&lt;/h3&gt;

&lt;p&gt;Engraver is less than a month old, but is built on over two years of Onyx deployment experience. We’re interested in expanding the number of cloud environments beyond AWS. We’ve also got our eye on using Engraver to provision a Kubernetes cluster, then in turn using Kubernetes as the dpeloyment target. If you’re interested in helping out with anything Engraver-related, get in touch!&lt;/p&gt;

&lt;p&gt;Thanks for following along. If you want to hear more, sign up for &lt;a href=&quot;http://pivotbase.us9.list-manage.com/subscribe?u=2f6846db1c437eae3c6d5af34&amp;amp;id=2ce27a68df&quot;&gt;our newsletter&lt;/a&gt;. We hope this helps you run Onyx more effectively and makes operations pleasant!&lt;/p&gt;
</description>
        <pubDate>Mon, 21 Mar 2016 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2016/03/21/Engraver-A-Tool-for-Managing-Onyx-Clusters.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2016/03/21/Engraver-A-Tool-for-Managing-Onyx-Clusters.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
      <item>
        <title>Onyx Straps in For a Jepsening</title>
        <description>&lt;h2 id=&quot;strapping-in-for-a-jepsening&quot;&gt;Strapping in for a Jepsening&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://onyx-platform.github.io&quot;&gt;Onyx&lt;/a&gt; is a high performance, distributed, fault tolerant, scalable data processing platform. Onyx programs are described in immutable data structures allowing jobs to cross language and machine boundaries at runtime.&lt;/p&gt;

&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;

&lt;p&gt;Distributed systems are incredibly powerful for dealing with massive amounts of load and providing high availability. Ensuring that your system behaves correctly under stress, however, is a notoriously difficult problem. All of this power is useless if you can’t trust your system to handle network partitions, connection loss, killed nodes, consistency anomalies, and other nasty issues.&lt;/p&gt;

&lt;p&gt;From the beginning, Onyx has had a variety of unit and integration tests. Over time we have also added numerous property tests to the mix. Our property tests stress our peer coordination code paths and cluster scheduler, and we found numerous bugs that would have been hard to pickup with other testing methods. These techniques have allowed us to add complex features quickly.&lt;/p&gt;

&lt;p&gt;While we have users happily &lt;a href=&quot;https://github.com/onyx-platform/onyx#companies-running-onyx-in-production&quot;&gt;using Onyx in production&lt;/a&gt;, it is likely that there are bugs waiting for the right set of scenarios to occur. When they do, reproducing these scenarios can be incredibly time consuming. We would much prefer to find these issues early and to have a way to test every release against grueling conditions that may only occasionally occur in a production environment.&lt;/p&gt;

&lt;p&gt;Many forms of distributed tests can be both difficult to formulate and time consuming for developers to build. Luckily, a paper, &lt;a href=&quot;http://www.eecg.toronto.edu/~yuan/papers/failure_analysis_osdi14.pdf&quot;&gt;Simple Testing Can Prevent Most Critical Failures Yuan et. al.&lt;/a&gt; found that almost all distributed systems failures can be reproduced with 3 or fewer nodes. Howevere we were in need of a better way to test for these forms of faults.&lt;/p&gt;

&lt;p&gt;Kyle Kingsbury’s &lt;a href=&quot;https://github.com/aphyr/jepsen&quot;&gt;Jepsen&lt;/a&gt; library and &lt;a href=&quot;https://aphyr.com/tags/jepsen&quot;&gt;Call Me Maybe&lt;/a&gt; series have been blazing a path to better testing of distributed systems. A Jepsen test is self described by Kingsbury as “a Clojure program which uses the Jepsen library to set up a distributed system, run a bunch of operations against that system, and verify that the history of those operations makes sense”. Kyle has been dragging the distributed systems world into a more consistent (and pager friendly) future. Did we mention that he’s now available &lt;a href=&quot;http://jepsen.io/&quot;&gt;for Jepsen consulting?&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;starting-out&quot;&gt;Starting out&lt;/h3&gt;

&lt;p&gt;As the Onyx team was new to Jepsen, we decided to initially perform a trial test on one of our dependencies. Onyx depends on two external services. The first is ZooKeeper, a distributed CP datastore, which we use for Onyx peer coordination, and the second is BookKeeper, a replicated log server, which we use to build replicated aggregation state machines to provide durability and consistency guarantees for our &lt;a href=&quot;https://onyx-platform.github.io/docs/user-guide/latest/aggregation-state-management.html&quot;&gt;State Management / Windowing&lt;/a&gt; features.&lt;/p&gt;

&lt;p&gt;As ZooKeeper has already received the &lt;a href=&quot;https://aphyr.com/posts/291-jepsen-zookeeper&quot;&gt;Call Me Maybe treatment&lt;/a&gt;, and passed with flying colors, we decided to first test BookKeeper. Testing our dependencies first gives us greater certainty about our system, and allows us to be reasonably sure that any bugs we find are our own fault, or will be fixed upstream.&lt;/p&gt;

&lt;h3 id=&quot;setting-up-our-jepsen-environment&quot;&gt;Setting up our Jepsen Environment&lt;/h3&gt;

&lt;p&gt;We initially setup our Jepsen environment in the recommended way, by implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jepsen.db/DB&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup!&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;teardown!&lt;/code&gt; procedures. Under our initial setup, Jepsen ran commands on each node via ssh, to install and reset ZooKeeper and BookKeeper to their original states. As this process was taking minutes to perform in our docker-in-docker configuration, we found this to be an impediment to test development time.&lt;/p&gt;

&lt;p&gt;We were already using docker-in-docker to run our Jepsen nodes, and by adding an additional layer to Jepsen’s docker containers we were able to avoid the Jepsen setup and teardown process completely. Each test would spin up a new set of containers in a pristine state - allowing us to iterate our tests quicker. See our Jepsen docker setup &lt;a href=&quot;https://github.com/onyx-platform/onyx-jepsen/blob/master/docker/README.md&quot;&gt;README&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h3 id=&quot;testing-bookkeeper&quot;&gt;Testing BookKeeper&lt;/h3&gt;

&lt;p&gt;Jepsen operates by spinning up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; &lt;a href=&quot;https://github.com/aphyr/jepsen/blob/master/jepsen/src/jepsen/os.clj&quot;&gt;server&lt;/a&gt; nodes (in our case 5), and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; &lt;a href=&quot;https://github.com/aphyr/jepsen/blob/master/jepsen/src/jepsen/client.clj&quot;&gt;clients&lt;/a&gt; (in our case 5). On each of the server nodes we ran a BookKeeper server and a ZooKeeper server, required for BookKeeper’s operation.&lt;/p&gt;

&lt;p&gt;Our test was configured to have 5 client threads writing to a BookKeeper ledger configured with an &lt;a href=&quot;https://onyx-platform.github.io/docs/cheat-sheet/latest/#peer-config/:onyx.bookkeeper/ledger-ensemble-size&quot;&gt;ensemble size&lt;/a&gt; of 3, and a &lt;a href=&quot;https://onyx-platform.github.io/docs/cheat-sheet/latest/#peer-config/:onyx.bookkeeper/ledger-quorum-size&quot;&gt;quorum size&lt;/a&gt; of 3. This is the default configuration used by Onyx in its state management feature.&lt;/p&gt;

&lt;p&gt;The 5 client threads write to this ledger, commanded by a a simple generator that generates incrementing values to be written to the ledger.&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gen/phases&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
           &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:invoke,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:f&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:add,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:value&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
           &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gen/seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gen/stagger&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;/10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gen/delay&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gen/nemesis&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gen/seq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gen/sleep&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:info&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:f&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gen/sleep&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:info&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:f&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}])))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gen/time-limit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;800&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read-ledger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This generator also commands the nemesis to &lt;a href=&quot;https://github.com/aphyr/jepsen/blob/master/jepsen/src/jepsen/nemesis.clj#L99&quot;&gt;partition random halves&lt;/a&gt; of the network, and in an alternate test, partition via the &lt;a href=&quot;https://github.com/aphyr/jepsen/blob/master/jepsen/src/jepsen/nemesis.clj#L59&quot;&gt;bridge nemesis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The final phase of the test is to read the ledger back. BookKeeper only allows a ledger to be written to by a single ledger handle, and guarantees that values read from a ledger will be in the order that they were written. This makes it rather easy to test for correctness: we simply read back the ledger, and inspect the history of the writes in our Jepsen &lt;a href=&quot;https://github.com/aphyr/jepsen/blob/master/jepsen/src/jepsen/checker.clj&quot;&gt;Checker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first hitch was we had to account for writes to the ledger that were unacknowledged, but read back by the checker. These are allowable and expected - see the &lt;a href=&quot;https://en.wikipedia.org/wiki/Two_Generals%27_Problem&quot;&gt;Two Generals Problem&lt;/a&gt; - and can be handled at the application layer if required. Onyx ensures that any events that must be transactional for correctness are written in the same write.&lt;/p&gt;

&lt;p&gt;After accounting for this checker discrepancy, we were still not able to get full test runs to complete.  The root cause of this issue was simple to determine. Our BookKeeper servers were committing suicide upon losing quorum. While this is a reasonable response to this issue, it was not our assumption, and it is not documented in BookKeeper’s documentation. After creating a &lt;a href=&quot;https://issues.apache.org/jira/browse/BOOKKEEPER-882&quot;&gt;JIRA issue&lt;/a&gt; for this documentation issue, and daemonising the BookKeeper server, we were able to achieve consistently successful test runs! Sometimes, the nemesis would cause all writes to a ledger to fail, however this is the intended behavior under these conditions, and an additional abstraction over a number of BookKeeper ledgers should be built if required. Kudos to the BookKeeper team for passing these tests with only a documentation issue.&lt;/p&gt;

&lt;h3 id=&quot;a-simple-first-onyx-test&quot;&gt;A simple first Onyx test&lt;/h3&gt;

&lt;p&gt;Having tested our dependencies, our next move was to start testing Onyx itself.&lt;/p&gt;

&lt;p&gt;Onyx operates by building a job composed of a workflow DAG of tasks, their configuration, and a scheduler configuration. Onyx depends on having a durable input stream as the input nodes of the DAG so that unprocessed data can be replayed in the case of input peer failures / rescheduling.&lt;/p&gt;

&lt;p&gt;Onyx already supports numerous &lt;a href=&quot;http://github.com/onyx-platform/onyx/tree/master#build-status&quot;&gt;plugins&lt;/a&gt;, however we have not Jepsen tested all of the products that they use under partition conditions. Luckily we have already tested BookKeeper and configured BookKeeper to run on Jepsen, and thus we decided to write &lt;a href=&quot;https://github.com/onyx-platform/onyx-bookkeeper&quot;&gt;onyx-bookkeeper&lt;/a&gt;. As a side note, developing an Onyx plugin used in a Jepsen test quickly found issues with our implementation at development time. One &lt;a href=&quot;https://github.com/onyx-platform/onyx/issues/435&quot;&gt;such issue&lt;/a&gt; was a cross-cutting problem in some of Onyx’s other plugins.&lt;/p&gt;

&lt;p&gt;Building an Onyx plugin for a Jepsen tested, durable input and output medium allowed us to build Onyx jobs consisting of BookKeeper data sources and sinks. We wrote a function to build a simple Onyx job to read from 1-5 BookKeeper ledgers, pass through an intermediate task that adds the job number to the message so that we could ensure that the segment has been routed from the correct job, and write the resulting segment to new BookKeeper ledgers.&lt;/p&gt;

&lt;p&gt;This test can dynamically build Onyx jobs based on a parameter that defines how many jobs should read from the ledgers. As the number of ledgers needs to be split up over the number of jobs, we tested Onyx scheduling 5 simultaneous jobs, reading from one ledger each, as well as 1 job, reading from all 5 ledgers.&lt;/p&gt;

&lt;p&gt;A programatically generated job, reading from one ledger, is shown below. In the below case, 1 job is submitted to the cluster. Hover over the tasks to view the configuration of each task.&lt;/p&gt;

&lt;iframe src=&quot;/assets/jepsen_viz/basic.html&quot; width=&quot;960&quot; height=&quot;255&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Test configuration:&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:job-params&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:batch-size&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:job-type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:simple-job&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:nemesis&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:random-halves&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;; :bridge-shuffle or :random-halves&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:awake-ms&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:stopped-ms&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:time-limit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:n-jobs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;; number of Onyx peers per Jepsen node&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:n-peers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We configured 3 Onyx &lt;a href=&quot;https://github.com/onyx-platform/onyx/blob/0.8.x/doc/user-guide/concepts.md#virtual-peer&quot;&gt;virtual peers&lt;/a&gt; to run per Jepsen node (of which there are 5 nodes). Onyx peers are general purpose execution units that can be assigned to tasks in jobs. For example one peer may be allocated to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:read-ledger-3&lt;/code&gt; task, to read from a BookKeeper ledger, and pass any read messages onto peers assigned to outgoing tasks in the job’s directed acyclic graph.&lt;/p&gt;

&lt;p&gt;Each task requires at least one virtual peer to be running. As the generator may submit 5 jobs with 3 tasks each, and each task requires at least one peer to run, it is possible for jobs will be descheduled by Onyx during nemesis events where nodes are partitioned from the ZooKeeper quorum, and re-allocated after healing or if one of the other jobs completes. This means that our scheduler would also be tested by the nemesis.&lt;/p&gt;

&lt;p&gt;We also configured each node with a full ZooKeeper instance and a full BookKeeper instance per node.&lt;/p&gt;

&lt;p&gt;Upon completing all of the jobs, the Jepsen checker reads back from the output ledgers, and determines whether all values written by the clients to the input ledgers were processed and written to the output ledgers, including the correct annotation of the job name.&lt;/p&gt;

&lt;p&gt;We quickly hit a number of issues, mostly relating to the peers join process, as well as rebooting themselves after being excised from the cluster.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/onyx-platform/onyx/issues/453&quot;&gt;Peer join race condition #453&lt;/a&gt; Resolved.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/onyx-platform/onyx/issues/437&quot;&gt;Peers that crash on component/start will not reboot #437&lt;/a&gt; Resolved.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/onyx-platform/onyx/issues/423&quot;&gt;Ensure peer restarts after ZooKeeper connection loss/errors #423&lt;/a&gt; Resolved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While we had property tests to thoroughly test the peer join process, the above bugs mostly appear in the impure sections of our code. These bugs operate in the real world where peers are not always able to write their coordination log entries, do not always manage to call their side effects, etc.&lt;/p&gt;

&lt;p&gt;Jepsen uses excellent scientific procedures for running tests, by outputting dated records including a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result.edn&lt;/code&gt; file, and history to a timestamped directory under your test name e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;store/onyx-basic/20160118T102259.000Z&lt;/code&gt;. You can view a sample of onyx-jepsen’s &lt;a href=&quot;https://gist.github.com/lbradstreet/60c4be48216146878f58&quot;&gt;result.edn&lt;/a&gt;. In addition to the standard Jepsen output, we also copy Onyx’s log output to the test run directory. Scientists often like to keep a log of experimental results, and we have tried to emulate one further, keeping a log of our immediate interpretations and hypothesis, of each failed run. See this &lt;a href=&quot;https://github.com/onyx-platform/onyx-jepsen/blob/master/onyx-issues-log.txt#L233&quot;&gt;sample if you are interested in our process&lt;/a&gt;, but please do not judge our notes!&lt;/p&gt;

&lt;p&gt;Onyx coordinates peers via a shared log, written to ZooKeeper. Each peer plays back this log in order, gaining a full view of the cluster replica. One advantage of this mechanism is that we can playback the log obtained by jepsen, and debug it step by step. A great post &lt;a href=&quot;https://news.ycombinator.com/item?id=10765378&quot;&gt;describing this design pattern&lt;/a&gt; has been written by &lt;a href=&quot;https://twitter.com/BrandonBloom&quot;&gt;Brandon Bloom&lt;/a&gt;. It is this pattern that makes testing our replica coordination, and cluster scheduler easy with property testing, and is now paying dividends with our Jepsen testing.&lt;/p&gt;

&lt;p&gt;To this end, we wrote a &lt;a href=&quot;https://github.com/onyx-platform/onyx-console-dashboard&quot;&gt;console application&lt;/a&gt; that opens Jepsen’s &lt;a href=&quot;https://gist.github.com/lbradstreet/60c4be48216146878f58&quot;&gt;result.edn&lt;/a&gt; outputs, allowing us to step through the replica, diff each action, filter by peer actions, ids, etc. This vastly simplifies debugging coordination and scheduler related issues.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/jepsen_viz/console_dashboard.png&quot; height=&quot;70%&quot; width=&quot;70%&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;testing-onyxs-state-management-feature&quot;&gt;Testing Onyx’s State Management feature&lt;/h3&gt;

&lt;p&gt;The previous Onyx test was a test of our cluster fault tolerance mechanisms and scheduler. In our next test, we will stress our &lt;a href=&quot;https://github.com/onyx-platform/onyx/blob/0.8.x/doc/user-guide/windowing.md&quot;&gt;windowing&lt;/a&gt; and &lt;a href=&quot;https://github.com/onyx-platform/onyx/blob/0.8.x/doc/user-guide/aggregation-state-management.md&quot;&gt;state management&lt;/a&gt; features, which are intrinsically linked by the way they incrementally journal aggregation state machine updates, window results, and transactional &lt;a href=&quot;https://github.com/onyx-platform/onyx/blob/0.8.x/doc/user-guide/aggregation-state-management.md#exactly-once-aggregation-updates&quot;&gt;exactly once aggregation updates&lt;/a&gt; (not to be confused with exactly once side effects, which are impossible).&lt;/p&gt;

&lt;p&gt;Onyx’s state management and windowing features journal each state update and corresponding unique id, to BookKeeper. Upon the failure of a peer, the state machine log is replayed to recover the full state. In this next test, we build a job that adds each message to a collection, using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:onyx.windowing.aggregation/conj&lt;/code&gt; aggregation, over a window on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:annotate-job&lt;/code&gt; task. Onyx’s “exactly once” / de-duplication feature, will ensure that this message will only be added to this collection once. Once all messages are processed, the final state must consist of all of the messages written to all of the ledgers by the Jepsen clients.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The window on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:annotate-job&lt;/code&gt; task:&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:window/id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:collect-segments,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:window/task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:annotate-job,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:window/type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:global,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:window/aggregation&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx.windowing.aggregation/conj,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:window/window-key&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:event-time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In order to check this final state, we also add a trigger to the window above. This trigger is configured to persist the full window state to BookKeeper, and only writes when a peer is stopped. The Jepsen checker reads the result of the the final trigger call, and checks it against the data written by the clients to the input BookKeeper ledgers. All data must be available in the final write, but must not be occur more than once, as that would violate de-duplication.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The trigger on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:collect-segments&lt;/code&gt; window:&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/window-id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:collect-segments,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/refinement&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx.triggers.refinements/accumulating,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/on&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx.triggers.triggers/segment,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/threshold&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:elements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:trigger/sync&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:onyx-peers.functions.functions/update-state-log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The Onyx Job DAG (hover to view task data):&lt;/em&gt;&lt;/p&gt;

&lt;iframe src=&quot;/assets/jepsen_viz/stateful.html&quot; width=&quot;960&quot; height=&quot;340&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;This test found two issues that were previously known by the Onyx team, but were theoretical as they had not been seen in practice.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/onyx-platform/onyx/issues/382&quot;&gt;BookKeeper state log / key filter interaction issue #382&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/onyx-platform/onyx/issues/390&quot;&gt;Failed async BookKeeper writes should cause peer to to restart #390&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jepsen was a powerful ally in fixing these bugs as it gave us certainty that we had fixed them correctly. We have internally joked about this as JDD (Jepsen Driven Development).&lt;/p&gt;

&lt;h3 id=&quot;kill--9-me&quot;&gt;Kill -9 Me&lt;/h3&gt;

&lt;p&gt;One of our production users reported an issue where a cluster had troubles recovering from a full cluster restart. We copied a &lt;a href=&quot;https://github.com/onyx-platform/onyx-jepsen/blob/master/src/onyx_jepsen/onyx_test.clj#L123&quot;&gt;crash nemesis&lt;/a&gt; from Jepsen’s &lt;a href=&quot;https://github.com/aphyr/jepsen/blob/master/elasticsearch/src/elasticsearch/core.clj&quot;&gt;elasticsearch&lt;/a&gt; tests. This nemesis kill -9s between 1-5 of the Jepsen nodes in each nemesis event. We re-used the simple job tested in our first test setup.  When all 5 of the nodes are killed, Jepsen reproduced the issue reported by our user. After reviewing the peer logs using the console dashboard, we were able to quickly discover the source of the issue and &lt;a href=&quot;https://github.com/onyx-platform/onyx/pull/526&quot;&gt;provide a fix&lt;/a&gt; that we had confidence in.&lt;/p&gt;

&lt;p&gt;In the future we will use a similar kill -9 test against our stateful jobs, and against BookKeeper, to test for full recovery of task state.&lt;/p&gt;

&lt;h3 id=&quot;things-we-learned&quot;&gt;Things we Learned&lt;/h3&gt;

&lt;p&gt;Test your assumptions. We had not realized that BookKeeper would commit suicide upon losing quorum. This is a good practice whether you are building a distributed system or not.&lt;/p&gt;

&lt;p&gt;Building tests with Jepsen can take a long time and has a bit of a learning curve, however it is incredibly worthwhile. This type of testing has greatly increased confidence in our product, and proven helpful in reproducing issues seen in the wild that would otherwise be difficult. Jepsen will also provide further confidence in refactoring our code, including building other forms of fault tolerance into our system.&lt;/p&gt;

&lt;p&gt;Store your application’s logs upon test completion. Obviously it is very helpful to be able to correlate the info and exceptions logged by your application with the events generated by Jepsen.&lt;/p&gt;

&lt;p&gt;Feedback loops while developing Jepsen tests can be long. We improved turn around times by pre-building docker images with all of our dependencies installed.&lt;/p&gt;

&lt;p&gt;We further improved test development time by building a test harness (&lt;a href=&quot;https://github.com/onyx-platform/onyx-jepsen/blob/master/test/onyx_peers/jobs/basic_test.clj#L23&quot;&gt;example&lt;/a&gt;) around Jepsen and Onyx, using with static generated events that use a single client, and no nemesis. These tests spin up a development mode Onyx cluster in the JVM without Jepsen orchestrating nodes being spun up and destroyed. This allowed us to build new tests quickly and re-factor our tests as required. We then use substantially similar tests with Jepsen orchestrating real nodes, a nemesis, and generated events. It also allowed us to CI test our Jepsen tests.&lt;/p&gt;

&lt;h3 id=&quot;the-future&quot;&gt;The Future&lt;/h3&gt;

&lt;p&gt;We will continue to add tests to &lt;a href=&quot;https://github.com/onyx-platform/onyx-jepsen/&quot;&gt;onyx-jepsen&lt;/a&gt;. Next up is further testing around fault tolerance aspects in triggers, aggregation grouping, and more. Following the lead of the Call Me Maybe posts, we also want to test performance and recovery characteristics resulting from nemesis events.&lt;/p&gt;

&lt;p&gt;Jepsen testing should also be integrated into our CI process. As users of &lt;a href=&quot;http://www.circleci.com/&quot;&gt;CircleCI&lt;/a&gt;, it is difficult for us to do this testing directly on Circle, as we will quickly hit resource limits. We are considering having successful CI builds trigger starting a spot instance on EC2 that runs our Jepsen suite.&lt;/p&gt;

&lt;p&gt;The test harness described above, may give Onyx a path to building all of our integration tests in a way that we can easily reuse them with Jepsen. This would require some re-factoring of our tests, primarily to be built around generators, however there are no technical obstacles standing in our way.&lt;/p&gt;

&lt;p&gt;Taking this idea even further, Onyx may be able to create testing functionality that essentially provides Jepsen testing of end user’s jobs for free. The main obstacle here is providing a way to allow plugin projects to be spun up outside of the purview of the Jepsen nemesis. As we are currently using docker-in-docker to run our Jepsen tests, this may be easy to provide. If we are able to achieve this in a sane, and easy to use manner, this would be a feature provided by no other solution that we know of.&lt;/p&gt;

&lt;h3 id=&quot;to-hear-more--our-pitch&quot;&gt;To Hear More &amp;amp; Our Pitch&lt;/h3&gt;

&lt;p&gt;If you are interested in hearing further thoughts on Onyx and distributed systems, please subscribe to &lt;a href=&quot;http://eepurl.com/beFW_P&quot;&gt;Distributed Masonry’s Newsletter&lt;/a&gt; and follow us &lt;a href=&quot;http://www.twitter.com/onyxplatform&quot;&gt;on Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Distributed Masonry are also &lt;a href=&quot;https://onyx-platform.github.io/support/&quot;&gt;available&lt;/a&gt; for Onyx Platform and general distributed systems consulting, contracting, support, and training services. Please feel free to contact us if you are interested in our services or just want to have a &lt;a href=&quot;https://github.com/onyx-platform/onyx/issues&quot;&gt;chat about this post&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;thanks&quot;&gt;Thanks&lt;/h3&gt;

&lt;p&gt;Thank you to Kyle Kingsbury, Michael Drogalis, Bridget Hillyer, and Gardner Vickers for reviewing this post.&lt;/p&gt;

&lt;p&gt;– Distributed Masonry, &lt;a href=&quot;http://www.twitter.com/ghaz&quot;&gt;Lucas Bradstreet&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 15 Mar 2016 00:00:00 +0000</pubDate>
        <link>https://onyx-platform.github.io/jekyll/update/2016/03/15/Onyx-Straps-In-For-A-Jepsening.html</link>
        <guid isPermaLink="true">https://onyx-platform.github.io/jekyll/update/2016/03/15/Onyx-Straps-In-For-A-Jepsening.html</guid>
        
        
        <category>jekyll</category>
        
        <category>update</category>
        
      </item>
    
  </channel>
</rss>
