RonDB training · module 20 of 25 · 18 min
Native APIs
The C++ NDB API that everything else is built on, its Event API for change streams, and ClusterJ for Java.
The API map · one road into the data nodes
Every way into RonDB ends in the C++ NDB API
Learn the NDB API and you understand what every other interface does underneath. Modules 21 and 22 cover REST and RonSQL.
docs: rondb_ndb_api.md · rondb_clusterj.md · notes: apis.md (rdrs2 hosts REST, RonSQL, Rondis)
NDB API · four levels of objects
NDB API objects: a cluster connection, Ndb objects, transactions, operations
An Ndb object is used by one thread at a time; give each thread its own. init() sets how many transactions it may run at once: 4 by default, at most 1024.
docs: rondb_ndb_api.md (Concurrency in the NDB API, Ndb cluster connection) · code: include/ndbapi/Ndb.hpp (init(int maxNoOfTransactions = 4), maximum 1024)
Connecting · from the shipped examples
Connecting: reach the management server, wait for data nodes, init an Ndb
ndb_init(); Ndb_cluster_connection connection(connectstring); if (connection.connect() != 0) /* no mgmd */; if (connection.wait_until_ready(30, 0) != 0) /* no data node within 30 s */; Ndb ndb(&connection, "ndbapi_examples"); if (ndb.init() != 0) /* error */;
- The connect string lists management servers; the configuration comes from there.
- Give the connection a fixed node id where you can: logs and tools then name it.
- One connection scales to many CPUs; very large programs open a few.
code: storage/ndb/ndbapi-examples/ndbapi_basic/ndbapi_basic_connect.cpp, ndbapi_basic_insert.cpp (error handling shortened) · docs: rondb_ndb_api.md
Inside an API node · threads you do not create
API threads: each cluster connection runs receive, send and manager threads
Receive
The heart: reads replies and wakes your threads. A user thread does it itself when few are active; a dedicated thread takes over above 8 (ndb_recv_thread_activation_threshold), at nice −20.
Send
Sends only when the socket cannot keep up; usually user threads send directly.
Manager · wakeup
The cluster manager keeps heartbeats with the data nodes; a wakeup thread can wake hundreds of waiting threads at once.
Only one thread at a time can receive for a connection: the limit that makes very busy programs open more connections.
docs: rondb_api_arch.md (NDB API send threads, receive threads, cluster manager threads, wakeup thread) · rondb_ndb_api.md
Transactions · define, then execute
Transactions: define operations locally, then execute them in one round trip
startTransaction(table, key): the key is a hint that starts the transaction on the node holding that row (module 10).- Define operations: nothing is sent yet.
execute(NoCommit)sends them and keeps the transaction open;execute(Commit)sends and commits;Rollbackaborts.closeTransaction()always, to free its resources.
AbortOnError aborts the transaction on any failed operation; AO_IgnoreError lets others go on, for example a read of a row that may not exist.
code: include/ndbapi/NdbTransaction.hpp (ExecType NoCommit, Commit, Rollback) · NdbOperation.hpp (AbortOnError, AO_IgnoreError) · docs: rondb_ndb_api.md
Key operations · an insert, from the examples
An insert: get the table, start a transaction, define, execute, close
const NdbDictionary::Table *table = ndb.getDictionary()->getTable("basic"); NdbTransaction *trans = ndb.startTransaction(table); NdbOperation *op = trans->getNdbOperation(table); op->insertTuple(); op->equal("ATTR1", key); // primary key op->setValue("ATTR2", value); if (trans->execute(NdbTransaction::Commit) != 0) /* trans->getNdbError() */; ndb.closeTransaction(trans);
| Operation | Is |
|---|---|
| insertTuple | insert; fails if it exists |
| updateTuple | update; fails if missing |
| writeTuple | insert or update |
| deleteTuple | delete |
| readTuple(mode) | read with a lock mode |
code: ndbapi-examples/ndbapi_basic/ndbapi_basic_insert.cpp (do_insert; error handling shortened) · include/ndbapi/NdbOperation.hpp
Batching · the biggest win
Batching: many operations per execute, one round trip, run in parallel
Executing after each operation pays a network round trip every time.
Define many, execute once: TC sends them out in parallel. Batching gives about 10× the throughput of one operation per round trip.
docs: rondb_ndb_api.md · notes: apis.md (flexAsynch; batching about 10×)
PART 1
Scans, NdbRecord, async
Reading many rows, describing rows once, many transactions per thread.
Scans · with NdbRecord, from the examples
Scans with NdbRecord: describe the row once, scan, loop over nextResult
NdbDictionary::RecordSpecification spec[] = {
{table->getColumn("ATTR1"), offsetof(BasicRow, attr1), 0, 0, 0},
{table->getColumn("ATTR2"), offsetof(BasicRow, attr2), 0, 0, 0}};
NdbRecord *record = dict->createRecord(table, spec, 2, sizeof(spec[0]));
NdbTransaction *trans = ndb.startTransaction(table);
NdbScanOperation *scan = trans->scanTable(record);
trans->execute(NdbTransaction::NoCommit); // start the scan
const BasicRow *row;
while (scan->nextResult((const char **)&row, true, false) == 0)
use(row->attr1, row->attr2); // batches arrive as you go
scan->close();
ndb.closeTransaction(trans);
NdbRecord maps columns to a C struct once: rows land in your memory without per-column calls. scanIndex scans an ordered index with bounds.
code: ndbapi-examples/ndbapi_basic/ndbapi_basic_read.cpp (std::size replaced by 2, errors shortened, use() is a placeholder)
NdbScanFilter · conditions run in the data nodes
A scan filter becomes an interpreted program on every row
NdbScanFilter filter(scan);
filter.begin(NdbScanFilter::AND);
filter.cmp(NdbScanFilter::COND_GE,
col_price, &min_price, 4);
filter.cmp(NdbScanFilter::COND_EQ,
col_region, ®ion, 4);
filter.end();
- Nested
begin(AND | OR)…end()build any boolean condition. - Compiled to the interpreter of module 10, evaluated in each ldm thread.
- Only matching rows cross the network.
- For full programs:
NdbInterpretedCode; for aggregates:NdbAggregator.
code: include/ndbapi/NdbScanFilter.hpp (begin, cmp, end) · NdbInterpretedCode.hpp · ndbapi-examples/ndbapi_agg (NdbAggregator) · the column ids and values are illustrations
Asynchronous API · many transactions per thread
Asynchronous API: one thread keeps hundreds of transactions in flight
Synchronous
execute()= prepare + send + wait.- One transaction per thread at a time.
- Simple; throughput needs many threads.
Asynchronous
executeAsynchPrepare(…, callback)on many transactions.sendPollNdb()sends them all and calls back as replies arrive.- The style of flexAsynch, RonDB's own benchmark.
code: ndbapi-examples/ndbapi_async (executeAsynchPrepare, sendPollNdb) · docs: rondb_ndb_api.md
PART 2
The Event API
Every committed change, as a stream.
Events · from the examples
Events: subscribe to a table and receive every insert, update and delete
NdbEventOperation *op =
ndb->createEventOperation(eventName);
NdbRecAttr *after = op->getValue("COL0");
NdbRecAttr *before = op->getPreValue("COL0");
op->execute(); // changes start flowing
while (running) {
if (ndb->pollEvents(1000) > 0) // wait ≤ 1 s
while ((op = ndb->nextEvent()))
switch (op->getEventType()) {
case NdbDictionary::Event::TE_INSERT: …
case NdbDictionary::Event::TE_UPDATE: …
case NdbDictionary::Event::TE_DELETE: …
}
}
- An event is created once per table, for chosen columns and change types.
- getValue gives the new value, getPreValue the old one.
- SUMA in the data nodes sends them (module 06).
code: ndbapi-examples/ndbapi_event/ndbapi_event.cpp (lines picked and shortened; column name simplified)
Events · delivered by epoch
Events by epoch: changes arrive complete and ordered, every 100 ms
By epoch
Events come grouped per epoch, the 100 ms GCI of module 14: whole transactions, in commit order of epochs.
Fault tolerant
Replicas of a fragment buffer the events until the subscriber has them; a node failure loses none.
Who uses it
The MySQL binlog injector for global replication (module 24); rdrs2 watching schema for TTL purging.
docs: rondb_checkpoints_global_redo.md (micro GCPs for replication) · notes: apis.md (events per GCI, node failure buffering, users)
PART 3
ClusterJ
Java objects on RonDB, through the NDB API.
ClusterJ · interfaces mapped to tables
Annotate an interface; ClusterJ reads and writes rows through it
@PersistenceCapable(table="customer") public interface Customer { @PrimaryKey @Column(name="customer_id") int getId(); void setId(int id); String getName(); void setName(String name); } Session s = sessionFactory.getSession(); Customer c = s.newInstance(Customer.class); c.setId(100); c.setName("Mikael"); s.persist(c); // autocommit insert Customer r = s.find(Customer.class, 100);
- SessionFactory = cluster connections; Session = an Ndb object, one thread at a time.
- Settings in clusterj.properties: connectstring, database, pool size.
- makePersistentAll batches; QueryBuilder scans.
- HopsFS runs its metadata on ClusterJ.
docs: rondb_clusterj.md (Customer interface, Session, persist, makePersistentAll, find, clusterj.properties)
Recap · module 20
What to take away
- Connection → Ndb (one thread at a time) → transaction → operations.
- Define many operations, execute once: batching is the biggest win.
- NdbRecord for rows, NdbScanFilter for pushed conditions, async for many transactions per thread.
- The Event API streams every change, grouped by epoch.
- ClusterJ maps Java interfaces to tables on the same API.
RonDB training · 25 modules · you are at 20