Modules · Context Transfer Engine
CTE core
The core module is the storage engine every other CTE module ends at. It owns the tag and blob namespace, registers the storage tiers as targets, places each blob by its score, and executes every put, get, delete, query and reorganize. This page is the reference for using and configuring it; the internals are on the design page.
- Module
- clio_cte_core
- Pool
- 512.0 (kCtePoolId)
- Source
- context-transfer-engine/core
- Python
- clio_cte_core_ext
What it does
The core stores blobs (named byte ranges) inside tags (named groups, roughly a bucket or a file) on storage targets (block-device pools: RAM, pinned host memory, HBM, a backing file, S3, GCS or a no-op device). Every blob has a score between 0.0 and 1.0. The score selects the tier at placement time and drives later reorganization: 1.0 means the fastest tier, 0.0 the slowest. A new blob without an explicit score lands at 1.0; a put that omits the score keeps the blob's current one.
Use the core directly when you want a tiered key-value style store with explicit control over placement. Use it through the cache, replication, compressor and indexer modules, or the filesystem, when you want those policies added on top. None of them bypass the core; see CTE core design for the data path, distribution and concurrency model.
Where it sits
- Pool. The core is composed at pool 512.0, the
constant
kCtePoolIdthat every CTE client and adapter resolves to by default. One container runs per node. - Below it. Each entry in its
storagelist becomes a bdev pool that the core creates and registers as a target. A target can instead bind to an already composed pool such as safe-bdev withexisting_pool_id. - Above it. The interposer modules speak the core's
own task interface and stack with
next_pool_id. The canonical chain in the default configuration is cache (563.0) to indexer (564.0) to optional compressor (562.0) to replication (561.0) to core (512.0). Each entry must be composed after the pool it forwards to. - CAE. The Context Assimilation Engine core sits at its own pool 400.0 and forwards tag creation, put, get and semantic search to the CTE core. The default configuration warns against placing CAE in front of the CTE at 512.0, because the two modules' method IDs collide.
Composing it
This entry is the CTE section of context-runtime/config/clio_default.yaml
with the comments removed. A RAM tier with capacity 0g takes
80% of system DRAM; a file tier grows lazily, so its capacity is a cap, not
an upfront allocation.
compose:
- mod_name: clio_cte_core
pool_name: cte_main
pool_query: local
pool_id: "512.0"
storage:
- path: "ram::cte_ram_tier1"
bdev_type: "ram"
capacity_limit: "0g"
score: 1.0
- path: "${CLIO_STORAGE_ROOT}/cte_disk_tier.dat"
bdev_type: "file"
capacity_limit: "10GB"
score: 0.2
persistence_level: "temporary"
performance:
metadata_log_path: "${CLIO_STORAGE_ROOT}/cte_metadata_log"
transaction_log_capacity: "32MB"
dpe:
dpe_type: "max_bw"
targets:
neighborhood: 1
default_target_timeout_ms: 30000
poll_period_ms: 5000To use a pool that is already composed as a tier, replace the
path, bdev_type and capacity_limit
keys with the pool's identifier. The pool must implement the bdev task
interface, and the core reads its free space from that pool's statistics.
This form is exercised by test/unit/test_cte_existing_pool.cc.
storage:
- existing_pool_id: "350.0"
existing_pool_module: "clio_safe_bdev"
score: 0.5Operations
Method IDs come from context-transfer-engine/core/clio_mod.yaml;
the client surface is include/clio_cte/core/core_client.h.
Every client method has an asynchronous form that returns a future, and
the Tag wrapper adds blocking convenience calls.
Targets
- RegisterTarget and UnregisterTarget
add or remove a storage target on a node. Registration takes the target
name, bdev type, total size and optionally an existing pool to attach
and a file growth unit. Compose registers the
storageentries for you; clients call this only when adding tiers at run time. - ListTargets, GetTargetInfo (score, capacity, performance figures) and StatTargets, which refreshes free space and performance for every target and runs periodically.
- GetCapacity reports remaining capacity.
Tags
- GetOrCreateTag returns a tag by name, creating it if needed. A second overload attaches a fault handler: another pool asked to produce a blob when a read misses, used for lazy copies and checkpoints.
- DelTag, RenameTag, GetOrCreateTagAlias, GetTagName, GetNumAliases and GetTagSize. Tag size is summed across containers.
- GetContainedBlobs lists a tag's blob names. TagXattr sets, gets, deletes or lists one extended attribute key on a tag.
- SyncTag flushes a tag's blobs to a persistent tier and syncs the devices and the write-ahead log. This is what fsync on the filesystem maps to.
Blobs
- PutBlob writes a byte range at an offset, with an
optional score and a
Context. Variants: a shared-memory pointer, a private buffer (the runtime copies it once, or not at all when the caller is inside the runtime), a vectored put of several regions in one task, and a deferred put that returns immediately and is tracked in a process-wide registry with flow control. Conditional forms put only if the blob is absent or still at a given version. - GetBlob reads a byte range, with the same shared-memory, private-buffer, vectored and deferred variants. A generational get waits for a writer to publish a given generation. When the bytes are in the local RAM tier and the metadata mirror is attached, a private-buffer get copies them without a round trip.
- DelBlob, TruncateBlob, GetBlobSize, GetBlobScore, GetBlobInfo (score, size, blocks and targets) and GetResidency.
- ReorganizeBlob sets a new score and moves the blob to the tier that score selects. ReorganizeHint passes a phase hint to the data organizer. Evict frees the lowest-scored blobs off a tier.
- Pod variants of put, get and reorganize, and batched multi-put, multi-get and multi-score forms, exist for GPU callers with fixed-length names.
Queries
- TagQuery matches tag names against a regular expression. BlobQuery matches blobs by tag and blob name patterns. ListLocalBlobs lists blobs matching a name pattern on the queried containers.
- SemanticSearch runs BM25 keyword search over blob contents with a tag and blob filter and a top-k limit. TemporalSearch returns blobs whose last-modified time falls in an inclusive range, filtered by tag and blob patterns and sorted by timestamp.
- PollTelemetryLog returns telemetry records newer than a given logical time.
Client API
In C++ the Tag wrapper covers the common case. Its blocking
put from a private buffer defaults the score to 1.0; the asynchronous and
shared-memory forms default to -1.0, which means keep the existing
score.
#include <clio_cte/core/core_client.h>
clio::cte::core::Tag tag("experiment/run1"); // GetOrCreateTag
std::string payload = "hello";
tag.PutBlob("step0", payload.data(), payload.size()); // offset 0, score 1.0
std::string out(payload.size(), '\0');
tag.GetBlob("step0", out.data(), out.size());
float score = tag.GetBlobScore("step0");
tag.ReorganizeBlob("step0", 0.2f); // demote to a slower tierThe Python module is clio_cte_core_ext, built with nanobind
from context-transfer-engine/wrapper/python/core_bindings.cc.
Blob payloads are bytes. The Tag class binds
PutBlob, GetBlob, GetBlobScore, GetBlobSize, GetContainedBlobs,
ReorganizeBlob and GetTagId; the Client class adds DelBlob,
RegisterTarget, TagQuery, BlobQuery, SemanticSearch, TemporalSearch,
PollTelemetryLog, the conditional and deferred puts, and the asynchronous
forms, which return a future with wait, done and
result. All blocking calls release the GIL.
import clio_cte_core_ext as cte
cte.clio_init(cte.RuntimeMode.kClient)
tag = cte.Tag("experiment/run1")
tag.PutBlob("step0", b"hello", 0)
size = tag.GetBlobSize("step0")
data = tag.GetBlob("step0", size, 0)
tag.ReorganizeBlob("step0", 0.0)
client = cte.get_cte_client()
client.DelBlob(tag.GetTagId(), "step0")The Python API page covers the module in more depth.
Configuration
Keys are parsed in context-transfer-engine/core/src/core_config.cc;
defaults are the constructors in core_config.h. Sizes accept
suffixes from k to p (powers of 1024).
storage (list)
path:ram::<name>for memory tiers, a file path for file tiers, a URL with a prefix fors3andgcs. Required unless binding to an existing pool.bdev_type:file,ram,hbm,pinned,noop,s3orgcs.capacity_limit: size string.0is valid only forramand means 80% of system DRAM.score: 0.0 to 1.0. Absent means automatic scoring from measured bandwidth.persistence_level:volatile(default),temporaryorlong_term. Placement filters and persistent replicas key off this, so a tier left at volatile can never satisfy a persistence request.growth_unit(file tiers): lazy-growth step, default 1 GB. The first unit is reserved at creation.existing_pool_id,existing_pool_module: bind to an already composed pool; the module name is for logging only.
performance
metadata_log_path: directory for the metadata write-ahead log and snapshots. Empty (the built-in default) disables metadata durability; the shipped configuration sets it.transaction_log_capacity: WAL capacity, default 32 MB. A full snapshot is rebuilt when the WAL exceeds it.flush_metadata_period_ms(5000) andflush_data_period_ms(10000): periods of the metadata and data flush tasks.flush_data_min_persistence(1) is the lowest persistence level the data flush writes to.fsync_mode:durable(default) returns from fsync once bytes sit on a tier at the minimum persistence and are synced along with the WAL;deferredreturns at once and relies on the periodic flushes.stat_targets_period_ms(1000): how often each target's capacity and free space are re-read. Validated to 10 to 60000.max_concurrent_operations(64),score_threshold(0.7),score_difference_threshold(0.05),target_stat_interval_ms(5000, marked legacy in the header).
targets
neighborhood: how many nodes, in ring order, a container registers persistent targets from. The built-in default is 4; the shipped configuration sets 1, so placement is owner-local. Memory tiers are always node-local regardless.default_target_timeout_ms(30000),poll_period_ms(5000).failover_to_successor(false): route a dead owner's blobs to the first live successor, which serves the replication module's remote copy.
dpe and organizer
dpe.dpe_type:max_bw(default),round_robinorrandom.organizer(top level of the entry, not under performance):none(default),frecency,grayscott,cyclic,scatterorhotset.organizer_tasks(1) periodic replicas partition the blob space;organizer_period_ms(5000) is the round period.
The config class can also load a standalone CTE configuration file
from the path in CLIO_SERVER_CONF; an unset variable means
built-in defaults, not an error.
Placement and organizer hooks
Two policy interfaces are pluggable without touching the data path.
- Data placement engine.
DataPlacementEngine::SelectTargetsininclude/clio_cte/core/dpe/dpe.hreceives the available targets, the blob's score and the size to place, and returns an ordered list of targets. Policies live one per file underdpe/and are registered inDpeFactory::CreateDpe. The placement page describes howmax_bwuses the score and capacity. - Data organizer.
DataOrganizer::Reorganizeininclude/clio_cte/core/data_organizer/data_organizer.his a coroutine called by each periodic DynamicReorganize task. It receives a read-only snapshot per blob (score, last read and write, access count, size) and applies new scores through the runtime's reorganize logic. Policies are registered inDataOrganizerFactory::Get. Withorganizer: noneno periodic task is spawned and placement changes only through explicit ReorganizeBlob calls.
How other modules attach
Every interposer derives from CoreInterposer in
include/clio_cte/core/core_interposer.h. It forwards any core
method it does not override to the next pool verbatim, routes tasks exactly
as the core does (blob verbs to the blob's owner by hash, tag creation by
tag-name hash), and numbers its own verbs at 100 and above so they never
shadow a core method. Compose order must put each interposer after the
pool its next_pool_id names.
- Replication (561.0) writes durable copies on the
next nodes by hash before the put is acknowledged and keeps the shadow
copies that
failover_to_successorserves. - Compressor (562.0, optional) transforms bytes on the way down and marks the blob with a transform flag the core persists.
- Indexer (564.0) sits above the compressor so its read-backs see logical bytes, and keeps its own index log.
- Cache (563.0) is the top of the chain. It keeps a raw
node-local copy, routes reads to the submitter's node, and keeps the
zero-IPC shared-memory path live even for compressed blobs. A client
bound to the chain attaches the core's metadata mirror with
AttachShmCacheOf. - Filesystem maps files to tags and blobs, and points at the top of whatever chain is composed. Its fsync becomes SyncTag.
Behaviors worth knowing
- Durability
- Blob layouts are logged to the WAL after the bytes are on the device.
Without
metadata_log_paththe disk tier's bytes survive a restart but nothing remembers which blob they belong to. Bytes that lived only in RAM are gone after a restart; reads into that range fail with an I/O error rather than returning zeros. - Consistency
- Each blob has one owner container that serialises its mutations. Reads of one blob run concurrently with each other and never block behind writers except in-place overwrites, which drain readers first. Conditional puts fail with distinct return codes.
- Capacity
- A put with no room evicts droppable (cache) data on the local node and retries once. Authoritative data is never dropped by the put path; demotion happens through the organizer or explicit reorganization.
- Startup
- A put that arrives before targets are registered waits up to two minutes instead of failing.
Current limits
- The method manifest in
clio_mod.yamlhas drifted from the generated method header: methods 36 to 42, MultiPutBlob (48) and RegisterReplicaContainer (49) exist in the header but not the manifest, so the manifest's own comment says to treat the header as authoritative until reconciled. - The organizer validation error message lists only
noneandfrecencyeven though all six names are accepted. - The shared-memory metadata mirror is sized once at startup and holds at most 16 blocks per blob; larger or fragmented blobs take the normal request path.
- Client-mode puts have no zero-IPC fast path. A direct write into the RAM segment could race the organizer relocating the blob, so writes always go through a task.
- Node failure, recovery, migrate and upgrade lifecycle methods are not implemented by this module.
How it is tested
context-transfer-engine/test/unit/: Catch2 tests for the core, includingtest_core_functionality.cc,test_tag_operations.cc,test_reorganize_blob.cc,test_cte_config_errors.cc,test_cte_config_dpe.cc,test_cte_existing_pool.cc,test_data_organizer.cc,test_cte_transaction_log.cc,test_query.cc,test_evict.cc,test_cte_shm_metadata_cache.cc,test_torn_read.ccand the interposer stack tests.context-transfer-engine/test/integration/: multi-node Docker Compose scenarios (distributed,restart,coherence,client_crash,replication_persistand others) with their ownclio_config.yamlfiles.context-transfer-engine/wrapper/python/: Python tests such astest_bindings.py,test_cte_async.py,test_cte_conditional.pyandtest_cte_semantic_search.py.