ID | IEP-9 |
Author | Pavel Tupitsyn |
Sponsor | Pavel Tupitsyn |
Created | 20-NOV-2017 |
Status | DRAFT |
Implement thin Ignite client in any programming language / platform using a well-defined binary connectiona protocol.
"Thin" here means that we do not start an Ignite node in order to communicate with the cluster, we do not implement discovery/communication spi logic, as opposed to currently available Ignite Client Mode.
Every Ignite node listens on a TCP port. Thin client implementations connect to any node in the cluster through TCP socket and perform Ignite operations using a well-defined binary protocol.
Server-side connection parameters are defined in ClientConnectorConfiguration
class. Default port is 10800. Connector is enabled by default, no configuration changes needed.
Byte order is little-endian. Strings are UTF-8 with int prefix (no null terminator). All data types in this proposal are Java-like (byte = 8 bits, short = 2 bytes, int = 4 bytes, long = 8 bytes).
User data, such as cache keys and values, is represented in Ignite Binary Object format. Binary Object can be a primitive (predefined type) or a complex object.
Primitives (predefined types)
Primitives are represented as a type code + value:
byte | Type code |
... | Value |
Available primitives are listed below:
Name | Type Code | Size (bytes) |
---|---|---|
byte | 1 | 1 |
short | 2 | 2 |
int | 3 | 4 |
long | 4 | 8 |
float | 5 | 4 |
double | 6 | 8 |
char | 7 | 2 |
bool | 8 | 1 |
string | 9 | 4 bytes length + length * 2 |
UUID (Guid) | 10 | 8 |
date | 11 | 8 |
byte array | 12 | 4 bytes length + length |
short array | 13 | 4 bytes length + length * 2 |
int array | 14 | 4 bytes length + length * 4 |
long array | 15 | 4 bytes length + length * 8 |
float array | 16 | 4 bytes length + length * 4 |
double array | 17 | 4 bytes length + length * 8 |
char array | 18 | 4 bytes length + length * 2 |
bool array | 19 | 4 bytes length + length |
string array | 20 | 4 bytes length + variable length strings, see above |
UUID (Guid) array | 21 | 4 bytes length + length * 8 |
date array | 22 | 4 bytes length + length * 8 |
NULL | 101 | 0 |
Complex Objects
Complex objects consist of a 24-byte header and a set of fields (key-value pairs).
Header | |
---|---|
byte | Object type code, always 103 |
byte | Version, always 1 |
short | Flags, see below |
int | Type id, Java-style hash code of the type name |
int | Hash code, Java-style hash of contents without header, necessary for comparisons |
int | Length, including header |
int | Schema Id, see below |
int | SchemaOffset, see below |
All messages, request and response, including handshake, start with int
message length (excluding these first 4 bytes). E.g. empty message would be represented by 4 zero bytes.
int | Length of Payload |
... | Payload |
Length is omitted in all message descriptions below for brevity.
The first message upon connection establishment must be a handshake. Handshake ensures that client and server versions are compatible.
Request | |
byte | Handshake code, always 1 |
short | Version major |
short | Version minor |
short | Version patch |
byte | Client code, always 2 |
Response (success) | |
byte | Success flag, 1 |
Response (failure) | |
byte | Success flag, 0 |
short | Server version major |
short | Server version minor |
short | Server version patch |
string | Error message |
Upon successful handshake client can start performing operations by sending a request with specific op code. Each operation has it's own request and response format, with a common header.
Request | |
short | Operation code |
long | Request id, generated by client and returned as-is in response |
... | Operation-specific data |
Response | |
long | Request id (see above) |
int | Status code (0 for success, otherwise error code) |
string | Error message (present only when status is not 0) |
... | Operation-specific data |
Operation codes and request ids are omitted everywhere below for brevity.
OP_CACHE_GET = 1
Retrieves a value from cache by key.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
Response | |
BinaryObject | value |
OP_CACHE_GET_ALL = 12
Retrieves multiple values from cache given multiple keys.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
int | key count |
BinaryObject * count | keys |
Response | |
int | result count |
(BinaryObject + BinaryObject) * count | Resulting key-value pairs. Keys that are not present in the cache are not included. |
OP_CACHE_PUT = 4
Puts a value with a given key to cache (overwriting existing value if any).
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
BinaryObject | value |
Empy response.
OP_CACHE_PUT_ALL = 20
Puts multiple key-value pairs to cache (overwriting existing associations if any).
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
int | Key-value pair count |
(BinaryObject + BinaryObject) * count | Key-value pairs |
Empy response.
OP_CACHE_CONTAINS_KEY = 10
Returns a value indicating whether given key is present in cache.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
Response | |
bool | True when key is present, false otherwise |
OP_CACHE_CONTAINS_KEYS = 11
Returns a value indicating whether all given keys are present in cache.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
int | key count |
BinaryObject * count | keys |
Response | |
bool | True when keys are present, false otherwise |
OP_CACHE_GET_AND_PUT = 13
Puts a value with a given key to cache, returning previous value for that key.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
BinaryObject | value |
Response | |
BinaryObject | Existing value for the specified key, or null |
OP_CACHE_GET_AND_REPLACE = 14
Puts a value with a given key to cache, returning previous value for that key, if and only if there is a value currently mapped for that key.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
BinaryObject | value |
Response | |
BinaryObject | Existing value for the specified key |
OP_CACHE_GET_AND_REMOVE = 15
Removes the cache entry with specified key, returning the value.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
Response | |
BinaryObject | Existing value for the specified key, or null |
OP_CACHE_PUT_IF_ABSENT = 16
Puts a value with a given key to cache only if the key does not already exist.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
BinaryObject | value |
Response | |
bool | success flag |
OP_CACHE_GET_AND_PUT_IF_ABSENT = 17
Puts a value with a given key to cache only if the key does not already exist.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
BinaryObject | value |
Response | |
BinaryObject | Previously contained value regardless of whether put happened or not. |
OP_CACHE_REPLACE = 18
Puts a value with a given key to cache only if the key already exists.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
BinaryObject | value |
Response | |
bool | value indicating whether replace happened |
OP_CACHE_REPLACE_IF_EQUALS = 19
Puts a value with a given key to cache only if the key already exists and value equals provided value.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
BinaryObject | value |
BinaryObject | new value |
Response | |
bool | value indicating whether replace happened |
OP_CACHE_CLEAR = 21
Clears the cache without notifying listeners or cache writers.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
Empy response.
OP_CACHE_CLEAR_KEY = 22
Clears the cache key without notifying listeners or cache writers.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
Empy response.
OP_CACHE_CLEAR_KEYS = 23
Clears the cache keys without notifying listeners or cache writers.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
int | Key count |
BinaryObject * count | keys |
Empy response.
OP_CACHE_REMOVE_KEY = 24
Removes an entry with a given key, notifying listeners and cache writers.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
Response | |
bool | value indicating whether remove happened |
OP_CACHE_REMOVE_IF_EQUALS = 25
Removes an entry with a given key if provided value is equal to actual value, notifying listeners and cache writers.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
BinaryObject | key |
BinaryObject | value |
Response | |
bool | value indicating whether remove happened |
OP_CACHE_GET_SIZE = 26
Gets the cache size.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
int | Peek mode count. Can be 0, which means ALL. |
byte * count | Peek modes. ALL = 0, NEAR = 1, PRIMARY = 2, BACKUP = 3 |
Response | |
long | Cache size |
OP_CACHE_REMOVE_KEYS = 27
Removes entries with given keys, notifying listeners and cache writers.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
int | key count |
BinaryObject * count | keys |
Empty response.
OP_CACHE_REMOVE_ALL = 28
Removes all entries from cache, notifying listeners and cache writers.
Request | |
int | Cache ID: Java-style hash code of the cache name |
byte | flags |
Empty response.
OP_CACHE_CREATE_WITH_NAME = 29
Creates a cache with a given name. Cache template can be applied if there is '*' in the cache name. Throws exception if a cache with specified name already exists.
Request | |
string | Cache name |
Empty response.
OP_CACHE_GET_OR_CREATE_WITH_NAME = 30
Creates a cache with a given name. Cache template can be applied if there is '*' in the cache name. Does nothing if the cache exists.
Request | |
string | Cache name |
Empty response.
OP_CACHE_DESTROY = 31
Destroys cache with a given name.
Request | |
string | Cache name |
Empty response.
OP_CACHE_GET_NAMES = 32
Gets existing cache names.
Empty request.
Response | |
int | Cache count |
string * count | Cache names |
OP_CACHE_GET_CONFIGURATION = 33
Gets cache configuration
Request | |
int | Cache ID: Java-style hash code of the cache name |
Response | |
CacheConfiguration | See below |
Cache configuration is sent and received by server in the following format:
CacheConfiguration | |
int | Length of the configuration, in bytes |
int | CacheAtomicityMode, TRANSACTIONAL = 0, ATOMIC = 1 |
int | Backups |
int | CacheMode, LOCAL = 0, REPLICATED = 1, PARTITIONED = 2 |
bool | CopyOnRead |
string | DataRegionName |
bool | EagerTtl |
bool | StatisticsEnabled |
string | GroupName |
bool | invalidate |
long | DefaultLockTimeout (milliseconds) |
int | MaxConcurrentAsyncOperations |
int | MaxQueryIterators |
string | Name |
bool | IsOnheapcacheEnabled |
int | PartitionLossPolicy, READ_ONLY_SAFE = 0, READ_ONLY_ALL = 1, READ_WRITE_SAFE = 2, READ_WRITE_ALL = 3, IGNORE = 4 |
TODO: Other operations
As an example let's see how to connect to Ignite instance on a local machine and retrieve an int value for a given int key, in pseudocode.
var socket = openSocket("127.0.0.1:10800"); // Message length socket.writeInt(8); // Handshake operation socket.writeByte(1); // Protocol version 1.0.0 socket.writeShort(1); socket.writeShort(0); socket.writeShort(0); // Client type: thin client socket.writeByte(2); // Receive result var resLen = socket.readInt(); assert resLen == 1; // Success message length is 1 var res = socket.readByte(); assert res == 1; // Success code // END HANDSHAKE // Perform OP_CACHE_GET // Message length socket.writeInt(20) // Op code = OP_CACHE_GET socket.writeShort(1); // Request id (can be anything; does not matter for simple blocking socket IO) var reqId = 1; socket.writeLong(reqId); // Cache id var cacheName = "myCache"; var cacheId = javaHashCode(cacheName); socket.writeInt(cacheId); // Flags = none socket.writeByte(0); // Cache key socket.writeByte(3); // Integer type code socket.writeInt(1); // Our cache value // Read result var len = socket.readInt(); assert len == 17; // requestId (8) + status code (4) + int object (1 + 4) var resReqId = socket.readLong(); assert reqId == resReqId; var statusCode = socket.readInt(); assert statusCode == 0; // Success var resTypeCode = socket.readByte(); assert restypeCode == 3; // Integer var res = socket.readInt(); print(res); // Resulting cache value
Similar protocols from other vendors:
See "thin client" component in JIRA