Showing posts with label Messaging. Show all posts
Showing posts with label Messaging. Show all posts

Everything to Gain from Thriving Southland

Mike's Notes

My notes from an all-day workshop organised for farmers, which I attended yesterday.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Thriving Southland
  • Home > Handbook > 

Last Updated

08/05/2026

Everything to Gain from Thriving Southland

By: Mike Peters
On a Sandy Beach: 07/05/2026

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

I attended the all-day workshop "Everything to Gain" on 6 May 2026, held at the Ascot Park Hotel in Invercargill, New Zealand. Organised by Thriving Southland.

It was a day full of informative speakers on the global agricultural market, attended mainly by working farmers.



I learned a great deal from the objective data presented and from chatting with the farmers at our table. Hard times are ahead.

Thinking visually while listening

mentally ran the Workspaces for Agriculture, testing the model assumptions against what I learned.

I also did a brain dump by creating 12 A4 drawings and solving the problem with variables in the current Pipi Core buildout.

Lessons I learned

Shifting a working Pipi 9 from a laptop to a data centre led to several unexpected consequences.

I underestimated the impact of

  • The naming, generation, and pub/sub of variables.
  • Host environment.
    • OS
    • Java
    • CFML Engine
  • Needing to turn Pipi into 4 separate role-based editions, which then exposed some hidden problems.
  • Adding a nest structure between Pipi and the host environment.
  • The impact of all of the above when each engine can pub/sub and be both deterministic and probabilistic, with multiple copies of each engine, and many in different locations.
  • Path length constraint in Windows vs Linux.

This very hard problem can only be solved by running a simulation of all 18 engines in parallel and watching the interaction. Lots of feedback loops.

Yesterday, a lot of progress was made visually, answering these questions. The variable-naming convention used for 12 months has held up, despite some earlier false flags.

  • More work is needed on variable distribution rules (messaging) for automation.
    • Global
    • Local
    • etc

Today I did another 8 drawings. They were of the Messaging Engine (msg) routing variables between the engines in both deterministic and probabilistic modes.


I will sleep on all this for a few days to see if anything else pops out, then commit it to code.

Potential uses of OpenTofu

Mike's Notes

Thoughts on Terraform and OpenTofu in the wake of the HCP Terraform Free Tier being discontinued by IBM.

Alex asked Gemini about OpenTofu. The output was not verified and is appended below. (I used Google Translate to English)

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

27/01/2026

Potential uses of OpenTofu

By: Mike Peters
On a Sandy Beach: 27/01/2026

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

Background

The original plan was for Pipi to use Terraform as Infrastructure-as-Code (IaC) to deploy cloud infrastructure across AWS, Azure, GCP, IBM, Oracle, and more. Then IBM announced that the Terraform Free Tier is being discontinued. There is now an open-source fork called OpenTofu, which is part of the Cloud Native Computing Foundation (CNCF). OpenTofu has strong community support. Pipi will now use OpenTofu.

Pipi as a code generator

A Pipi Agent could easily generate the highly structured Terraform/OpenTofu code.

Potential Uses

  • The Pipi messaging system could use OpenTofu syntax as the message format for internal messaging between Pipi Agents. The needs are relatively simple compared to what is available. But more capacity is available if needed.

    Message examples
    • Tell the Namespace Engine (nsp) to shut down.
    • Tell the Factory Engine (fac) to make more Workflow Engines (wfl) and where to deploy them.
    • Tell the Ontology Engine (ont) to import the latest version of SNOMED.
    • The updated SNOMED Ontology availability would then trigger many other engines to run updates to Workspace for Health and User Documentation.
    • Tell the Workspace Engine (wsp) to build a Hebrew-language/script generic model of the Workspace for Screen.
    • Tell the Google Cloud Engine (GCE) to deploy a Vietnamese-language/script customised Health System Workspace for a Hospital in Vietnam to GCP.
    • Tell the Physical Law Engine (pyw) to update with the latest definitions from the Particle Data Group.
  • Dedicated Pipi Agents would use OpenTofu to deploy cloud infrastructure to each cloud platform.
    • Apple Engine (ale)
    • AWS Engine (aws)
    • AZURE Engine (azu)
    • Digital Ocean Engine (dgo)
    • Google Cloud Engine (ggc)
    • IBM Engine (ibm)
    • Meta Engine (met)
    • Oracle Engine (ora)
    • (More will be added later; all are welcome)

OpenTofu Example

This code sample is from the OpenTofu Docs.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 1.0.4"
    }
  }
}

variable "aws_region" {}

variable "base_cidr_block" {
  description = "A /16 CIDR range definition, such as 10.1.0.0/16, that the VPC will use"
  default = "10.1.0.0/16"
}

variable "availability_zones" {
  description = "A list of availability zones in which to create subnets"
  type = list(string)
}

provider "aws" {
  region = var.aws_region
}

resource "aws_vpc" "main" {
  # Referencing the base_cidr_block variable allows the network address
  # to be changed without modifying the configuration.
  cidr_block = var.base_cidr_block
}

resource "aws_subnet" "az" {
  # Create one subnet for each given availability zone.
  count = length(var.availability_zones)

  # For each subnet, use one of the specified availability zones.
  availability_zone = var.availability_zones[count.index]

  # By referencing the aws_vpc.main object, OpenTofu knows that the subnet
  # must be created only after the VPC is created.
  vpc_id = aws_vpc.main.id

  # Built-in functions and operators can be used for simple transformations of
  # values, such as computing a subnet address. Here we create a /20 prefix for
  # each subnet, using consecutive addresses for each availability zone,
  # such as 10.1.16.0/20 .
  cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index+1)
}

Yes, Mike! It's cool!

I talked a little bit with Gemini [1]. OpenTofu is a great tool

Alex

[1] https://gemini.google.com/share/4c550e83a230

OpenTofu: Features and Benefits

Some responses below were generated according to the creator's saved info and preferences. Learn moreOpens in a new window 27 January 2026 at 21:22

Published on 27 January 2026 at 21:37

Prompt

What features does OpenTofu provide?

Output

OpenTofu is an open-source fork of Terraform (created after HashiCorp's license was changed to BSL), developed under the auspices of the Linux Foundation. In 2026, it became a full-fledged competitor to Terraform, offering a number of unique features while maintaining backward compatibility.

Here are the main features that OpenTofu provides:

1. Full compatibility with the Terraform ecosystem

OpenTofu is designed as a drop-in replacement. This means:

  • HCL support: The same configuration file syntax is used.
  • Working with providers and modules: OpenTofu uses its own registry ( OpenTofu Registry ), which mirrors most popular providers (AWS, Azure, GCP, etc.) and modules.
  • Migration: You can migrate from Terraform (versions up to and including 1.6 and many later) with virtually one command without rewriting any code.

2. Advanced Security (State Encryption)

One of the main "killer features" introduced in OpenTofu (starting with version 1.7) is native encryption of state files .

  • Unlike standard Terraform, where sensitive data terraform.tfstate is often stored in cleartext, OpenTofu allows you to encrypt it at rest.
  • Various key management methods are supported: AWS KMS, GCP KMS, HashiCorp Vault (OpenBao), as well as environment variables and files.

3. Improvements in code management

OpenTofu is actively implementing features that the community has been requesting for years:

  • Dynamic Provider Functions: Ability to use functions defined directly in providers within your HCL code.
  • Improved loops in import blocks: Can now be used for_each inside count blocks import, making it much easier to bulk import existing resources into your code.
  • Early Variable Evaluation: Allows you to use variables in parts of the configuration where they were previously unavailable (for example, in backend settings).

4. Openness and community

  • MPL v2.0 License: Ensures that the tool will always remain free and open for use for any commercial purposes without legal risks.
  • Community-driven Roadmap: The development plan is formed based on RFCs (Request for Comments) from the community, and not on the commercial interests of a single corporation.

Comparison of key aspects

Characteristic OpenTofu Terraform
License Open Source (MPL 2.0) Business Source License (BSL)
State encryption Native (client) Depends on the backend (SaaS/Enterprise)
Management Linux Foundation HashiCorp
Registry opentofu.org registry.terraform.io

Is it worth switching? 

If your organization values ​​open source and vendor lock-in, or client-side encryption of state files is critical to you, OpenTofu is an excellent choice.

Would you like me to help you create a migration plan or a sample configuration for encrypting the state file?

Prompt

What is it intended for? Let's say I'm creating a multi-agent system.

OpenTofu is designed for managing infrastructure as code (IaC). Simply put, instead of manually clicking through the cloud provider's console (creating servers, databases, and networks), you describe the desired architecture in text files, and OpenTofu automatically "builds" and connects these components.

If you're building a multi-agent system (MAS), OpenTofu will be responsible for the "foundation" on which those agents will live and communicate.


The Role of OpenTofu in Creating a Multi-Agent System

When agents become numerous, they require computing resources and a communication environment. Here's how OpenTofu helps in this scenario:

1. Deployment of computing nodes

You can describe a cluster (e.g. Kubernetes or a group of virtual machines) where your agents will run.

  • Example: You need 10 isolated containers for different agent types (scheduler agent, executor agent, etc.). OpenTofu will create them with a single command.

2. Setting up the communication environment (Message Brokers)

Agents must exchange messages.

  • OpenTofu will automatically deploy and configure RabbitMQ , Kafka , or Redis Pub/Sub to serve as the "bus" for your agents to communicate.

3. Database Management (Vector and Regular)

Agents often need memory (Long-term memory).

  • You can use code to set up a vector database (such as Pinecone , Weaviate , or Milvus ) and a regular one (PostgreSQL) to store conversation history and logs.

4. Scaling

If your system needs 500 agents instead of 5 tomorrow, you don't need to manually configure each server. Simply change one number in the configuration: count = 500, and OpenTofu will build the missing infrastructure.


Example of a logical diagram of work:

  1. Code (HCL): You describe: "I need a 3-node cluster and a database."
  2. OpenTofu (Execution): Compares your code with what's already in the cloud.
  3. Infrastructure: Creates servers -> Installs the runtime environment (Docker/K8s) on them -> Sets up the network between them.
  4. Agents: Now your Python scripts (agents) can run in this ready-to-use and stable environment.


Why choose OpenTofu over just scripts?

  • Idempotency: If you run the server creation script twice, it can create two servers. OpenTofu will recognize that one already exists and won't do anything extra.
  • Dependency Management: It knows that it needs to create a network first, and only then a server on that network.
  • Collaboration: The state file ( state) allows an entire team of engineers to work on the same infrastructure without chaos.

Can you suggest which cloud resources or providers would be best suited to host your multi-agent system?

Prompt

I'm interested in how to organize the exchange of messages between agents that exchange commands and data.

To organize communication between agents for exchanging commands and data, you typically use one of three architectural models. The choice depends on how autonomous your agents are and how critical delivery speed is.

Below is a breakdown of the main approaches you can implement and automate using OpenTofu.

1. Message Bus (Pub/Sub) — The Most Flexible Approach

This is a classic model where agents do not communicate directly. Instead, they send messages to "topics" or "exchanges."

  • How it works: Agent A sends a command to the topic commands. Agent B is subscribed to this topic, receives the command, executes it, and sends the result to the data topic.
  • Tools: RabbitMQ, Apache Kafka, NATS, or Redis.
  • Advantage: Agents are fully decoupled. If one agent goes offline, messages accumulate in the queue and are processed once it restarts.

2. Centralized Orchestrator (Shared Space)

In this model, there is a central "knowledge base" or dispatcher that distributes tasks and maintains state.

  • How it works: Agents write data to a shared database (e.g., a vector database or Redis) and check the status of tasks in a central table.
  • Tools: Redis (using Streams or Lists), PostgreSQL with queues (Listen/Notify).
  • Advantage: It is easy to track the state of the entire system and the history of all commands in one place.

3. Direct Interaction (gRPC / HTTP)

Agents contact each other directly via their network addresses.

  • How it works: Each agent has its own API endpoint.
  • Tools: gRPC (high speed, strict data typing) or FastAPI/Flask (simplicity).
  • Advantage: Minimal latency because there are no intermediate layers.
  • Disadvantage: Harder to scale. If an agent's IP address changes, others must be notified (requires Service Discovery).

Technical Implementation: The Role of OpenTofu

Once you decide on the architecture, you need to "spin up" the infrastructure for this communication. OpenTofu automates this process:

  1. Broker Deployment: OpenTofu code deploys a RabbitMQ or NATS cluster.
  2. Queue Configuration: It immediately creates the necessary queues (e.g., task_queue, results_queue).
  3. Security: It generates unique credentials for each agent type, ensuring an "executor" agent cannot accidentally delete messages from the "planner" queue.
  4. Service Discovery: If using gRPC, OpenTofu can configure Consul or internal DNS in Kubernetes so agents find each other by name (e.g., agent-alpha.local) rather than static IPs.

Where to start?

If you are just starting to design your system, I recommend the Redis + Python (using libraries like Celery or TaskIQ) or NATS combination. This provides a great balance between simplicity and performance.

Would you like me to provide a sample OpenTofu configuration to deploy a message broker (like Redis or RabbitMQ) in Docker or a cloud provider?

Recent work on system messaging

Mike's Notes

The work this week. 

I found a problem with the existing Pipi 6 era Messaging Engine (msg). It needed to be replaced to support the richer Pipi 9 internal environment of autonomous agents that can self-organise and move.

I spent days staring into space, and then the solution became very clear while daydreaming 😴 between morning coffee and ice cream summer afternoons 😎. Slow but getting there.

Resources

References

  • Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions (Addison-Wesley Signature Series), by Gregor Hohpe and Bobby Woolf.
  • Integration Patterns (Patterns & Practices). Microsoft.

Repository

  • Home > Ajabbi Research > Library > Authors > Gregor Hophe
  • Home > Handbook > 

Last Updated

25/01/2026

Recent work on system messaging

By: Mike Peters
On a Sandy Beach: 22/01/2026

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

Pipi 4 (2005-2008)

A simple internal messaging system was invented and built for Pipi 4 around 2005. It had a messaging store and handled interactions between different internal systems. It was not called a messaging system; it was an unnamed part of the Metadata Repository. It was built in CFML code and ran on MS SQL Server.

Pipi 6 (2017-2019)

After reading about Apache Kafka and RabbitMQ, I realised that Pipi 4 had contained a primitive internal messaging system, 5 years before Kafka. A new, quick-and-dirty broker-based messaging system, similar to RabbitMQ, was built as a CFML module to send messages across hundreds of modules when Pipi 4 was rebuilt from memory as Pipi 6. The work of Gregor Hohpe and Bobby Woolf was an inspiration. Martin Fowler was an enormous influence on everything.


Pipi 9 (2023-

It has taken all week to figure out a solution. I ended up splitting the role of the engine

  • The existing Pipi 6 messaging module is now being fully refactored into the Pipi 9 Messaging Engine (msg). Previously, it used a wrapper to make it appear to be an agent. Some minor changes are being made to the Namespace Engine (nsp).
  • The new Messaging Engine (msg) is used to create the local messaging systems via the Factory Engine (fac). The Factory Engine (fac) will place a local message store using a separate embedded database inside each agent to connect to its Messaging Endpoint. Each system (made up of many agents) has its own messaging store attached to the system router.

As agents dynamically self-assemble in response to events, the messaging system always works.

The new Engine closely follows the patterns in Enterprise Integration Patterns and even uses the same icons, aka "Gregorgrams".

Summary of changes underway

  • Internal system messaging between hundreds of autonomous agents.
  • Possible because of the Namespace Engine (nsp) built into Pipi 8.
  • External messaging between Pipi and applications, such as databases, cloud platforms and containers, will be handled using an open-source messaging system, such as Kafka or RabbitMQ.
  • The two messaging systems need to interconnect. I don't know how yet! 😇
  • Event messaging and Pub/Sub are the most common; others include CQRS, Dead Letter, and Point-to-Point.
  • Each agent has a state.
  • Essential for robust Pipi self-management.
  • The pipiWiki will automatically document the configuration of each engine, including messaging (Gregorgrams) diagrams. See link to the Primative Engine (prm) mockup above.
  • The messaging configuration can change dynamically.
  • The Messaging Engine will be accessible for configuration via the coming msg module in the Agent Workspace

Pub/Sub Clearly Explained (in Under 6 Minutes)

Mike's Notes

Excellent description.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Level Up Coding System Design
  • Home > Handbook > 

Last Updated

14/01/2026

Pub/Sub Clearly Explained (in Under 6 Minutes)

By: Nikki Siapno
Level Up Coding System: 20/12/2025

Founder LUC | Eng Manager | ex-Canva | 400k+ audience | Helping you become a great engineer and leader.

(6 Minutes) | How It Works, Best Practices, Why Teams Reach for It, When Not to Use It, and Tradeoffs

Pub/Sub Clearly Explained

Pub/sub is not “just a queue with topics.”

It’s a different way of wiring a system. Instead of calling a specific service and waiting, you publish an event and let whoever cares react.

That shift sounds small, but it changes your scalability, your failure modes, and how teams add features without tripping over each other.

If you’ve ever added “just one more downstream call” and watched latency, coupling, and deploy coordination explode, pub/sub is the pattern that turns that mess into a cleaner fan-out.

What Pub/Sub Actually Is

Publish/subscribe (pub/sub) is a messaging pattern where publishers broadcast messages to a topic, and subscribers receive messages for the topics they registered to, without either side needing to know who the other is.

That last part is the real power: decoupling.

The publisher doesn’t care who listens, and the listener doesn’t care who produced the event.

Each side evolves, scales, deploys, and even fails independently. You stop wiring services together one call at a time and start letting events flow through the system like signals; allowing teams to plug in new subscribers without modifying the service that emits the event.

How It Works

Most pub/sub setups have a broker (also called an event bus). Publishers send messages to the broker on a named topic/channel, and the broker routes a copy to every subscriber of that topic.

Key parts:

  • Publisher → Produces an event and publishes it to a topic.
  • Topic/Channel → The category label that subscribers use to filter what they receive.
  • Message broker → Tracks subscriptions and handles routing/buffering/filtering.
  • Subscriber → Consumes events for topics it subscribed to.

Two details that matter in real systems:

  • Push vs pull → Brokers may push events to subscribers, or let subscribers pull, depending on the implementation.
  • Transient vs durable → Some brokers store-and-forward (so offline subscribers can catch up), others only deliver to active subscribers.

Why Teams Reach for Pub/Sub

Pub/sub earns its popularity the moment a team feels how much friction it removes.

Instead of stitching services together with fragile chains of calls, you let events move through the system and let each component respond on its own terms.

That shift opens up a kind of freedom that’s hard to give up once you’ve experienced it.

It buys you that freedom in three ways:

  • Loose coupling → Publishers and subscribers don’t need to know about each other, so components evolve independently.
  • Async flow → Publishers don’t wait for subscribers to finish, so the system stays responsive and producers keep moving.
  • Fan-out scaling → One event triggers many independent workflows, and subscribers can scale out in parallel.
And you see the impact immediately.

A UserSignedUp event can kick off analytics, send a welcome email, run fraud checks, and sync the CRM; all without the signup service knowing or calling any of those systems.

The Tradeoffs

Pub/sub feels like a breakthrough the first time you use it.

You publish an event, everything reacts in parallel, and the system suddenly looks cleaner and more scalable.

But the moment you move past the happy path, you start to see the tradeoffs that come with that freedom. The architecture gets simpler; the responsibilities shift somewhere else.

Here’s where the cracks start to show:

  • Debugging gets harder → Tracing “who reacted to what” is less obvious, so you need strong observability.
  • Delivery semantics vary → Many systems are at-least-once or at-most-once, so consumers must handle duplicates or missed messages.
  • Ordering is not guaranteed → Global ordering is difficult; you may only get ordering within a partition, or none at all.
  • Operational overhead is real → Running brokers, scaling subscribers, and tuning throughput adds complexity.
  • Fire-and-forget” can bite you → Publishers typically only know whether the broker accepted the event; not whether any subscriber successfully processed it.
  • Schema still couples you → You remove service coupling, but you keep contract coupling via event shape/meaning.

In the end, pub/sub doesn’t fully erase complexity; it moves it.

Instead of wrestling with chains of downstream calls, you manage event contracts, consumer behavior, and the operational realities of the broker.

The system becomes more flexible, but you’ll need to carry the new responsibilities that come with that flexibility.

When to Use Pub/Sub

As systems grow, there’s a moment when direct calls stop being a convenience and start becoming a liability.

Pub/sub shines in that moment.

Use it when your system benefits from decoupled, asynchronous, one-to-many communication.

Where it works well:

  • One-to-many events → A single event triggers updates across multiple services/UI components.
  • High throughput workflows → Many tasks can run in parallel off the same event.
  • Rapid evolution → You expect to add new subscribers later without changing the service that emits the event.
  • Real-time feeds/notifications → One update fans out to many subscribers without polling.

When Not to Use It

But pub/sub isn’t magic; it works beautifully in the right context and poorly in the wrong one.

Don’t force pub/sub into problems that need the following:

  • Single dedicated recipient → Use a queue or direct call because fan-out isn’t needed.
  • Strict global ordering → Pub/sub ordering is hard; you’ll add complexity fast.
  • Immediate confirmation → If the workflow requires “did it succeed?” you need request/response or an explicit acknowledgment protocol.
  • Small/simple systems → Pub/sub can be overkill when a few components can just talk directly.

Best Practices

Pub/sub works best when you treat events as first-class APIs.

The architecture gives you room to move, but only if the contracts and operations are strong enough to support that freedom.

These practices help keep the system predictable as it grows:

  • Design clear event schemas → Define each field, its meaning, and its expected stability because consumers rely on your contract.
  • Version events thoughtfully → Add fields in a backward-compatible way and deprecate slowly so subscribers have time to adjust.
  • Make consumers idempotent → Handle duplicates safely because delivery semantics vary across brokers.
  • Keep events focused → Emit facts (“UserSignedUp”) rather than commands (“SendWelcomeEmail”) because facts allow many independent reactions.
  • Avoid leaking internal details → Publish stable domain-level events instead of exposing internals that might change as the system evolves.

Recap

Pub/sub gives you a different way to wire a system: publish once, react in many places.

It removes the tight coupling of direct calls and opens the door to parallel workflows, faster evolution, and cleaner boundaries between teams. But it also shifts complexity into event design, operations, and debugging.

In the end, pub/sub pays off when you embrace its power and its responsibilities.

Why Was Apache Kafka Created?

Mike's Notes

The backstory.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

19/12/2025

Why Was Apache Kafka Created?

By: Stanislav Kozlovski
Big Data Stream: 23/08/2025

7yr experience with Kafka; committer; writes concisely about kafka and big data engineering.

EDIT: This reached first page on Hacker News and spurred some good discussion. Check it out here:

  • https://news.ycombinator.com/item?id=44988845
  • https://lobste.rs/s/pc87c0/why_was_apache_kafka_created

Intro - the Integration Problem

We talk all the time about what Kafka is, but not so much about why it is the way it is.

What better way than to dive into the original motivation for creating Kafka?

Circa 2012, LinkedIn’s original intention with Kafka was to solve a data integration problem.

LinkedIn used site activity data (e.g. someone liked this, someone posted this) [1] for many things - tracking fraud/abuse, matching jobs to users, training ML models, basic features of the website (e.g who viewed your profile, the newsfeed), warehouse ingestion for offline analysis/reporting and etc.

The big takeaway is that many of these activity data feeds are not simply used for reporting, they’re a dependency to the website’s core functionality.

As such, they require very robust infrastructure.

Their old infrastructure was not robust.

It mainly consisted of two pipelines:

The Batch Pipeline

One was an hourly batch-oriented system designed purely to load data into a data warehouse.

Applications would directly publish XML messages of the events (e.g profile view) to an HTTP server. The system would then write these to aggregate files, copy them to ETL servers, parse & transform the XML and finally load it into the warehouse infrastructure consisting of a relational Oracle database and Hadoop clusters.

The Real-Time Pipeline

They used a separate more real-time pipeline for observability. It contained regular server metrics (CPU, errors, etc.), structured logs and distributed tracing events, all flowing to Zenoss.

It was a cumbersome manual process to add new metrics there. This pipeline’s data was not available anywhere else besides Zenoss, so it couldn’t be freely processed or joined with other data.

💥 The Problems

There were a few commonalities between both of these pipelines:

  1. manual work - both systems required a lot of manual maintenance, both to keep the lights on and add new data to.
  2. large backlogs - both systems had large work backlogs of missing data that needed to be added by the resource-constrained central teams responsible for them.
  3. no integration - both of these systems were effectively point-to-point data pipelines that delivered data to a single destination with no integration between them or other systems.

Along the line, LinkedIn figured out that they gained massive value from the simple act of integration - joining previously-siloed data together. Just getting data into Hadoop would unlock new features for them.

As such, the demand for more pipelines to move data to Hadoop grew. But data coverage wasn’t there - only a very small percentage of data was available in Hadoop.

The current way was unsustainable - the pipeline team would never be able to catch up with their ever-growing backlog as more teams realized the value and demanded more features.

There were too many problems - both individual within the pipeline and fundamental throughout the architecture - that made it impossible to scale:

  • Schema Parsing: there were hundreds of XML schemas. XML did not map to the downstream [2] systems (e.g. Hadoop), so it required custom parsing and mapping.
    • This was time consuming, error prone and computationally expensive - they would often fail and were hard to test with production releases. This then led to delays in adding new types of activity data, which forced them to do hacks like shoe-horning new data into already-existing inappropriate types in order to avoid extra work but ship on time.
  • Brittle: The pipeline(s) were critical, because any problem in it would break the downstream system (i.e website feature). Running fancy algorithms on bad data simply produced more bad data.
    • Reliability had to be prioritized.
  • Schema Evolution: adding/removing fields from the schemas without breaking downstream systems in the pipeline was hard.
    • The app generating the data would own the XML format - testing and being aware of all downstream uses was difficult for them, especially due to the asynchronous nature of the pipeline.
    • There wasn’t good communication between teams either, so sometimes the schema would change unexpectedly. [3]
  • Lag: it was impossible to inspect the activity metrics in real time. They would only be available hours later (via the batch process).
    • This led to a long lag time in understanding and resolving problems with the website.
  • Separation of Data: the fact that operational server metrics can’t be joined with the activity data was problematic - it further prevented them from correlating, detecting and understanding problems (e.g. decrease in page views can’t always be seen in server CPU metrics)
  • No Deep Operational Metrics Analysis: can’t run long-form longitudinal queries on the operational metrics because they’re only present in a real-time system that doesn’t support such queries.
  • Single Destination: the use cases for the activity data were growing by the day. But the clean data was solely present in the warehouse. Even if it had 100% data coverage, it wasn’t enough for the data to just be there. It had to go to other destinations too.

What Did They Need?

Reading these problems, LinkedIn’s requirements are pretty clear.

They needed to have robust pipeline (1/7) infrastructure (i.e a standardized, well-maintained API). This pipeline had to be scalable (2/7).

They needed to handle schemas (3/7) properly - commit to a backwards-compatible contract.

They needed the system to handle high fan-out (4/7) - the activity data, for example, needed to go to a lot of destinations.

They needed it to be real-time (5/7) - measured in seconds, not hours.

They needed plug-and-play integration capabilities (6/7). Ideally new sources/sinks would be very easy to load, without any manual work. For this to work, they needed structured schemas (the 3/7) with clean data (6/7), so no extra processing would be required.

And they needed to switch the ownership (7/7) of some of this work, because the two small teams handling the pipelines would have never caught up on their large backlogs while the rest of the organization kept coming up with new use cases.

Something like this had to happen

PS: While evaluating solutions, they figured out that the system had to support data backlogs - i.e decouple writers from readers (8/8). Some systems they tested (ActiveMQ) would collapse in performance when readers fell behind and a significant data backlog accumulated.

Enter - Kafka 🔥

They created Apache Kafka. This solved for 5/8 of the problems:

  • Robustness (1/7) - being a distributed system with built-in replication, failover and durability guarantees meant single machine hiccups would not disrupt the pipelines
  • Scalability (2/7) - the distributed nature and sharding of topics via partitions made it horizontally scalable on commodity hardware
  • High read fan-out (4/7) - the lock-free design of the Log data structure made high-scale reading trivial
  • Real-time (5/7) - the system was real-time, although funnily initially average latency of their large multi-cluster pipeline was 10 seconds. Nowadays this’d be a lot lower.
  • Decouple writers from readers (8/8)- the fact that the data was buffered to disk by design allowed them to set longer retention and not tie the retention to whether the message was consumed or not. This meant slow readers would never impact the system.

The other three problems were schemas, data integration and ownership.

Let’s dive into them, because they’re pretty interesting.

1. Schemas

They moved from XML to Apache Avro as both the schema and serialization language for all the activity data records, as well as downstream in Hadoop.

This led to significantly less data size - Avro messages were 7x smaller than XML. They additionally compressed them 3x down later when producing to Kafka.

They developed what seems like the precursor to Confluent’s Schema Registry: a service to serve as the source of truth for the schema in a Kafka topic, as well as maintain a history of all schema versions ever associated with the topic.

Kafka messages carried an id to refer to the exact schema version it was written with. This versioning made it impossible to break deserialization of messages, because you could always know the right schema to read a message with. [4]

This was a much needed improvement, but it wasn’t everything they needed.

While messages were individually understandable, Hadoop applications could still be broken by toying with the schema - e.g. removing a field in the record they needed to use. They generally expected a single schema to describe a data set. [5]

To solve this backwards compatibility issue, LinkedIn developed a compatibility model to programmatically check schema changes for backwards compatibility against existing production schemas. The model only allowed changes that maintained compatibility with historical data - something that sounds just like schema registry’s `BACKWARD` compatibility level. [6]

2. Plug & Play Integration

To make new data very easy to integrate without any extra manual work, you need to settle on a single schema between the upstream and downstream system.

Having even slight differences means you’ll always have to translate them between systems - i.e., an extra JIRA ticket for somebody to manually write, test & deploy code to transform the data into whatever the downstream system’s requirements.

But if the schema is the same, then that work doesn’t exist.

Ideally, you could then fully automate data offload to sinks because there’s no extra work to do besides writing to the particular sink system’s API.

Another big problem with the different schema approach was the time at which problems would surface. Hadoop used a Schema on Read model, where data would be stored in files, unstructured [7], and the structure would only be defined within each script that reads the files. This meant that things would break much later, at query time, not at the time of ingestion.

Traditionally, at the time, the data warehouse was the only location with a clean version of the data. The dirty, unstructured data would land in Hadoop, and then get cleansed/curated through various scripts (if they didn’t break).

Access to such clean & complete data was of utmost importance to any data-centric company, so having it locked up in the warehouse didn’t scale to meet the organization’s needs. Not to mention it only arrived in the warehouse after hours of delay - very problematic for anybody who wants to access it sooner.

The solution was very straightforward - “simply” move the clean data upstream to a real-time source. Clean it as it lands into Kafka - i.e adopt a Schema on Write model.

This not only allows plug and play integration with the warehouse (e.g creating a new table from a new topic is a piece of cake), but also makes it available for other types of consumers - be it real-time or batch.

Similarly, schema changes like adding a new field could be automatically handled.

That’s exactly what LinkedIn did - define a single uniform schema with the canonical, cleansed format of the particular message. But this required organizational change to execute:

3. Ownership and Governance

Previously, it was the pipeline team’s responsibility to match the schema to the downstream system. LinkedIn needed to move this ownership away from them if they wanted to ever get to 100% data coverage. This was the final step in solving the problem.

The best team to drive this? The team(s) that created the data, of course! They best know what the cleanest representation of the data should be.

Agreeing on a uniform schema amongst downstream systems was still a joint effort though.

LinkedIn established a mandatory code review process between all involved teams - whenever schema code was changed, there would need to be LGTMs from stakeholders before the next production release [8].

A side-effect of this was that it helped document the thousands of fields in their hundreds of schemas.

Summary

This was a very long-winded way of saying that:

  • data needs to be integrated between many systems
  • schemas play a very crucial role in this, make sure you define them well and establish good ownership
  • Kafka, with schemas, was literally invented to solve this problem at scale.

In the end, this proved very effective for LinkedIn. A single engineer was able to implement and maintain the process that does data loads for all topics with no incremental work or co-ordination needed as teams add new topics or change schemas.

Source: this 2012 paper and this blog.

Schema Driven Development Anyone?

One thing that surprised me while reading this paper was the emphasis on schemas.

I’ve been vocal before that I believe the lack of first-class schema support was Kafka’s biggest mistake.

The paper’s solution shares a lot similarities to Buf’s schema-driven development vision. Buf has been talking about universal schemas for the longest time, and built a diskless Kafka system that prioritizes schemas as a first-class citizen. I haven’t seen any other Kafka provider focus on schemas that much.

What surprised me precisely in this paper is that it, 13+ years ago, described a problem for which LinkedIn literally invented Kafka and auxiliary systems to solve, then took their time to write out the universal schema solution really well:

why am I hearing about this schema-centric vision a full decade later from a Protobuf/Kafka startup?

I’ve posted before on how Kafka lacks first-class schemas and how Buf seems to be the only one in the space beating the drum on their importance:

Every valuable use-case requires schemas. You can’t use Kafka Connect to integrate data between upstream system A, Kafka and downstream system B without the existence of schemas — because chances are both systems A & B require some structure. Ditto for stream processing - you can’t do joins and aggregations on 1s and 0s.

Every message has a schema - it’s either explicit and defined in a single place, or implicit and scattered throughout your application’s code.

Fragmentation - absent of an “official” schema registry that ships with Kafka, we have dozens of options to choose from - Confluent’s Schema Registry, Karaspace, AWS Glue, Apicurio, Buf’s Schema Registry, etc.

No Server-Side Validation - the Producer client is the only one who validates the schema prior to writing. There’s no way to defend against buggy (or malicious) clients, hence no way to enforce a uniform schema under all circumstances.

Again, I really commend Buf’s server-side validation. When you embrace the fundamental idea that messages must have schemas and enforce it on the server, a lot of things open up:

  • Native Iceberg integration - writing into an open table format becomes a trivial job of translating one schema language (e.g Avro or Protobuf) to another’s (Parquet’s) [9]
  • Semantic validation - ensuring message fields match a specific format (e.g email validation, age validation)
  • Filtering fields based on policies (RBAC) - certain sensitive fields ought to not be readable by certain groups.
  • Debugging - if a bad message somehow makes it in there (e.g configuration wasn’t right), the server can immediately pin-point it.
  • Filtering bad messages directly on the server - avoiding the need to have custom poison pill handling code (e.g send into a DLQ topic) in each consumer application.

Schemaless Kafka

Lack of schemas continues to be my biggest gripe with Kafka.

The fact that LinkedIn treated schemas as first-class citizens all the way back in 2012 really has left me scratching my head…

They knew this was a major problem, yet never baked it into the product. Neither did Confluent after branching out of LinkedIn.

I’m uncertain how much of the schema decision was a business decision versus a technical one.

Back in the days, Confluent made their money off of their on-premise Confluent Platform package which priced things per node, hence a financial incentive existed to deploy more nodes (i.e. a schema registry cluster with three nodes for HA versus bundling it in the broker).

Perhaps there was investor pressure to preserve pricing optionality? Any business has to make money, and you can’t give away your most valuable features. It’s pretty common in the open-core business model to gate enterprise features behind paywalls.

Schema Registry is source-available though. In fact, it’s pretty much free to use unless you plan on selling it as a SaaS. But basic features like ACLs require a paid license. Something open-source Schema Registries like Karaspace offer for free. And hence my point on fragmentation in the space. It’s got to the point where we have to have proxies that enforce schemas [10].

At the same time, there are some technical reasons why no-schema can be preferred:

  • Enforcement on the client scales easier, as no bottleneck exists in the broker.
  • Any state management on the broker could require extra resource usage (parsing schemas [11], validating) and worse off — it could block the stream as it’d require additional locking. That’s contrary to the value prop of Kafka for slinging lots of bytes fast.

I believe it didn’t have to be an either-or decision today. Kafka could have shipped with first-class schema support built into topics - just an optional toggle.

And I don’t think performance would be hurt to the point of being unusable. Few people push Kafka to its true limits anyway - it’s frequently bottlenecked on network and storage. Not to mention that serialization overhead on CPUs has gone a long way since 2011 - the Java libraries improved, Java’s GC improved, JVM improved and CPU perf skyrocketed.

In hindsight, it’s clear that the schemaless wave (“it’s just byte arrays!”) was a fad that went away. We see which model won out in the SQL vs NoSQL wars - Postgres is not eating the world today by accident. [12]

I keep asking - why doesn’t Apache Kafka have schemas?

  1. My understanding is this was all sort of user activity on the website. Sending a message to somebody, applying for a job, liking a post, reposting something, commenting under something, opening a profile. Basic features off the website work on this principle - e.g you get a notification of who “viewed your profile”. Less basic things too - the newsfeed adjusts to what you’ve been consuming; jobs get recommended to you based on what you’ve been applying to/viewing, etc.
  2. I used to always confuse upstream and downstream together for whatever reason, so let me clarify: data has a source (where it comes from) and a destination (where it goes to). In this article, I use the following words interchange-ably: {Source, Upstream, Producer} and {Destination, Downstream, Sink, Consumer}
  3. Probably the #1 problem in data engineering - schemas aren’t respected! They’re the contract between your systems, so naturally any unexpected changes there will break the pipeline. We, as an industry, need to treat this contract as something explicit (not implicit) and develop more tooling/automation around it.
  4. Said more explicitly, if my schema database naively stores only one schema (e.g. a key-value pair of topic name to latest schema), I wouldn’t be able to deserialize old messages because I don’t know their exact schema! This is an important problem to solve for, because even if you store very little data (e.g 24hrs’ worth), there will always be a t=1h time point where you have 1 hours’ worth of the latest schema and 23 hours’ worth of the old schema. No consumer would be able to read everything within that window until the old data gets deleted. Hence - versioning of schemas and associating each message to a version to the rescue.
  5. Hadoop tools like Hive and Pig would work with many, many files, but expected one fixed schema for all of them. If you change the schema, it’s expected to rewrite all your historical data with the new schema. In LinkedIn’s case, this was hundreds of TBs worth of data.
  6. Nowadays these tools are pretty standard. `buf breaking` is another standard CLI tool that comes to mind with relation to checking compatibility for protobuf
  7. Unstructured data — data that does not conform to a pre-defined model or schema. This type of thing was very popular during the NoSQL wave.
  8. This is what the buzzword Schema Governance means. A set of rules/processes/tools to help you control how schemas are handled in data pipelines.
  9. Funnily this is what initially got me thinking into why Kafka doesn’t support schemas as first-class citizens. I asked about it on Reddit here - https://www.reddit.com/r/apachekafka/comments/1h80if5/why_doesnt_kafka_have_firstclass_schema_support/.
  10. I think Kroxylicious is super cool. But in an ideal world, we wouldn’t need a third-party proxy to enforce such a basic feature.
  11. I don’t have actual data as to how much resource usage parsing would take. I tried figuring out how Bufstream does it (since they’re the only solution with server-side validation i.e parsing) and they seemed to have written their own protobuf parser that’s allegedly 10x faster. Maybe something like that is necessary to keep performance under check. Even if so - that parser is Apache-licensed so nothing would stop Kafka from adopting it.
  12. Well, it didn’t literally go away - it just faded in significance. Postgres supports “NoSQL” like JSON arrays too now, and the general flexibility is welcomed. But it’s clear it’s not the default.