The ElCache C SDK provides high-performance cache access with zero external dependencies.
- Zero dependencies: Only requires POSIX APIs
- Two transport modes: Unix sockets (reliable) and shared memory (fastest)
- Partial read support: Efficiently read byte ranges of large values
- Position-based writes: Write large values in chunks with parallel support
- Streaming API: Handle values larger than memory
- Thread safety: Each client instance is single-threaded; create one per thread
cd build
cmake ..
make elcache_sdk
# Install
sudo make install# Dynamic linking
gcc myapp.c -lelcache_sdk -o myapp
# Static linking
gcc myapp.c -l:libelcache_sdk.a -lpthread -lrt -o myapp#include <elcache/elcache_sdk.h>
#include <stdio.h>
#include <string.h>
int main() {
// Create client
elcache_client_t* client = elcache_client_create();
if (!client) {
fprintf(stderr, "Failed to create client\n");
return 1;
}
// Connect
elcache_error_t err = elcache_client_connect_unix(client,
"/var/run/elcache/elcache.sock");
if (err != ELCACHE_OK) {
fprintf(stderr, "Connect failed: %s\n", elcache_error_string(err));
elcache_client_destroy(client);
return 1;
}
// Store a value
const char* key = "greeting";
const char* value = "Hello, World!";
err = elcache_client_put(client, key, strlen(key),
value, strlen(value), NULL);
if (err != ELCACHE_OK) {
fprintf(stderr, "Put failed: %s\n", elcache_error_string(err));
}
// Retrieve the value
char buffer[1024];
size_t len;
err = elcache_client_get(client, key, strlen(key),
buffer, sizeof(buffer), &len, NULL);
if (err == ELCACHE_OK) {
printf("Got: %.*s\n", (int)len, buffer);
}
// Cleanup
elcache_client_destroy(client);
return 0;
}For values larger than 256KB (the default buffer size), use position-based writes:
#include <elcache/elcache_sdk.h>
int upload_large_file(elcache_client_t* client,
const char* key,
const void* data,
size_t total_size) {
// 1. Create sparse entry
elcache_client_create_sparse(client, key, strlen(key), total_size, NULL);
// 2. Write in chunks (can be parallel from multiple threads)
size_t chunk_size = 200 * 1024; // 200KB chunks
for (size_t offset = 0; offset < total_size; offset += chunk_size) {
size_t len = (offset + chunk_size > total_size)
? (total_size - offset) : chunk_size;
elcache_client_write_range(client, key, strlen(key),
offset, (char*)data + offset, len);
}
// 3. Finalize
return elcache_client_finalize(client, key, strlen(key));
}elcache_client_t* elcache_client_create(void);Create a new client instance.
Returns: Client pointer, or NULL on failure.
void elcache_client_destroy(elcache_client_t* client);Destroy client and free all resources.
elcache_error_t elcache_client_connect_unix(
elcache_client_t* client,
const char* socket_path
);Connect via Unix domain socket.
Parameters:
client- Client instancesocket_path- Path to Unix socket (e.g.,/var/run/elcache/elcache.sock)
Returns: ELCACHE_OK on success, error code otherwise.
elcache_error_t elcache_client_connect_shm(
elcache_client_t* client,
const char* shm_path
);Connect via shared memory (fastest, requires same host).
Parameters:
client- Client instanceshm_path- Shared memory name (e.g.,/elcache_shm)
Returns: ELCACHE_OK on success, error code otherwise.
void elcache_client_disconnect(elcache_client_t* client);Disconnect from server.
int elcache_client_is_connected(const elcache_client_t* client);Check connection status.
Returns: Non-zero if connected, 0 otherwise.
elcache_error_t elcache_client_put(
elcache_client_t* client,
const void* key,
size_t key_len,
const void* value,
size_t value_len,
const elcache_put_options_t* options // NULL for defaults
);Store a value.
Options:
typedef struct {
int64_t ttl_seconds; // 0 = no expiry
uint32_t flags; // User-defined flags
int no_memory; // Skip memory cache
int no_disk; // Skip disk cache
int no_cluster; // Don't replicate
} elcache_put_options_t;
// Initialize with defaults
elcache_put_options_t opts = ELCACHE_PUT_OPTIONS_INIT;elcache_error_t elcache_client_get(
elcache_client_t* client,
const void* key,
size_t key_len,
void* buffer,
size_t buffer_size,
size_t* value_len, // Output: actual value length
const elcache_get_options_t* options // NULL for defaults
);Retrieve a value.
Options:
typedef struct {
uint64_t offset; // Start offset (for partial read)
uint64_t length; // Length to read (0 = entire value)
int allow_partial; // Return partial data if available
int timeout_ms; // Timeout (0 = default)
} elcache_get_options_t;
// Initialize with defaults
elcache_get_options_t opts = ELCACHE_GET_OPTIONS_INIT;Returns:
ELCACHE_OK- Full value returnedELCACHE_ERR_NOT_FOUND- Key not foundELCACHE_ERR_BUFFER_TOO_SMALL- Buffer too small (value_len contains required size)ELCACHE_ERR_PARTIAL- Partial data returned (when allow_partial is set)
elcache_error_t elcache_client_get_alloc(
elcache_client_t* client,
const void* key,
size_t key_len,
void** value, // Output: allocated buffer
size_t* value_len, // Output: value length
const elcache_get_options_t* options
);Retrieve a value with automatic allocation.
Important: Caller must free the buffer with elcache_free().
elcache_error_t elcache_client_delete(
elcache_client_t* client,
const void* key,
size_t key_len
);Delete a value.
elcache_error_t elcache_client_exists(
elcache_client_t* client,
const void* key,
size_t key_len,
int* exists // Output: 1 if exists, 0 otherwise
);Check if a key exists.
elcache_error_t elcache_client_read_range(
elcache_client_t* client,
const void* key,
size_t key_len,
uint64_t offset,
void* buffer,
size_t buffer_size,
size_t* bytes_read
);Read a specific byte range.
Example:
// Read bytes 1000-1999 of a large file
char buffer[1000];
size_t bytes_read;
err = elcache_client_read_range(client, "bigfile", 7,
1000, // offset
buffer, sizeof(buffer),
&bytes_read);The position-based write API allows you to write large values in parts, potentially from multiple threads in parallel. This is useful for:
- Writing files larger than the SDK buffer size (default 256KB)
- Parallel uploads from multiple sources
- Resumable uploads
- Assembling data from multiple producers
- Create a sparse entry with total size
- Write ranges at specific offsets (can be parallel)
- Finalize to commit to cache (validates all bytes written)
elcache_error_t elcache_client_create_sparse(
elcache_client_t* client,
const void* key,
size_t key_len,
uint64_t total_size,
const elcache_put_options_t* options // NULL for defaults
);Create a sparse entry that will be filled with position-based writes.
Parameters:
client- Client instancekey- Cache keykey_len- Key length in bytestotal_size- Total size of the final value in bytesoptions- Optional put options (TTL, flags, etc.)
Returns:
ELCACHE_OK- Entry createdELCACHE_ERR_INVALID_ARG- Invalid parametersELCACHE_ERR_INTERNAL- Entry already exists
elcache_error_t elcache_client_write_range(
elcache_client_t* client,
const void* key,
size_t key_len,
uint64_t offset,
const void* data,
size_t data_len
);Write data at a specific offset within a sparse entry.
Parameters:
client- Client instancekey- Cache keykey_len- Key length in bytesoffset- Byte offset where data should be writtendata- Data bufferdata_len- Length of data to write
Returns:
ELCACHE_OK- Data written successfullyELCACHE_ERR_NOT_FOUND- Sparse entry doesn't existELCACHE_ERR_INVALID_ARG- Range exceeds total sizeELCACHE_ERR_VALUE_TOO_LARGE- Data too large for buffer
Note: Multiple write_range calls can overlap or be called in parallel from different threads/connections. Each byte position only needs to be written once.
elcache_error_t elcache_client_finalize(
elcache_client_t* client,
const void* key,
size_t key_len
);Finalize a sparse entry, validating all bytes have been written and moving it to the main cache.
Parameters:
client- Client instancekey- Cache keykey_len- Key length in bytes
Returns:
ELCACHE_OK- Entry finalized and available in cacheELCACHE_ERR_NOT_FOUND- Sparse entry doesn't existELCACHE_ERR_PARTIAL- Not all byte ranges have been written
#include <elcache/elcache_sdk.h>
#include <string.h>
int upload_large_file(elcache_client_t* client,
const char* key,
const uint8_t* data,
size_t total_size) {
// Step 1: Create sparse entry
elcache_error_t err = elcache_client_create_sparse(
client, key, strlen(key), total_size, NULL);
if (err != ELCACHE_OK) {
return -1;
}
// Step 2: Write in 200KB chunks
const size_t chunk_size = 200 * 1024;
for (size_t offset = 0; offset < total_size; offset += chunk_size) {
size_t len = (offset + chunk_size > total_size)
? (total_size - offset)
: chunk_size;
err = elcache_client_write_range(
client, key, strlen(key), offset, data + offset, len);
if (err != ELCACHE_OK) {
return -1;
}
}
// Step 3: Finalize
err = elcache_client_finalize(client, key, strlen(key));
if (err != ELCACHE_OK) {
return -1;
}
return 0;
}#include <elcache/elcache_sdk.h>
#include <pthread.h>
typedef struct {
const char* socket_path;
const char* key;
const uint8_t* data;
size_t offset;
size_t length;
int success;
} write_task_t;
void* write_worker(void* arg) {
write_task_t* task = (write_task_t*)arg;
// Each thread creates its own client connection
elcache_client_t* client = elcache_client_create();
elcache_client_connect_unix(client, task->socket_path);
// Write this thread's chunk
elcache_error_t err = elcache_client_write_range(
client, task->key, strlen(task->key),
task->offset, task->data + task->offset, task->length);
task->success = (err == ELCACHE_OK);
elcache_client_destroy(client);
return NULL;
}
int parallel_upload(const char* socket_path,
const char* key,
const uint8_t* data,
size_t total_size,
int num_threads) {
// Create sparse entry first
elcache_client_t* client = elcache_client_create();
elcache_client_connect_unix(client, socket_path);
elcache_error_t err = elcache_client_create_sparse(
client, key, strlen(key), total_size, NULL);
if (err != ELCACHE_OK) {
elcache_client_destroy(client);
return -1;
}
// Spawn threads for parallel writes
pthread_t* threads = malloc(num_threads * sizeof(pthread_t));
write_task_t* tasks = malloc(num_threads * sizeof(write_task_t));
size_t chunk_size = total_size / num_threads;
for (int i = 0; i < num_threads; i++) {
tasks[i].socket_path = socket_path;
tasks[i].key = key;
tasks[i].data = data;
tasks[i].offset = i * chunk_size;
tasks[i].length = (i == num_threads - 1)
? (total_size - tasks[i].offset)
: chunk_size;
tasks[i].success = 0;
pthread_create(&threads[i], NULL, write_worker, &tasks[i]);
}
// Wait for all threads
int all_success = 1;
for (int i = 0; i < num_threads; i++) {
pthread_join(threads[i], NULL);
if (!tasks[i].success) all_success = 0;
}
free(threads);
free(tasks);
if (!all_success) {
elcache_client_destroy(client);
return -1;
}
// Finalize
err = elcache_client_finalize(client, key, strlen(key));
elcache_client_destroy(client);
return (err == ELCACHE_OK) ? 0 : -1;
}For values too large to fit in memory:
elcache_write_stream_t* stream;
// Start stream
err = elcache_client_write_stream_start(client,
key, key_len,
total_size,
NULL, // options
&stream);
// Write chunks
while (has_more_data) {
err = elcache_write_stream_write(stream, chunk, chunk_len);
}
// Finish
err = elcache_write_stream_finish(stream);
// Or abort
elcache_write_stream_abort(stream);elcache_read_stream_t* stream;
// Start stream
err = elcache_client_read_stream_start(client,
key, key_len,
NULL, // options
&stream);
// Get total size
uint64_t total = elcache_read_stream_total_size(stream);
// Read chunks
char buffer[65536];
size_t bytes_read;
while (!elcache_read_stream_eof(stream)) {
err = elcache_read_stream_read(stream, buffer, sizeof(buffer), &bytes_read);
// process buffer...
}
// Close
elcache_read_stream_close(stream);void elcache_client_set_timeout(elcache_client_t* client, int timeout_ms);Set operation timeout in milliseconds.
void elcache_client_set_recv_buffer(elcache_client_t* client, size_t size);Set receive buffer size.
void elcache_client_set_send_buffer(elcache_client_t* client, size_t size);Set send buffer size.
typedef struct {
uint64_t hits;
uint64_t misses;
uint64_t partial_hits;
uint64_t bytes_read;
uint64_t bytes_written;
uint64_t latency_us_avg;
uint64_t latency_us_p99;
} elcache_stats_t;
elcache_error_t elcache_client_stats(
elcache_client_t* client,
elcache_stats_t* stats
);Get client-side statistics.
typedef enum {
ELCACHE_OK = 0,
ELCACHE_ERR_INVALID_ARG = -1,
ELCACHE_ERR_NOT_FOUND = -2,
ELCACHE_ERR_PARTIAL = -3,
ELCACHE_ERR_TIMEOUT = -4,
ELCACHE_ERR_CONNECTION = -5,
ELCACHE_ERR_PROTOCOL = -6,
ELCACHE_ERR_BUFFER_TOO_SMALL = -7,
ELCACHE_ERR_KEY_TOO_LARGE = -8,
ELCACHE_ERR_VALUE_TOO_LARGE = -9,
ELCACHE_ERR_OUT_OF_MEMORY = -10,
ELCACHE_ERR_INTERNAL = -11,
} elcache_error_t;const char* elcache_error_string(elcache_error_t error);Get human-readable error message.
const char* elcache_client_last_error(const elcache_client_t* client);Get detailed error message from last operation.
void elcache_free(void* ptr);Free memory allocated by elcache_client_get_alloc().
#include <elcache/elcache_sdk.h>
// Cache an HTTP response
void cache_response(elcache_client_t* client,
const char* url,
const char* body, size_t body_len,
int ttl_seconds) {
elcache_put_options_t opts = ELCACHE_PUT_OPTIONS_INIT;
opts.ttl_seconds = ttl_seconds;
elcache_client_put(client, url, strlen(url), body, body_len, &opts);
}
// Get cached response
char* get_cached_response(elcache_client_t* client, const char* url) {
void* value;
size_t len;
elcache_error_t err = elcache_client_get_alloc(client,
url, strlen(url), &value, &len, NULL);
if (err == ELCACHE_OK) {
return (char*)value; // Caller must elcache_free()
}
return NULL;
}#include <elcache/elcache_sdk.h>
void stream_video(elcache_client_t* client, const char* video_id) {
elcache_read_stream_t* stream;
elcache_error_t err = elcache_client_read_stream_start(
client, video_id, strlen(video_id), NULL, &stream);
if (err != ELCACHE_OK) {
return;
}
uint64_t total = elcache_read_stream_total_size(stream);
printf("Streaming %lu bytes\n", total);
char buffer[1024 * 1024]; // 1MB chunks
size_t bytes_read;
while (!elcache_read_stream_eof(stream)) {
err = elcache_read_stream_read(stream, buffer, sizeof(buffer), &bytes_read);
if (err == ELCACHE_OK) {
// Send to video player...
send_to_player(buffer, bytes_read);
}
}
elcache_read_stream_close(stream);
}#include <elcache/elcache_sdk.h>
#include <pthread.h>
// Thread-local client
__thread elcache_client_t* tls_client = NULL;
elcache_client_t* get_client() {
if (!tls_client) {
tls_client = elcache_client_create();
elcache_client_connect_unix(tls_client, "/var/run/elcache/elcache.sock");
}
return tls_client;
}
void* worker_thread(void* arg) {
elcache_client_t* client = get_client();
// Use client safely in this thread...
return NULL;
}- Use shared memory transport when client and server are on the same host
- Reuse client instances - connection setup has overhead
- Use partial reads for large values when you only need a portion
- Batch operations by keeping the connection open
- Tune buffer sizes based on your typical value sizes
| Limit | Value |
|---|---|
| Maximum key size | 8 KB |
| Maximum value size | 20 TB |
| Default timeout | 30 seconds |
| Default buffer size | 256 KB |
| Maximum single PUT size | Buffer size (default 256 KB) |
| Sparse write chunk size | Buffer size (default 256 KB) |
Note: For values larger than the buffer size, use the position-based write API (create_sparse, write_range, finalize) which allows uploading in chunks.