🚀 Welcome to MDriven Learn –  MDriven is now on Discord!  Don’t miss the latest Release Notes.
Scaling MDrivenServer for multiple reasons
This page was created by Alexandra on 2018-11-28. Last edited by Wikiadmin on 2026-07-29.

You can use MDrivenServer distribution to place read-serving MDrivenServer Slave nodes near users or add read capacity while keeping one Master node responsible for all writes.

When to use MDrivenServer distribution

Use this architecture when your application has substantially more reads than writes and you need either of the following:

  • Lower latency for distant users. A Slave in another geographic region maintains a local copy of the data, acting as a large local cache for reads.
  • More read capacity. Multiple Slaves can serve users while one Master remains the authority for changes.

This is different from load balancing a single active MDrivenServer with hot standbys. For active/passive fail-over, load-balancer health checks, and preventing standby nodes from running periodic work, see Documentation:Additional considerations with load balancing Turnkey and MDrivenServer. For deployment infrastructure such as web servers and load balancing, see Documentation:Deployment.

Architecture

A distribution group has one Master and one or more Slaves. The Master is the only node that accepts and commits data writes. Each Slave has its own database and keeps it synchronized with the Master through committed commit-blocks. A commit-block is the persisted block of changes produced by a committed data update.

Component Responsibility
Master MDrivenServer Accepts all writes, commits business-data changes, records commit-block information in the same database transaction, and supplies models and committed updates to Slaves.
Slave MDrivenServer Serves from its local database, forwards user saves to the Master, polls the Master for committed updates, evolves its database when the Master model changes, and applies received commit-blocks locally.
Commit-block table An additional database table required on all participating nodes. It persists commit-block information together with the business-data transaction.

Example: users in two regions

Assume the Master is located in Region A and a Slave is located in Region B.

  1. A user in Region B reads data through the Slave. The Slave reads its local database.
  2. The user saves a change through the Slave.
  3. The Slave routes that save request to the Master; it does not commit the user change itself.
  4. The Master commits the change and its commit-block information in one transaction.
  5. The Slave requests available updates from the Master, receives the newly committed commit-block, and merges it into its own database.

The Slave continuously polls the Master for changes. With connectivity available, it converges on the Master's content after committed updates have been transferred and applied.

Consistency and write behavior

Distribution involves copies of data at separate locations. MDrivenServer addresses this by making the Master the single writer.

  • A Slave routes all user saves to the Master.
  • Only after the Master accepts a save can the resulting committed update be distributed.
  • A Slave obtains updates by polling the Master. The Slave always initiates this communication; the Master accepts save requests and returns committed updates.
  • A Slave can temporarily contain older data while it waits for a newly committed update. Given time and a working network path, it receives and applies the Master's committed changes.

When a Slave cannot reach the Master

If a Slave loses connectivity to the Master:

  • Users connected through that Slave cannot save data because saves must be accepted by the Master.
  • The Slave cannot receive newly committed updates, so its local data is not refreshed during the outage.
  • When connectivity returns, the Slave can again route saves and resume obtaining updates.

Plan routing and operational monitoring around this behavior. A Slave is not an independent write node and does not replace the Master during a network partition.

Database durability and provisioning

The commit-block table is part of each participating database. On the Master, commit-block information is written in the same transaction that updates the business data. Therefore, a restored Master database retains commit-block information consistent with the restored business data.

Because the Master and Slave use the same additional table structure, you can use a backup of the Master database as the starting database for a new Slave. This gives the new Slave a consistent initial data set before it resumes receiving later commit-blocks from the Master.

Use the normal MDrivenServer database maintenance guidance for database operations; see HowTos:Configure and Maintain MDriven Server and Training:MDrivenServer Summarized.

Model changes and automatic evolution

A commit-block is created in the context of a particular model version. A Slave must not apply a commit-block against an incompatible model.

MDrivenServer manages this requirement by recording a checksum of the current model with each commit-block on the Master. When a Slave receives a commit-block, it compares that checksum with the model it is currently using.

  1. Deploy the changed model to the Master.
  2. The Master creates later commit-blocks with the checksum of that model.
  3. A Slave receives a commit-block and detects that its local model checksum differs.
  4. The Slave requests the required model from the Master by checksum.
  5. The Master returns the required model.
  6. The Slave automatically evolves its database to that model.
  7. After the evolution succeeds, the Slave applies the commit-block.

This lets you deploy the model to the Master and have Slaves retrieve the required model and evolve before applying incompatible updates. Review model evolution and deployment procedures before changing a production model; start with Documentation:Server and Documentation:Deployment.

Design checklist

Before adopting distribution, confirm the following:

  • Your workload benefits from local reads or read scaling; the design assumes reads are more frequent than writes.
  • You can operate one Master as the sole write authority.
  • Each Slave has reliable connectivity to the Master for save forwarding and continuous polling.
  • You accept that a disconnected Slave cannot save and cannot receive updates.
  • All participating databases include the commit-block table.
  • You have a backup and restore process that preserves the Master database and its commit-block history.
  • You understand the effect of server-side actions in a multi-node environment. Do not treat distribution as permission for multiple nodes to perform work intended to have a single performer.

Implementing equivalent behavior in an MDriven Framework server

MDrivenServer provides the distribution behavior described on this page. If you build your own server with the MDriven Framework, you must implement the equivalent responsibilities yourself.

The first requirement is to capture the commit-block submitted by Eco.Persistence.SyncHandler and persist it in the same database transaction that applies the business-data update. The following fragment shows the event-registration pattern from the synchronization handler:

var res = new Eco.Persistence.SyncHandler();
res.OnSubmittedCommitBlock += Res_OnSubmittedCommitBlock;

In the event handler, obtain the update parameters and persist the commit-block for every database participating in the update. The essential requirement is transactional: the commit-block record and the business-data update must either both commit or both roll back.

var updP = e.OperationsParams as TUpdateParameters;
if (updP != null)
{
    foreach (IDatabase db in updP.Databases)
    {
        var query = db.GetExecQuery();
        try
        {
            query.AssignSqlText(
                "insert into MDrivenServerSynk " +
                "(Time, CommitBlock, modelid, slaveMergeTime) " +
                "values (:inserttime, :CommitBlock, :modelid, :slaveMergeTime)");
        }
        finally
        {
            // Dispose or release the query according to the database API in use.
        }
    }
}

Capturing commit-blocks is only one part of the design. Your implementation must also:

  1. Copy committed commit-blocks from the Master to each Slave and apply them to the Slave database.
  2. Intercept update calls made to a Slave and reroute them to the Master.
  3. Detect a Master model change at the Slave, obtain the required model, evolve the Slave database, and only then apply commit-blocks created for that model.

Do not use the fragment as a complete implementation. It omits the event-handler context, parameter binding, polling, error handling, authentication, update forwarding, model transfer, and evolution coordination required for a working distributed server.

See also