How Tencent Docs Seamlessly Switches Between Monolith and Microservices

This article explains how Tencent Docs tackled the high runtime, deployment, and image‑distribution costs of a large microservice fleet by building a flexible architecture that can merge selected services into a few monolith binaries for private deployments, while retaining microservices for public scenarios, and shares the tooling, configuration, challenges, and performance gains achieved.

ITPUB
ITPUB
ITPUB
How Tencent Docs Seamlessly Switches Between Monolith and Microservices

Background

Software architecture has no universal silver bullet; a good design must be continuously iterated. Tencent Docs faced business pressures that required a flexible solution capable of switching between a monolithic deployment and a microservice‑based deployment.

Why Switch Between Monolith and Microservices?

Monolithic services are simple to develop and maintain, making them suitable for small‑to‑medium projects, but they suffer from scalability and deployment issues as the codebase grows. Microservices provide isolation, resilience, and independent scaling, which is ideal for large projects, yet they introduce higher operational complexity, deployment overhead, and resource consumption.

Challenges Driving the Switch

Running over a hundred microservices in a private‑cloud scenario caused excessive CPU and memory usage because each service instantiated its own tRPC‑Go runtime.

Maintaining separate tad description files for each service created a massive maintenance burden during version upgrades.

Packaging each microservice as a Docker image resulted in a total image size exceeding 10 GB, making distribution to private customers impractical.

To keep the advantages of microservices while eliminating these drawbacks, Tencent Docs designed a strategy that merges selected microservices into a small number of monolith services for private deployments, yet continues to run microservices on the public TKEx platform.

Tooling: the monolith Utility

The monolith tool reads a YAML‑style configuration that lists modules and the microservices to be merged. An excerpt of the configuration format is shown below:

modules:
  - name: monolith-module1
    merged_servers:
      - name: microserver1
        ...
      - name: microserver2
        ...
  - name: monolith-module2
    merged_servers:
      - name: microserver3
        ...
      - name: microserver4
        ...

During each merge request (MR), the tool automatically detects which services need to be merged and generates the corresponding monolith code.

Key Challenges and Solutions

3.1 Diverse Frameworks

Over five years, Tencent Docs experimented with several frameworks (tRPC‑Go, tRPC‑Cpp, SPP, LSF). To keep the merging process manageable, the team standardized on tRPC‑Go and only merged services that adhered to the standard tRPC‑Go conventions.

3.2 Divergent Configuration Structures

Microservices and monolith services originally used different configuration layouts. A unified configuration component was introduced, allowing both deployment models to share a common plugin.config schema. Example snippets illustrate the before‑and‑after structures:

# Microservice configuration
plugin:
  config:
    app:
      providers:
        serverA:
          app.yaml:
            foo: bar
            foz: baz
# Monolith configuration
plugin:
  config:
    app:
      providers:
        serverA:
          app.yaml:
            foo: bar
            foz: baz
        serverB:
          app.yaml:
            zoo: bar
            zoz: baz
        ...

Plugin configuration conflicts (e.g., different Redis targets) were also resolved by normalising the schema.

3.3 Global Variable Modifications

tRPC‑Go exposes global variables such as http.DefaultServerCodec and restful.Marshaller. Modifying these globals in a monolith can affect unrelated services. Two mitigation strategies were adopted:

Common plugin : Provide a unified CGI component that encapsulates the required behaviour without touching globals.

Module isolation : Isolate services that need to modify restful.Marshaller into a dedicated module, keeping them separate from services that do not.

3.4 Hidden Bugs When Merging

Some bugs only surface after merging. For example, the following registration code overwrites previously registered services in a monolith, leading to 404 responses:

func Register(s *server.Server) {
    someserverpb.RegisterSomeServerSomeService(s, newSomeServiceImpl())
}

The author proposes a “binary localisation” debugging method: repeatedly halve the set of merged services, test the behaviour, and narrow down the offending service.

3.5 Change Management

Automated checks run on every MR to verify that merged services still satisfy configuration and structural constraints. Future plans include integrating interface tests and end‑to‑end (e2e) tests to further guard against regressions.

Generated Monolith Template

// Code generated by backend/tools/monolith, DO NOT EDIT.
// Package service rpc entry layer.
package service

import (
    // merged services are imported here
    // e.g., "docx/backend/application/serviceA/service"
    "git.code.oa.com/trpc-go/trpc-go/server"
)

// Register registers pb service implementations.
func Register(s *server.Server) {
    // each merged service registers itself
    serviceA.Register(s)
    serviceB.Register(s)
    // ...
}

This template registers all standardized services into a single binary, dramatically simplifying deployment.

Performance Gains

Comparing a representative module before and after monolithisation yields the following improvements:

Binary size reduced from 264 MB to 68 MB (≈ 74 % reduction).

Memory usage dropped from 1 722 MB to 670 MB (≈ 61 % reduction).

CPU consumption fell from 6.43 cores to 4.99 cores (≈ 22 % reduction).

Average request latency changed marginally from 9.8 ms to 9.3 ms.

Extrapolating to the entire system, total image size is expected to shrink by 75 % and total memory consumption by 96 %.

Conclusion

By building a flexible architecture that can merge microservices into monolith binaries on demand, Tencent Docs achieved substantial resource savings while preserving the benefits of microservices for public scenarios. The approach demonstrates how careful standardisation, automated tooling, and systematic debugging can enable large‑scale service fleets to adapt to diverse deployment requirements.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendarchitectureConfigurationmonolithtRPC
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.