RonDB training · module 06 of 25 · 14 min

Blocks, signals and the virtual machine

How a data node is built: modules that own their data, messages between them, and a scheduler that keeps every message short.

Architecture · from the AXE switch (module 01)

A data node is a set of blocks that talk only through signals

Block

A C++ class with its own code and its own data. Other blocks never touch that data.

Signal

A message with a destination address, a signal number and data. The only way into a block.

Thread

Runs a set of blocks and a scheduler that executes their signals, one at a time.

A bug can only reach another block through the data it sends, so errors stay isolated and findable. The price: reading the code means following signal flows.

docs: rondb_dn_arch.md (Block and Signal Architecture, Blocks)

In the code · one method per signal

A block receives a signal in a method named after it

// storage/ndb/src/kernel/blocks/dbtc/DbtcMain.cpp
void Dbtc::execTCKEYREQ(Signal *signal) {
  // read the request from signal->theData,
  // find the partition and its nodes,
  // then send LQHKEYREQ to the LDM
  // that owns the row
}

// storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp
void Dblqh::execLQHKEYREQ(Signal *signal) { ... }
  • Signal TCKEYREQ to block DBTC runs Dbtc::execTCKEYREQ.
  • Requests usually end in REQ, answers in CONF (success) or REF (refused).
  • A block sends with sendSignal: destination, signal number, length, priority.

code: dbtc/DbtcMain.cpp (Dbtc::execTCKEYREQ) · dblqh/DblqhMain.cpp (Dblqh::execLQHKEYREQ) · the method bodies are summaries · docs: rondb_dn_arch.md (Scheduling rules)

The blocks · data and transactions

Blocks in the ldm and tc threads do the work of every query

BlockNo.What it ownsThread
DBTC245Transaction coordinator: transactions, routing, two-phase committc
DBSPJ264Joins pushed down into the data nodestc
DBLQH247Local query handler: operations on its fragments, REDO logldm
DBACC248Hash index and row locksldm
DBTUP249Tuple manager: rows, the interpreter, triggersldm
DBTUX258Ordered indexes (T-trees)ldm
PGMAN · BACKUP · RESTORE261 · 244 · 262Disk page cache · checkpoints and backups · checkpoint restoreldm
DBQLQH … QRESTORE267–272Query-worker copies of the ldm blocks, for readsevery

code: storage/ndb/include/kernel/BlockNumbers.h (0xF4 BACKUP … 0x111 V_QUERY) · src/kernel/vm/mt.cpp (add_thr_map, mt_add_thr_map) · module 11 covers the threads

The blocks · running the cluster

Blocks in the main, rep and recv threads run the cluster itself

BlockNo.What it ownsThread
DBDICT250Schema: tables, indexes, schema transactionsmain
DBDIH246Where every fragment lives; global and local checkpointsmain
QMGR · NDBCNTR252 · 251Membership, heartbeats, arbitration · node start and restartmain
NDBFS · CMVMI253 · 254File system · configuration and cluster logmain
TRIX · DBUTIL255 · 256Index builds and reorganisation · internal utility transactionsmain
SUMA257Change events for subscribers and replicationrep
TSMAN · LGMAN · DBINFO259 · 260 · 263Tablespaces · UNDO logs · ndbinfo tablesrep
THRMAN · TRPMAN265 · 266One per thread: load statistics · the network connectionsevery · recv

code: BlockNumbers.h · mt.cpp (add_thr_map: thr_GLOBAL is the main thread, thr_LOCAL the rep thread) · 30 block numbers, 244 to 273

Multi-threaded blocks · one instance per thread, one proxy

A proxy block fans a signal out to every instance

DBLQH proxyinstance 0 · rep thread DBLQH 1 · ldm thread 1 DBLQH 2 · ldm thread 2 DBLQH 3 · ldm thread 3 DBLQH 4 · ldm thread 4 One request, e.g. from DBDICT:the proxy copies it to every instance The proxy waits for all repliesand answers once

DBLQH, DBACC, DBTUP, DBTUX, DBTC and others run as one instance per thread. Each instance owns its part of the data.

Other blocks address the proxy (instance 0) when a request concerns all instances, such as a new table.

The sender sees one block and one reply, however many threads the node has.

code: mt.cpp ("For multithreaded blocks we will assign the Proxy blocks below to their thread") · src/kernel/blocks/LocalProxy.hpp

PART 1

Signals

Addresses, sizes and the format on the wire.

Block reference · 32 bits, like an IP address

Block references: a node, a block and an instance in one 32-bit word

On the wire instance7 bits block number9 bits · DBTC = 245 node id16 bits · up to 8191 used; data nodes 1 to 144 bit 0 Inside the data node instance10 bits · up to 1024 block6 bits · 64 blocks node idunchanged

The low 16 bits name the node. The high 16 bits name the block and which instance of it.

Block numbers run from 244 to 273, so RonDB maps them to 0–63 and moves 3 bits to the instance: 1024 instances, still compatible below 128.

code: include/kernel/RefConvert.hpp (NDBMT_REAL_BLOCK_BITS 6, FIRST_BLOCK 244) · ndb_limits.h (MAX_NODES_ID 8191, MAX_DATA_NODE_ID 144)

Routing · decided by the address alone

Signal routing: another node goes to a send buffer, this node to a job buffer

sendSignalfrom a block node id?in the reference send bufferanother node send threador a block thread helping job bufferof the destination thread block threadblock + instance → thread

The sender does not know or care where the receiver runs: the reference decides.

To another node, the signal is copied into a send buffer; the block continues at once and a send thread writes the socket later.

Inside the node, block and instance give the thread, and the signal is queued in its job buffer.

docs: rondb_dn_arch.md (Block references, Send handling in Data Node)

Signal sizes · three kinds

Short, long and fragmented signals

25

words at most in a short signal: 1 to 25 unsigned 32-bit integers

32 KB

a long signal: short data plus up to 3 sections, stored in 60-word segments

more

a fragmented signal: many long signals, put together before it runs. Mostly metadata

Inside a node a signal takes at most 34 words in a job buffer: a 9-word header and 25 data words. Sections travel by reference.

code: src/kernel/vm/mt.cpp (MAX_SIGNAL_SIZE 34) · VMSignal.hpp · ndb_limits.h (60-word segments) · TransporterDefinitions.hpp (32768-byte message) · docs: rondb_dn_arch.md (Signal data structure)

On the network · after the connection is set up

A signal on the wire: header, data, section lengths, sections

header3 words signal idoptional data1 to 25 words lengths1 word per section sections0 to 3 checksumoptional Word 1 · total size, data size (max 25), priority, fragment bits, flags for signal id and checksum, endian bits Word 2 · signal number (20 bits), trace number, number of sections Word 3 · sender block and receiver block, 16 bits each; the node ids come from the connection

Every signal carries its own length, so the receiver finds where the next one starts. Signal id and checksum are off by default.

Three header words describe it. Packed signals put several small ones (COMMIT, COMPLETE, LQHKEYCONF and a few more) in one, saving the header and a scheduling round.

docs: rondb_dn_arch.md (RonDB signal header definition, Send handling) · code: include/kernel/signaldata/PackedSignal.hpp

Connection setup · before any signal

Connection setup: two nodes greet each other in five lines of text

API node 2 connects to data node 3
client: ndbd TLS disabled
client: (empty line)
server: Cleartext ok
client: 2 1 3 0
server: 3 1
-- full duplex signals from here on
  • The client is the API node, or the data node with the higher node id.
  • The first line says whether the client uses TLS. With TLS the server answers TLS ok and the rest runs encrypted.
  • Then each side gives its node id and the transporter type, 1 for TCP/IP. Any surprise closes the socket.

code: src/common/util/SocketAuthenticator.cpp (SocketAuthTls: "ndbd TLS disabled", "Cleartext ok", "TLS ok") · transporter/TCP_Transporter.cpp (use_tls_client_auth) · transporter/Transporter.cpp (hello "%d %d %d %d")

PART 2

The scheduler

No time slicing: every signal runs to the end, and is short.

Scheduling · the block's own discipline

A signal runs to the end, so it must be short: 1–2 microseconds, never over 10

One ldm thread, time → scan · some rows key key commit scan · next rows key scan · next rows key key scan · next rows the scan sends itself a signal and waits its turn hundreds of queued signals still finish within a few hundred microseconds

No time slicing: the scheduler never interrupts a block. Coding rules keep each signal short.

Long work is cut into steps. A scan handles a few rows, then continues with a new signal at the back of the queue, so key lookups never wait long.

docs: rondb_dn_arch.md (Scheduling rules) · module 10 covers how scans split their work

Signals · priority, and starting new work

Signal priorities: two levels, and parallelism that costs nanoseconds

Priority A and B

  • B is normal; A runs first.
  • Only scheduling inside a data node changes: network and API nodes treat both alike.
  • Checkpoints use A when needed, to keep progressing under heavy traffic.

A new thread of execution

  • Sending two signals instead of one starts parallel work.
  • A scan of a 4-partition table sends 4 signals at once.
  • Rule: a signal starts at most 4 new signals in its own thread. MaxNoOfConcurrentScans (256, up to 500) bounds scans.

docs: rondb_dn_arch.md (Asynchronous signals, Parallelising signal execution) · code: ConfigInfo.cpp (MaxNoOfConcurrentScans default 256, 2–500)

Job buffers · one writer, one reader

Job buffers: every pair of threads has its own queue, so no locks are needed

recv thread tc thread 1 tc thread 2 main thread job buffer · from recv job buffer · from tc 1 job buffer · from tc 2 job buffer · from main ldm threadreads each queue in turn, up to 75 signals

One writer and one reader per queue: memory barriers instead of mutexes. Queues are 32 KB pages. Above 32 block threads, writers share 32 queues behind a mutex each.

code: mt.cpp (NUM_JOB_BUFFERS_PER_THREAD 32, MAX_SIGNALS_PER_JB 75, thr_job_buffer 8188 words) · docs: rondb_dn_arch.md (Receive handling)

The scheduler loop · one per block thread

The scheduler loop: execute, flush, send, check timers, sleep or spin

  1. Execute signals from the job buffers, up to 75 from each.
  2. Flush signals for other threads in this node: after 2 signals in a recv thread, 5 in a tc thread, 20 in others.
  3. Send: hand send buffers to the send threads, or send directly when not overloaded.
  4. Timers: signals sent with a delay of some milliseconds become due.
  5. Sleep when idle, after spinning a while if spinning is likely to pay off.

SchedulerResponsiveness tunes how many signals run between flushes and sends: lower latency, or more throughput.

code: mt.cpp (MAX_SIGNALS_BEFORE_FLUSH_RECEIVER 2, _TC 5, _OTHER 20) · docs: rondb_dn_arch.md (Send handling) · module 12 covers spinning

The hot path · signals between threads, calls inside one

Inside an ldm thread, a key operation is three function calls

tc thread DBTCtransaction LQHKEYREQ · a signal ldm thread DBLQHthe operation call DBACCfind the row, lock it call DBTUPread or write it

Between threads, everything is a signal: DBTC asks DBLQH with LQHKEYREQ.

Inside the ldm thread, DBLQH calls DBACC and DBTUP as plain C++ functions: the most optimised path in RonDB, still keeping each block's data its own.

code: dblqh/DblqhMain.cpp (c_acc->execACCKEYREQ, c_tup->execTUPKEYREQ) · docs: rondb_dn_arch.md (Synchronous signals)

Sending · who writes the sockets

Send threads send, and block threads help when they have time

Block thread

Copies a signal into a send buffer and carries on. Every scheduler round makes its buffers available for sending.

Send thread

Takes a node from its list and writes to that node's socket. Each connection has one responsible send thread.

Assisting

Below about 75% load a thread helps before it sleeps; above, it leaves all sending to the send threads. tc threads help the most.

recv threads wait in epoll and wake as soon as data arrives. They do not help the send threads.

docs: rondb_dn_arch.md (Send handling in Data Node, Receive handling in Data Node) · code: mt.cpp (do_send, assist_send_thread) · thrman.cpp (get_load_status)

Recap · module 06

What to take away

  • Blocks own their data; signals are the only way in. Bugs stay isolated.
  • A 32-bit reference names node, block and instance: 64 blocks, 1024 instances.
  • Short signals carry 25 words; long ones up to 32 KB in 3 sections.
  • Signals run to completion in 1–2 microseconds; long work re-queues itself.
  • Lock-free job buffers between threads; direct calls inside one.
  • Key lookups call LQH → ACC → TUP directly.
  • Range scans call LQH → TUX → TUP, and ACC when they lock rows.

Next · 07 · Data structure layout