← Back to list
SW Engineering & Management
#모노레포#폴리레포#의존성그래프#빌드캐시#형상관리
Last updated · 2026-11-08

Monorepo Strategy and Comparison with Polyrepo

1. Overview

Definition: A monorepo is a source control management strategy that consolidates the code of multiple projects, services, and libraries into a single version control repository. Within one repository, code is shared, multiple modules are changed atomically in a single commit, and development proceeds over a unified build and dependency graph. It is a problem of code organization, distinct from the monolithic architecture that denotes a single deployment unit.

As a software organization grows, the number of services and libraries climbs into the hundreds and thousands. The polyrepo (multi-repo) approach of placing each artifact in a separate repository feels natural at first, but as common libraries are referenced by version across many repositories, it becomes hard to track "which repository uses which version." To apply a single security patch to a shared module, you must raise a version-bump PR in each of the dozens of repositories that use it — the so-called dependency hell and diamond dependency problem.

A monorepo resolves this through a change of perspective. Instead of "splitting code along repository boundaries and binding it by version," it binds code "at the source level within a single repository (source dependency)." As a result, the commit that modifies a shared library and the modifications of all its consumers happen atomically within the same commit and the same PR. "Where does a change have impact" is answered immediately not by a version matrix but by static analysis of the dependency graph.

From a professional engineer's answer perspective, a monorepo is not merely "a big repository" but an engineering trade-off in which the values of atomic change, a single source of truth, and global visibility are gained in exchange for the costs of build performance, access control, and scalability, which are offset by dedicated tooling. More important than the fact that giant organizations such as Google, Meta, and Microsoft chose monorepos is explaining "why a monorepo cannot stand without a dedicated build system."

1.1 Background and Necessity

First, the demand for code sharing and duplication removal. As microservices multiply, common code such as authentication, logging, and serialization is either copied into each service or carved out into an internal package distributed by version. Copying breaks consistency; version distribution causes upgrade lag. A monorepo keeps common code as a single source and lets every consumer reference the latest HEAD directly, eliminating both problems at once.

Second, the safety of large-scale refactoring and API change. In a polyrepo, when you change the function signature of a common library, consumers still reference the old version, so you cannot immediately tell whether the change "broke" anything. In a monorepo, the library and all call sites are fixed together in one commit and tested at once, so if compilation and tests pass, the whole organization is guaranteed not to be broken.

Third, developer experience and onboarding. A new developer who clones a single repository can explore, search, and change the entire codebase, and unified code style, lint, and CI rules can be applied wholesale. However, this advantage reverses into a clone and build performance problem once the repository reaches tens of millions of files, so it must always be discussed together with dedicated tooling.

2. Overall Structure and Dependency Model

The essence of a monorepo lies not in repository layout but in expressing inter-project dependencies as a source-level graph. The structure diagram below shows multiple applications and shared libraries within a single repository bound into one dependency graph, operating over a common build, CI, and tooling layer.

graph TD
  subgraph REPO["Single repository (monorepo)"]
    subgraph APPS["Application layer"]
      WEB["web-app"]
      API["api-service"]
      BATCH["batch-worker"]
    end
    subgraph LIBS["Shared library layer"]
      AUTH["lib-auth"]
      LOG["lib-logging"]
      MODEL["lib-domain-model"]
    end
    WEB --> AUTH
    WEB --> LOG
    API --> AUTH
    API --> MODEL
    BATCH --> MODEL
    BATCH --> LOG
    AUTH --> LOG
  end
  TOOL["Build system / CI (graph-aware)"] -.analyze.-> REPO

In the graph above, modifying lib-logging brings lib-auth, web-app, api-service, and batch-worker, which reference it (directly or indirectly), into the affected set. The core capability of dedicated monorepo tooling is precisely to compute this impact graph accurately and, for a changed commit, select "only the projects that must be rebuilt and retested." This is called affected/impacted analysis, and it is the performance secret that preserves safety without building the entire repository every time.

2.1 Source Dependency and Atomic Commits

Whereas a polyrepo references artifacts by version number (e.g., lib-auth@1.4.2) as a binary dependency, a monorepo defaults to a source dependency that references a path directly within the source tree. This difference derives almost every strength and weakness of a monorepo.

With source dependency, the library's "current HEAD" is the only version, so diamond dependency conflicts arising from multiple versions being live at once vanish at the root. At the same time, an atomic commit becomes possible — for example, a change that renames a field in lib-domain-model and the modifications to api-service and batch-worker that use that field are bundled into one commit, so the repository always stays in a "buildable, consistent state." A rollback, too, is reduced to a git revert of that single commit, restoring everything together.

Conversely, this also means the risk of tight coupling — "someone may change a common library without my knowing and break my service." Hence a monorepo must be accompanied by CODEOWNERS that enforce ownership boundaries as code, visibility rules, and a system that runs full impact tests in CI before changes.

2.2 How the Build and CI Pipeline Works

In a monorepo, the naive approach of "build everything" collapses the moment the repository grows. Building and testing thousands of projects on every commit explodes CI time into hours. Therefore monorepo tooling performs only the necessary work through the following pipeline.

flowchart LR
  COMMIT["Commit/PR changed files"] --> GRAPH["Compute dependency graph"]
  GRAPH --> AFFECTED["Select affected projects"]
  AFFECTED --> CACHE{"Build cache hit?"}
  CACHE -->|"hit"| REUSE["Reuse cached artifacts"]
  CACHE -->|"miss"| RUN["Build/test only that task"]
  RUN --> STORE["Store in remote cache"]
  REUSE --> RESULT["CI result"]
  STORE --> RESULT

The key lies in two optimizations. First, impact-based selection: trace the dependency graph in reverse from the changed files to pick only the projects that must be revalidated. Second, a content-addressed cache: fingerprint a task's inputs (source, dependencies, command, toolchain) as a hash, and if the inputs are identical, download and reuse the already-computed artifacts from a remote cache. This distributed cache sharing — where developer A's locally built result is reused by developer B and CI — governs the productivity of a large-scale monorepo.

3. Types and Representative Tools

The build systems that sustain a monorepo divide broadly, by design philosophy, into graph-based task runners and strict hermetic build systems. The former is a lightweight approach that layers impact analysis and caching atop existing ecosystems (npm, Gradle, etc.), while the latter is a heavyweight approach that fully controls build inputs and outputs to guarantee reproducibility and even remote execution.

Dimension Graph-based runner Hermetic build system
Representative tools Nx, Turborepo, Lerna Bazel, Buck2, Pants
Philosophy Orchestration atop existing package managers Full control of inputs/outputs, reproducible builds
Cache Per-task local/remote cache Per-action content-addressed cache + remote execution
Adoption difficulty Low (easy incremental adoption) High (full re-description via BUILD files)
Suitable scale Small–large Ultra-large, multi-language, long-term maintenance

The point to note here is that tool choice is not a question of "which is better" but of fit to organizational scale and the required level of reproducibility. If an organization of a few hundred people centered on web work adopts Bazel wholesale, the cost of writing and maintaining BUILD files can overwhelm productivity; conversely, if an organization of thousands across many languages uses only a graph runner, it hits the limits of build reproducibility and remote execution.

3.1 Hermetic Builds and Reproducibility

A hermetic build isolates a build task so that it can see only its declared inputs (source, dependencies, toolchain versions) and cannot depend on non-declared inputs such as network or system state. Thanks to this, "same input → always same output" holds, maximizing cache hit rates and allowing builds to be distributed across hundreds of remote workers (remote execution). The secret by which Google's internal build system Blaze (whose open-source edition is Bazel) handles a single repository of billions of lines is precisely this combination of hermeticity with remote caching and execution.

On the other hand, hermeticity requires all dependencies to be declared explicitly, so the initial conversion cost is large. Because every system library or global tool that was previously referenced implicitly must be pulled into BUILD rules, hermeticizing a legacy codebase all at once is practically impossible, and incremental migration is required.

4. Comparison with Polyrepo — Why the Differences Arise

The opposition between monorepo and polyrepo is not "into how many repositories to split" but the choice of whether to pull the coupling point forward to source or defer it to binary. From this fundamental difference, all the practical differences below derive.

Perspective Monorepo Polyrepo
Dependency coupling Source (latest HEAD) Binary (version pin)
Code sharing Direct reference, instant reflection Requires package publish and version bump
Atomic change Global change in one commit Requires coordination across repositories
Access control Hard to granularize (needs separate tooling) Clear per repository
Build/CI Explodes without dedicated tooling Independent per repository, simple
Team autonomy Enforces common rules, autonomy↓ Freedom of per-team tech choice↑

The reason atomic change is possible is the source dependency. In a polyrepo, changing a common API requires multi-stage coordination — "publish the library → each consumer bumps the version → each redeploys" — and in this process old and new versions coexist, creating a compatibility burden. A monorepo replaces this coordination with the compiler and tests and finishes it at once.

The reason access control is hard is that one repository is one permission boundary. In organizations such as finance and defense that must strictly separate source access by team and clearance, a polyrepo with clear per-repository permissions is advantageous for regulatory compliance. Doing granular access control in a monorepo requires additional mechanisms such as sparse checkout and subtree permissions, raising operational complexity.

As a practical implication, many organizations choose a hybrid. They keep monorepos per product line or domain (e.g., a frontend monorepo, a data platform monorepo) while separating into distinct repositories the areas that are regulated, externally published, or have an entirely different lifecycle. That is, "one giant repository" is not the goal; integration within a highly cohesive boundary is the practical orientation.

5. Deep Dive — Scaling Techniques and Cases for Ultra-Large Monorepos

The greatest technical challenge of a monorepo is Git's own scaling limits. Git is a distributed model that replicates the entire history and working tree locally, so once files reach millions to tens of millions, git status, clone, and checkout become unmanageable. To solve this, the following techniques have developed.

First, partial clone and sparse checkout. git clone --filter=blob:none lazily loads blobs, and only the directories needed for the work are expanded into the working tree to reduce local burden. When Microsoft moved the Windows source (about 300GB, 3.5 million+ files) to Git, it used VFS for Git (later Scalar) to fetch files via a virtual filesystem on demand, enabling work without a full clone.

Second, virtual filesystems and file monitors. In its Mercurial-based monorepo, Meta combined Watchman (file-change monitoring), EdenFS (an on-demand loading virtual filesystem), and the build tool Buck2 to keep status queries near constant time even in a repository of tens of millions of files. Google does not use Git at all, operating a single repository where billions of lines and some twenty thousand developers work concurrently, via the centralized VCS Piper, the virtual filesystem CitC, and the build system Blaze (as disclosed in 2016, about 86TB with tens of thousands of commits per day).

Third, remote cache and remote execution. If the content-addressed cache described earlier is shared across the whole organization, even a new developer's first build fetches most artifacts from the cache, cutting a build that would take tens of minutes down to a few minutes. As a measured case, organizations that adopted Nx or Turborepo report CI time reductions of 50–90% on remote cache hits (because the variance is large depending on change scope and hit rate, this should be understood as a tendency rather than asserted).

The common lesson of these cases is clear — a monorepo is not a strategy but an investment. It stands only when a dedicated build system, a virtual filesystem, remote cache infrastructure, and ownership/visibility governance are in place together; merely merging repositories without this infrastructure instead causes productivity to plummet.

6. Considerations and Implications (Professional Engineer's Perspective)

  • Adoption strategy (incremental): Rather than aiming for an enterprise-wide single repository from the start, first bundle highly cohesive domains that change together into one monorepo, introduce impact analysis and remote cache with a graph-based runner (Nx, Turborepo), and as scale grows, expand to a hermetic build and a virtual filesystem — this staged maturity roadmap is safe. "How to draw the boundary" comes before "merging."
  • Trade-off (visibility vs. autonomy/performance): A monorepo grants the values of global visibility, atomic change, and consistent tooling, but it constrains per-team tech choice and incurs Git/CI scaling costs. For a small organization lacking the capacity to invest in dedicated tools, or a product with a fully independent lifecycle, a polyrepo may be more reasonable. The right answer varies with organizational scale, coupling, and regulatory requirements.
  • Security and governance: Because a single repository widens the permission boundary, you must control the risk of tight coupling by enforcing CODEOWNERS-based reviews, per-directory visibility rules, limiting access scope via sparse checkout, and combining supply-chain security (SBOM, commit signing) into CI. Since the repository itself becomes a top attack target and a single point of failure, backup, access control, and protected branches are essential.
  • Organization and culture: A monorepo demands a shift in collaboration culture — "everyone is jointly responsible for one codebase." Common lint, test, and version policies must be established by company-wide consensus, and the effect is maximized when combined with trunk-based development and large-scale automated refactoring (codemod) culture.
  • Outlook and linkage: The monorepo is settling in as the foundation of platform engineering, DevSecOps, and CI/CD, and remote execution and distributed caching are being commercialized as cloud build services. Furthermore, in that an AI code assistant can reference the context of the entire codebase from one repository, its value as a single source of organizational knowledge is expected to grow further.

References


In one line: A monorepo is a strategy that consolidates multiple projects in a single repository via source dependency to gain atomic change, global visibility, and code sharing; it scales only when backed by dedicated infrastructure such as impact-based selective builds, remote cache, and virtual filesystems, and its trade-offs of access control, team autonomy, and Git scaling costs must be designed to fit organizational scale and coupling.