---
topic: dev-practices
author: Crashtech Editorial
published: Nov 14, 2026 · read: 4 min
---

Carrying Patches on an Open-Source Fork: Upstream-First vs Hotfix, Without Drifting Away

Release branches, patch triage and the rule for choosing between upstream-first and hotfix, from a Kafka fleet of 4,000 brokers and 7 trillion messages a day.

– –

Using open-source software at scale ends in one of two places: you wait for upstream to fix what hurts you, or you start carrying patches. Carrying patches is fine. Carrying them without a system is how a team ends up on a five-year-old fork nobody dares to upgrade.

The scale that forces the question

Kafka at that size means:

DimensionMagnitude
Clusters100+
Brokers4,000+
Topics100,000+
Partitions7 million
Messages per day7 trillion

Stock Kafka is excellent and was not tuned for clusters of 140+ brokers with millions of replicas each. The surrounding ecosystem tells you what a platform team ends up building: a REST proxy for non-Java clients, a schema registry for Avro, a mirroring tool for cross-cluster replication (Brooklin), a rebalancing and self-healing service (Cruise Control, now open source), a completeness audit to prove no data was lost, and a usage-metering tool for chargeback.

Release branches: fresh base, carried patches

Each internal release is a branch cut from a specific upstream version, such as an internal 2.3.0.x built on Apache 2.3.0. Patches on it fall into four groups:

Patch typeOrigin
Upstream patchesAlready in the official release the branch started from
Upstream-first patchesWritten by the company, merged upstream first, then pulled in
Hotfix patchesWritten internally first for an urgent problem, to be sent upstream afterwards
Internal-only patchesSpecific to the company, not suited to upstream
  1. Cut the new branch from the latest official release

    Never extend an old fork forward. Start from the newest upstream version so you inherit every upstream fix and feature.

  2. Re-apply only what upstream lacks

    Walk the previous internal branch’s patches and carry over just those not yet merged upstream. Merged ones are dropped automatically, because they are already in the new base.

  3. Mark patch status in commit messages

    A consistent note in each commit says whether it is merged upstream, pending, or internal-only, so a script can generate the carry-over list.

  4. Track upstream continuously

    Pull in upstream changes regularly instead of once a year, so each integration is small.

  5. Test with real data and real load

    Run the candidate branch on production-shaped traffic and compare performance and behaviour before it serves anything important.

The fork's real cost is the carry list

Every patch you keep private is work you redo on every upstream release. The measure of a healthy fork is a carry list that shrinks over time, not one that grows.

Choosing the path for a change

Upstream-first the default
Make the change in the official project, pull it into your branch after it merges. Best when it can wait a week or so, is modest in size, or is a new feature. New features that go through the project’s proposal process (a KIP, in Kafka’s case) are always upstream-first.
Hotfix-first for urgent production pain
Patch your own branch right away to stop an incident, deploy, and then submit upstream afterwards. Speed is the point, and the follow-through is the discipline.

The decision turns on three questions:

  1. How urgent is it? A live production problem argues for the hotfix.
  2. How long would upstream review take? Quick review favours upstream-first.
  3. Is it a feature or a bug fix? Features go upstream first. Bugs may be either.
Advertisement

What kind of patches arise at scale

Scalability. Controllers that ran short of memory and were slow in huge clusters were fixed by reusing objects and reducing logging. Slow broker startup and shutdown improved by reducing lock contention between threads.

Operability. Draining a broker before removal is hard when new data keeps arriving on it. A maintenance mode for brokers, where no new replicas or traffic are assigned, makes safe removal straightforward.

Features. Per-user usage tracking for chargeback, enforcement of a minimum replication factor when topics are created, and resetting a consumer to the closest valid offset.

Contributions back. Better quota handling, detecting stale control messages and long-offline brokers, separating control and data channels, and bounding how far log compaction may lag.

Rules for any team that forks

Do

Cut each internal release from the newest upstream, keep patches small and separately revertible, label their status, upstream everything that is not company-specific, and review the carry list every release with an aim to shrink it.

Don't

Fix a bug in your fork and never tell the project. Two years later you maintain a private variant of a feature that upstream implemented differently, and the merge is a rewrite.

If you are not at this scale, the same principles apply to a single patched dependency: pin the version, record why each patch exists, and open the upstream issue the same day you write the workaround.

Takeaway

Customising open source is sustainable when it is a process: fresh branches from official releases, patches labelled by status, upstream-first as the default, hotfix as the exception, and a standing habit of contributing back. Then your fork stays close to the project, upgrades stay routine, and the community carries part of your maintenance load.

Advertisement

Frequently asked questions

What does upstream-first mean?

Making a change in the official open-source project first and pulling it into your own branch after it merges. The change lands in a form maintainers accept, you carry no private divergence, and everyone benefits. It suits changes that can wait days or weeks, and new features that go through the project's proposal process.

When is a hotfix on your own branch justified?

When a production problem needs fixing faster than the upstream review cycle allows. Patch your branch immediately, then submit the change upstream afterwards. The discipline is to track every hotfix and either upstream it or document why it must stay private.

How do you keep a long-lived fork maintainable?

Cut each new internal release branch fresh from the latest upstream release, then re-apply only the patches that upstream does not yet contain. Tag every patch with its status in the commit message so a script can tell which are merged upstream, pending or permanently internal.

Why contribute patches back at all?

Every private patch must be rebased on every upstream release, forever. Upstreaming deletes that cost, gets maintainer review that improves the change, and lets the community share the maintenance. Patches that encode company-specific behaviour are the legitimate exception.

Sources & further reading

/* Comments */