ytsaurus-api 0.3.0

The transport-independent YTsaurus client interface, implemented over HTTP and over the RPC proxy
Documentation

ytsaurus-api

The transport-independent YTsaurus client interface: one API, two transports.

Pre-release, published from 0.3.0. The interface is the one thing here that is expensive to change later and it has not settled: the version is 0.x and it may change in a patch release. It is on crates.io because ytsaurus-rpc and ytsaurus-client's create_client / create_rpc_client return this crate's TableClient and could not be published otherwise.

Why

YTsaurus reaches its dynamic tables two ways, and the C++ client does not make callers pick an API to go with the transport. It has one interface and two constructors:

IClientPtr CreateClient   (const TString& serverName, ...);  // HTTP
IClientPtr CreateRpcClient(const TString& serverName, ...);  // RPC proxy

This crate is the Rust equivalent of what those return, and the layering copies the C++ because that layering is the reason it works:

C++ here
yt/yt/client/api — the interface this crate
yt/yt/client/api/rpc_proxy — one implementation ytsaurus-rpc
yt/cpp/mapreduce — the wrapper with both constructors ytsaurus-client

So the constructors live in ytsaurus-client, which depends on both, and choosing a transport is one line:

use ytsaurus_api::{LookupOptions, Row, TableClient};

# fn main() -> Result<(), ytsaurus_api::Error> {
let client = ytsaurus_client::create_client("http://localhost:8000")?;   // HTTP
// or, with the `rpc` feature:
// let client = ytsaurus_client::create_rpc_client("localhost:8011")?;   // RPC

let key = Row::new().with("key", 1i64);
let rows = client.lookup_rows("//tmp/table", &[key], &LookupOptions::default())?;
# Ok(())
# }

The interface is synchronous

Deliberately, and it is the decision here worth arguing about. The C++ wrapper blocks, every other crate in this workspace is synchronous, and a MapReduce job is a synchronous, single-purpose process.

Async callers lose nothing. ytsaurus_rpc::Client is untouched and is still the only way to get concurrent in-flight requests — which is the entire reason the RPC proxy exists. What this interface buys is portability between transports, not concurrency; the blocking facade drives one call at a time.

The row model

Column names, not ids. The RPC wire format numbers its values and resolves them through a name table, HTTP names them directly, and a caller should not have to know which.

use ytsaurus_api::{Row, Value};

let row = Row::new().with("key", 1i64).with("value", "hello");
assert_eq!(row.get("key"), Some(&Value::Int64(1)));

Rows keep the order their columns were added in, because a key row's column order is the table's key order — sorting them would ask for a different row. Strings are bytes rather than String: a YTsaurus column may legitimately hold something that is not UTF-8.

What it covers, and what it does not

The dynamic-table surface both transports implement: lookup_rows, select_rows, insert_rows, delete_rows, and tablet transactions.

Cypress, operations and file I/O stay on ytsaurus-client. The RPC crate deliberately does not implement them, and an interface with half its methods unavailable on one transport would be worse than two honest APIs.

One asymmetry, and the cluster reports it

Tablet transactions are RPC-only. They are sticky — a transaction belongs to the proxy that created it, and every later call in it must reach that same proxy — and an HTTP client routes each request independently. Ask an HTTP client for one and a real cluster answers:

Sticky transaction … is not found, this usually means that you use tablet transactions within HTTP API; consider using RPC API instead

So the HTTP implementation refuses up front with Error::Unsupported and quotes that advice, rather than failing on the second call. This is one of the reasons CreateRpcClient exists in the C++ at all.

Everything else — reads, and writes outside a transaction — works on both.

Checked against a real cluster

cargo run -p ytsaurus-client --features rpc --example both_transports

Runs the same code over each transport against one cluster and compares the results row for row. The two are wire-level unrelated — YSON over HTTP, the row wire protocol over bus — so "they behave the same" is a claim that needs running, and this is the first differential test in this repository between two independent implementations of the same operations.