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
TCKEYREQto block DBTC runsDbtc::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
| Block | No. | What it owns | Thread |
|---|---|---|---|
| DBTC | 245 | Transaction coordinator: transactions, routing, two-phase commit | tc |
| DBSPJ | 264 | Joins pushed down into the data nodes | tc |
| DBLQH | 247 | Local query handler: operations on its fragments, REDO log | ldm |
| DBACC | 248 | Hash index and row locks | ldm |
| DBTUP | 249 | Tuple manager: rows, the interpreter, triggers | ldm |
| DBTUX | 258 | Ordered indexes (T-trees) | ldm |
| PGMAN · BACKUP · RESTORE | 261 · 244 · 262 | Disk page cache · checkpoints and backups · checkpoint restore | ldm |
| DBQLQH … QRESTORE | 267–272 | Query-worker copies of the ldm blocks, for reads | every |
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
| Block | No. | What it owns | Thread |
|---|---|---|---|
| DBDICT | 250 | Schema: tables, indexes, schema transactions | main |
| DBDIH | 246 | Where every fragment lives; global and local checkpoints | main |
| QMGR · NDBCNTR | 252 · 251 | Membership, heartbeats, arbitration · node start and restart | main |
| NDBFS · CMVMI | 253 · 254 | File system · configuration and cluster log | main |
| TRIX · DBUTIL | 255 · 256 | Index builds and reorganisation · internal utility transactions | main |
| SUMA | 257 | Change events for subscribers and replication | rep |
| TSMAN · LGMAN · DBINFO | 259 · 260 · 263 | Tablespaces · UNDO logs · ndbinfo tables | rep |
| THRMAN · TRPMAN | 265 · 266 | One per thread: load statistics · the network connections | every · 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, 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
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
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
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
- 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
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
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
- Execute signals from the job buffers, up to 75 from each.
- Flush signals for other threads in this node: after 2 signals in a recv thread, 5 in a tc thread, 20 in others.
- Send: hand send buffers to the send threads, or send directly when not overloaded.
- Timers: signals sent with a delay of some milliseconds become due.
- 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
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.
RonDB training · 25 modules · you are at 06