Caveat Configurator: When Configuration Becomes Code

Dominick LoBraico explains why flexible OCaml plugins created production problems at Jane Street, and how configuration generation and disciplined plugin management address them.

DLDominick LoBraicoIntervenant · Jane Street
Chaîne
Jane Street
Publié
20 juin 2017
Vidéo
52:51
Lecture
10 min
Diapositives
5
Original
anglais
Ouvrir sur YouTube
En faisant défiler, la vidéo se réduit en bas à droite et continue. Les sous-titres suivent votre langue de lecture.
Langue de lectureDisponible en 2 langues
Cet article n’est pas encore disponible en français ; il s’affiche dans l’original en anglais.
RÉSUMÉ

En 30 secondes

Jane Street's Dominick LoBraico traces the path from simple configuration data to domain-specific languages and dynamically loaded OCaml plugins. He describes the safety, versioning and tooling failures that emerged in production, then compares generated static configuration with centralized, tested plugins.

  1. 1Consider zero configurationA rebuild and deployment may be simpler when sane defaults meet the system's needs.
  2. 2A DSL is a language to maintainCustom syntax brings a parser, semantics, tools and a learning burden alongside the application.
  3. 3Plugin compatibility can break deploymentsPlugins must compile against the library versions bundled into the application that loads them.
  4. 4Generate data with codeA generator provides familiar abstractions while leaving a static result that can be inspected and diffed.
  5. 5Treat plugins like application codePlugd centralizes versions and serves plugins only after review, compilation and testing.

À retenir

  1. Start by asking whether a system needs separate configuration at all.
  2. A custom configuration language adds the burden of semantics, bugs, tooling and specialized skills.
  3. Runtime plugins execute real code and require compatibility, deployment and safety discipline.
  4. Generate inspectable static configuration with code when runtime functions are unnecessary.
  5. When independently deployed plugins are essential, centralize versions, review, builds and tests.
TEXTE INTÉGRAL

Lire le texte remanié, chapitre par chapitre

Transcription seule
01

Flexibility, safety and the plugin experiment

Jane Street's trading systems must be reusable and safe. A promising OCaml plugin approach ran into real production problems.

At Jane Street, we configure systems under two constraints: flexibility and safety. We trade many instruments around the world using mostly in-house tools, so each engineer is responsible for many systems. Reusing a general system with small changes can make the difference between launching an opportunity quickly and building a new system from scratch.

We also trade our own money. A small bug may cost an opportunity or prevent us trading somewhere; a large bug in a tight loop can threaten the firm itself. Configuration is one way to get more mileage from general systems while managing those risks.

In 2014, a colleague and I presented ocaml_plugin, a library that makes dynamic loading of OCaml code easier. It let us plug code into a system and change its behavior more flexibly than earlier configuration architectures. We were excited enough to use it in a new implementation of our email system.

Our first, naive use of plugins in production turned out to be a disaster. I want to explain the problems we encountered, how we arrived at plugins, and the approaches we eventually used to deal with configuration.

02

What configuration does

Configuration separates variable behavior from a core application. Sometimes a system can avoid separate configuration entirely.

Configuration lets you change a system's behavior without necessarily recompiling, repackaging and redeploying the application. It separates decisions exposed to operators from core functionality, and it can provide an escape hatch for situations the original authors did not anticipate.

Consider a mail server: it accepts connections, receives messages, processes them—for example by adding headers or removing infected attachments—and delivers or forwards them. One possible design has no separate configuration at all: changes are made in the application, which is rebuilt and deployed.

Zero configuration is not crazy. If sensible defaults and easy deployment meet your needs, it avoids many of the problems discussed here. Mail systems often need changes at runtime, but it is worth asking whether separate configuration is needed before introducing it.

As we move more decisions out of the application's core and into configuration, the system can become more flexible. At the extreme, configuration handles almost everything. Whatever the boundary, the choices must be represented somewhere the application can read, such as a file or database.

03

From pure data to a DSL

Simple data is transparent but repetitive as complexity grows. A custom language offers power while imposing design, tooling and training costs.

A common starting point is pure data: command-line parameters, environment variables, key-value files, or formats such as YAML, JSON and S-expressions. A small flat file is easy to understand: if it says the mail server listens on port 25, there is no hidden logic to interpret.

Simple data is also relatively easy to version. An upgrader can read an old format and produce the internal representation needed by a newer application. The drawback is limited expressive power: as configuration grows more complex, it can become verbose and repetitive.

Structured mail configuration
DIAPOSITIVE 1 / 5Structured mail configurationThe example specifies ports, logging, next hops and retry intervals, illustrating repetition as configuration grows.

In a JSON-like mail configuration, we might list ports, a log directory and level, then specify next-hop servers by sender domain, including retry intervals. Adding another domain can duplicate ports and retry schedules. We can factor repeated values into defaults, but some fields have several common cases rather than one obvious default.

The tempting next step is to add macros or variables. That can help repetition, but it begins an ad hoc language. You might instead deliberately design a domain-specific language, choosing its structure and features up front rather than accumulating special cases.

A DSL gives its designer control and can be arbitrarily powerful. But language design takes time and is hard even for skilled designers. The resulting parser or interpreter can have bugs that are difficult to distinguish from application bugs, especially during a production incident.

The semantics may be unfamiliar to everyone else, even if they draw on features from existing languages. Tooling such as syntax highlighting, navigation, completion and tests must be created or adapted. Experienced mail operators still have to learn this new language, and its author may be the only person called when it breaks at 2 a.m.

04

Mail configuration as code

An Exim plus-addressing example illustrates DSL complexity. An OCaml message handler expresses the same behavior in a familiar programming language.

For a concrete example, consider Exim. A basic configuration can deliver mail to a file named for the local part of an address, check a user list and reject unknown users. Adding Gmail-style plus addressing lets a recipient append a suffix to an address while retaining the base destination.

Plus addressing in Exim
DIAPOSITIVE 2 / 5Plus addressing in EximThe Exim example shows the extra syntax and conditions introduced for a relatively modest feature.

Even this modest feature requires a more involved Exim configuration if we allow individual users to enable it. There are nested braces, a special suffix pattern, and different rules for referring to or checking variables. Ordering and scope matter. The example may work, but getting it right takes real effort.

We already have a tool designed to express complex behavior: a programming language, with libraries and workflows we know. In OCaml, we can model a message with sender, recipients and body, then define a handler that returns delivered, relay with destinations, or reject with an error.

Modeling a mail server in OCaml
DIAPOSITIVE 3 / 5Modeling a mail server in OCamlA message type and handler signature express delivery, relay and rejection as code.

To implement plus addressing, the handler can split the local part around the plus sign, check the user's preference, and either reject or deliver the message. For someone familiar with the language, that is ordinary code rather than another set of DSL-specific rules. Dynamic loading is possible in many languages; ocaml_plugin makes it convenient in OCaml.

05

Why naive plugins failed

Plugins introduce production-code risks, difficult dependency compatibility, scattered files and tooling gaps. Some applications reverted to deploying plugins with the application.

Initially, replacing larger configurations with OCaml made them concise and readable. Then people used the power they had been given: plugin usage grew and the problems appeared. Code can do almost anything, and a plugin configuring a production service runs inside its process, under its user and on its machine.

We deployed systems whose users wrote their own plugins separately from the application developers. Things worked until we needed to change and redeploy the core application. We had not thought enough about versioning: a plugin must compile against the libraries linked into the particular application version.

If library interfaces change and the plugin is not updated, it may no longer compile at runtime. We actually abandoned and restarted some applications after encountering this problem. Independent plugin files also need a plan for where they live, how they are deployed and tracked, and how they are updated alongside the application.

Surprisingly, the tools were still poor. A standalone plugin outside the normal codebase does not automatically tell an editor where its dependencies live, so features such as jump-to-definition may fail. Treating configuration code differently from the rest of the code breaks assumptions in the tools.

That matters even more when configuration grows from a 12- or 13-line example to 4,000 lines of OCaml, as our actual mail-server configuration did. At that scale, a poor development workflow is no longer a minor inconvenience.

In many cases we moved the boundary back: we bundled the plugins with the application and deployed them together, even though we still loaded them through ocaml_plugin. We retained a clean separation between core and configurable behavior and an emergency runtime escape hatch, but lost much of the original independent configuration benefit.

We took two broad routes. For some applications, we concluded independent plugin flexibility was unnecessary and shipped plugins with the application. Elsewhere, we invested in tools for editing, version control, rebasing and merging dispersed plugin changes, effectively rebuilding parts of an ordinary development environment.

06

Configuration generation

Code can generate static, versioned configuration that is inspectable and diffable. The method works when the required behavior can be represented as finite data.

Code has real strengths: it is expressive, scales through familiar abstractions, and has mature tools. It also comes with a culture of review, tests and refactoring. Those practices improve quality and correctness, yet people do not automatically apply them to configuration files.

One alternative is configuration generation. Write code to produce a finite, static configuration, then have the application read that data. This combines the expressive power of code when authoring the configuration with the transparency of inspecting the result before it is loaded.

Generation requires a configuration that can be written down finitely; a live function cannot simply be serialized as ordinary data. The on-disk format still needs a versioning story, but upgrading a data format is simpler than maintaining compatibility between dynamically loaded code and all of an application's dependencies.

A system already using hand-written data can move to a generator without necessarily changing the application: the generator emits the same format. The technique also works with third-party systems, since the generator can use any language and output the format the target application expects.

At Jane Street, one option is an OCaml script pinned to appropriate compiler and library versions. Another is a repository with code review, a build daemon and tests. Committing the generated configuration as well makes changes easy to inspect and allows rollback without running the generator again.

Our monitoring configuration describes hosts, services, check commands and schedules. Because the generated file is visible before loading, operators can inspect the actual settings. We can also generate a CSV of resolved hosts and checks, then diff versions to see exactly which checks disappear or which hosts and checks are added.

Diffing generated checks
DIAPOSITIVE 4 / 5Diffing generated checksA generated host-and-check list makes the operational effect of a configuration change visible.
07

Disciplined plugins and the decision tree

Plugd centralizes reviewed, compiled and tested plugin versions. The choice depends on runtime dynamism, independent deployment and stability needs.

The other route is to tackle plugin problems directly. We need to handle version compatibility, the danger of a buggy plugin or top-level exception destabilizing the application, inconsistent deployment and file management, and poor tooling. Jane Street built a system called plugd to address these issues.

Plugd centralizes configurations in a repository and treats them like application code. It serves versions that have been reviewed, compiled and tested, while decoupling the application from the location of plugin files. It also provides a workflow for moving patches between versions so plugins used by older application releases can remain up to date.

That infrastructure took real work, but plugins provide something configuration generation cannot: runtime dynamism. A function can be the configuration, so behavior need not be limited to a finite static file.

Choosing a configuration approach
DIAPOSITIVE 5 / 5Choosing a configuration approachThe decision tree considers runtime expressiveness, decoupling and stability.

The decision tree begins by asking whether runtime computational flexibility is necessary. If not, configuration generation is a strong choice. If it is, ask whether plugin writers must be decoupled from application developers; if not, bundle and deploy the plugins with the application.

If independent plugins are necessary and stability matters, use disciplined versioning and infrastructure rather than naive plugins. Also reconsider whether plugins are needed at all: in many cases their attractive separation of core and configurable functionality could have been achieved simply by structuring the application well.

08

Questions from the audience

The discussion covers non-developer users, version compatibility, library-based applications, sandboxing and maintaining plugin infrastructure.

Asked how plugd itself is configured, I would put it closer to the zero-configuration camp. It has sensible defaults and integrates tightly with the code-review system and build daemon, from which it can obtain much of the information it needs.

An audience member asks about researchers and operations staff outside the developer group. Plugins require their authors to know OCaml. Although many non-engineers at Jane Street can read or write code, we sometimes deliberately keep a simpler format that a person can open and change without learning a programming language.

The two approaches can also be combined: a plugin provides runtime dynamism while defining a simpler configuration format inside it. In other cases traders write plugins themselves, and people familiar with the core application review them to help ensure they are safe and reasonable.

A question about versioning asks whether the issue is merely a lack of version control. Version control matters, and configuration is less likely to be placed under it by default, but the harder issue is compatibility between a plugin and the precise library versions available to an application at runtime.

OCaml plugin bundles the compiler and dependencies into the application. At runtime it expands them, compiles the plugin against those dependencies and dynamically loads the result. A plugin written against a different version of a library may therefore fail to compile when loaded by that application.

Another alternative is to expose the application as a library and let users build a small program around it. We use related patterns extensively: our codebase is a collection of libraries, and some expose functions for starting configured applications. The boundary between configuration and using a library is not always sharp.

When should you choose that approach? The answer depends partly on how much functionality is bundled and how much is left to the user. An audience member adds that if behavior must change while a program is executing, loading a new plugin matters; if everything is set up before running, the difference may mostly be tooling.

Asked about sandboxing, I describe libraries that start worker processes to build plugins so a failing worker does not directly affect the master. This adds substantial plumbing and has not become our dominant use case, though it remains an option.

A 'good' plugin is one that passes the workflow: code review, compilation and tests. The build daemon runs the relevant tests for each release, and plugd builds and tests plugins independently before serving them to the application.

The audience asks about the people needed to build and maintain plugd. The speakers estimate that roughly one person built most of it, with maintenance requiring a fraction of a person's time, although the total development effort across contributors was considerable.

What happens if the owner is unavailable? That is a broader operational problem. Other people understand the production operation well enough to respond, and code review spreads some understanding of changes. Documentation is encouraged but uneven; for especially important systems, we make sure several people keep up with day-to-day operation.

Finally, someone asks whether plugin functionality ever migrates into the core application. I expect that it does, though I cannot immediately name a specific example.

Sur la même chaîne

1:34:29EN
JSJane Street· 24 juin 2026Jacob Baskin on Building Superstore and Scheduling the HiveYaron Minsky, Jacob Baskin · Jane StreetThe Jane Street engineer traces the design choices behind a distributed analytical database and the challenge of allocating a growing research cluster.Infrastructure et déploiementSoftware engineeringDecision Making+1
1:34:36EN
JSJane Street· 1 juin 2026Nate Foster: Treating the Network as a ProgramYaron Minsky, Nate Foster · Jane StreetFrom programmable switches to BGP, a programming-languages researcher explains how higher-level policies and formal models can make networks easier to change.Infrastructure et déploiementProgramming LanguagesSoftware engineering+2
5 diapositives15:48EN
JSJane Street· 15 mai 2026Inside Jane Street’s Texas AI Data CenterDwarkesh Patel, Yaron Minsky, Daniel Pontecorvo · Dwarkesh PatelDwarkesh Patel tours a 4,032-GPU training facility with Ron Minsky and Dan Pontecorvo, examining liquid cooling, power distribution and operational safeguards.Infrastructure et déploiementAI cost & performance
7 diapositives1:20:57EN
JSJane Street· 23 déc. 2025Matt Godbolt: Inside Skylake’s Out-of-Order PipelineMatt GodboltA guided tour of instruction decoding, register renaming, execution and memory ordering, grounded in reverse engineering and careful measurements.SemiconductorsSoftware engineeringBenchmarks
1:12:58EN
JSJane Street· 4 sept. 2025Chris Lattner on Mojo and the Search for Portable GPU PerformanceYaron Minsky, Chris Lattner · Jane StreetRon Minsky and Chris Lattner discuss the language and compiler design behind Modular’s effort to make accelerator programming both portable and fast.Programming LanguagesGPU programming & kernelsSoftware engineering+1
58:47EN
JSJane Street· 28 juil. 2025Daniel Pontecorvo on Cooling Trading Infrastructure and WorkspacesYaron Minsky, Daniel Pontecorvo · Jane StreetFrom dense GPU racks to movable desks, Jane Street’s physical engineering team designs around heat, power, failure and human comfort.Infrastructure et déploiementEnergyObservabilité et fiabilité+1