From 094386721398f5bc533d507a4ddd24dc14017a61 Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Sat, 14 Feb 2026 10:38:40 -0500 Subject: [PATCH 1/3] add grpc proto definitions for ember.v1 service single service covering all command groups: strings, keys, lists, hashes, sets, sorted sets, vectors, and server commands. includes bidirectional Pipeline rpc for batch operations. key design decisions: - packed float vectors for zero-parse VADD/VSIM - versioned package (ember.v1) for future compatibility - shared response types matching ShardResponse semantics - go_package option for client codegen --- proto/ember/v1/ember.proto | 673 +++++++++++++++++++++++++++++++++++++ 1 file changed, 673 insertions(+) create mode 100644 proto/ember/v1/ember.proto diff --git a/proto/ember/v1/ember.proto b/proto/ember/v1/ember.proto new file mode 100644 index 00000000..640816dc --- /dev/null +++ b/proto/ember/v1/ember.proto @@ -0,0 +1,673 @@ +syntax = "proto3"; +package ember.v1; + +option go_package = "github.com/kacy/ember-go/proto/ember/v1;emberv1"; + +// EmberCache provides a gRPC interface to ember's key-value store. +// all commands route through the same engine as RESP3, so behavior +// is identical regardless of protocol. +service EmberCache { + // --- strings --- + + rpc Get(GetRequest) returns (GetResponse); + rpc Set(SetRequest) returns (SetResponse); + rpc Del(DelRequest) returns (DelResponse); + rpc MGet(MGetRequest) returns (MGetResponse); + rpc MSet(MSetRequest) returns (MSetResponse); + rpc Incr(IncrRequest) returns (IntResponse); + rpc IncrBy(IncrByRequest) returns (IntResponse); + rpc DecrBy(DecrByRequest) returns (IntResponse); + rpc IncrByFloat(IncrByFloatRequest) returns (FloatResponse); + rpc Append(AppendRequest) returns (IntResponse); + rpc Strlen(StrlenRequest) returns (IntResponse); + + // --- keys --- + + rpc Exists(ExistsRequest) returns (IntResponse); + rpc Expire(ExpireRequest) returns (BoolResponse); + rpc PExpire(PExpireRequest) returns (BoolResponse); + rpc Persist(PersistRequest) returns (BoolResponse); + rpc Ttl(TtlRequest) returns (TtlResponse); + rpc PTtl(PTtlRequest) returns (TtlResponse); + rpc Type(TypeRequest) returns (TypeResponse); + rpc Keys(KeysRequest) returns (KeysResponse); + rpc Rename(RenameRequest) returns (StatusResponse); + rpc Scan(ScanRequest) returns (ScanResponse); + + // --- lists --- + + rpc LPush(LPushRequest) returns (IntResponse); + rpc RPush(RPushRequest) returns (IntResponse); + rpc LPop(LPopRequest) returns (GetResponse); + rpc RPop(RPopRequest) returns (GetResponse); + rpc LRange(LRangeRequest) returns (ArrayResponse); + rpc LLen(LLenRequest) returns (IntResponse); + + // --- hashes --- + + rpc HSet(HSetRequest) returns (IntResponse); + rpc HGet(HGetRequest) returns (GetResponse); + rpc HGetAll(HGetAllRequest) returns (HashResponse); + rpc HDel(HDelRequest) returns (IntResponse); + rpc HExists(HExistsRequest) returns (BoolResponse); + rpc HLen(HLenRequest) returns (IntResponse); + rpc HIncrBy(HIncrByRequest) returns (IntResponse); + rpc HKeys(HKeysRequest) returns (KeysResponse); + rpc HVals(HValsRequest) returns (ArrayResponse); + rpc HMGet(HMGetRequest) returns (OptionalArrayResponse); + + // --- sets --- + + rpc SAdd(SAddRequest) returns (IntResponse); + rpc SRem(SRemRequest) returns (IntResponse); + rpc SMembers(SMembersRequest) returns (KeysResponse); + rpc SIsMember(SIsMemberRequest) returns (BoolResponse); + rpc SCard(SCardRequest) returns (IntResponse); + + // --- sorted sets --- + + rpc ZAdd(ZAddRequest) returns (IntResponse); + rpc ZRem(ZRemRequest) returns (IntResponse); + rpc ZScore(ZScoreRequest) returns (OptionalFloatResponse); + rpc ZRank(ZRankRequest) returns (OptionalIntResponse); + rpc ZCard(ZCardRequest) returns (IntResponse); + rpc ZRange(ZRangeRequest) returns (ZRangeResponse); + + // --- vectors --- + // only available when the server is built with the vector feature. + + rpc VAdd(VAddRequest) returns (BoolResponse); + rpc VSim(VSimRequest) returns (VSimResponse); + rpc VRem(VRemRequest) returns (BoolResponse); + rpc VGet(VGetRequest) returns (VGetResponse); + rpc VCard(VCardRequest) returns (IntResponse); + rpc VDim(VDimRequest) returns (IntResponse); + rpc VInfo(VInfoRequest) returns (VInfoResponse); + + // --- server --- + + rpc Ping(PingRequest) returns (PingResponse); + rpc FlushDb(FlushDbRequest) returns (StatusResponse); + rpc DbSize(DbSizeRequest) returns (IntResponse); + rpc Info(InfoRequest) returns (InfoResponse); + + // --- streaming --- + // bidirectional streaming for batch operations, matching RESP3 pipelining. + + rpc Pipeline(stream PipelineRequest) returns (stream PipelineResponse); +} + +// --------------------------------------------------------------------------- +// shared response types +// --------------------------------------------------------------------------- + +message IntResponse { + int64 value = 1; +} + +message BoolResponse { + bool value = 1; +} + +message FloatResponse { + string value = 1; +} + +message StatusResponse { + string status = 1; +} + +// --------------------------------------------------------------------------- +// strings +// --------------------------------------------------------------------------- + +message GetRequest { + string key = 1; +} + +message GetResponse { + optional bytes value = 1; +} + +message SetRequest { + string key = 1; + bytes value = 2; + // expire time in seconds. 0 means no expiration. + uint64 expire_seconds = 3; + // expire time in milliseconds. takes precedence over expire_seconds. + uint64 expire_millis = 4; + // NX: only set if key does not exist. + bool nx = 5; + // XX: only set if key already exists. + bool xx = 6; +} + +message SetResponse { + // true if the key was set, false if NX/XX condition prevented it. + bool ok = 1; +} + +message DelRequest { + repeated string keys = 1; +} + +message DelResponse { + int64 deleted = 1; +} + +message MGetRequest { + repeated string keys = 1; +} + +message MGetResponse { + // one entry per requested key. missing keys have value unset. + repeated OptionalValue values = 1; +} + +message OptionalValue { + optional bytes value = 1; +} + +message MSetRequest { + repeated KeyValue pairs = 1; +} + +message KeyValue { + string key = 1; + bytes value = 2; +} + +message MSetResponse {} + +message IncrRequest { + string key = 1; +} + +message IncrByRequest { + string key = 1; + int64 delta = 2; +} + +message DecrByRequest { + string key = 1; + int64 delta = 2; +} + +message IncrByFloatRequest { + string key = 1; + double delta = 2; +} + +message AppendRequest { + string key = 1; + bytes value = 2; +} + +message StrlenRequest { + string key = 1; +} + +// --------------------------------------------------------------------------- +// keys +// --------------------------------------------------------------------------- + +message ExistsRequest { + repeated string keys = 1; +} + +message ExpireRequest { + string key = 1; + uint64 seconds = 2; +} + +message PExpireRequest { + string key = 1; + uint64 milliseconds = 2; +} + +message PersistRequest { + string key = 1; +} + +message TtlRequest { + string key = 1; +} + +message PTtlRequest { + string key = 1; +} + +message TtlResponse { + // -2 = key does not exist, -1 = no expiry, >= 0 = remaining time. + int64 value = 1; +} + +message TypeRequest { + string key = 1; +} + +message TypeResponse { + string type_name = 1; +} + +message KeysRequest { + string pattern = 1; +} + +message KeysResponse { + repeated string keys = 1; +} + +message RenameRequest { + string key = 1; + string new_key = 2; +} + +message ScanRequest { + uint64 cursor = 1; + uint32 count = 2; + optional string pattern = 3; +} + +message ScanResponse { + uint64 cursor = 1; + repeated string keys = 2; +} + +// --------------------------------------------------------------------------- +// lists +// --------------------------------------------------------------------------- + +message LPushRequest { + string key = 1; + repeated bytes values = 2; +} + +message RPushRequest { + string key = 1; + repeated bytes values = 2; +} + +message LPopRequest { + string key = 1; +} + +message RPopRequest { + string key = 1; +} + +message LRangeRequest { + string key = 1; + int64 start = 2; + int64 stop = 3; +} + +message ArrayResponse { + repeated bytes values = 1; +} + +message LLenRequest { + string key = 1; +} + +// --------------------------------------------------------------------------- +// hashes +// --------------------------------------------------------------------------- + +message HSetRequest { + string key = 1; + repeated FieldValue fields = 2; +} + +message FieldValue { + string field = 1; + bytes value = 2; +} + +message HGetRequest { + string key = 1; + string field = 2; +} + +message HGetAllRequest { + string key = 1; +} + +message HashResponse { + repeated FieldValue fields = 1; +} + +message HDelRequest { + string key = 1; + repeated string fields = 2; +} + +message HExistsRequest { + string key = 1; + string field = 2; +} + +message HLenRequest { + string key = 1; +} + +message HIncrByRequest { + string key = 1; + string field = 2; + int64 delta = 3; +} + +message HKeysRequest { + string key = 1; +} + +message HValsRequest { + string key = 1; +} + +message HMGetRequest { + string key = 1; + repeated string fields = 2; +} + +message OptionalArrayResponse { + repeated OptionalValue values = 1; +} + +// --------------------------------------------------------------------------- +// sets +// --------------------------------------------------------------------------- + +message SAddRequest { + string key = 1; + repeated string members = 2; +} + +message SRemRequest { + string key = 1; + repeated string members = 2; +} + +message SMembersRequest { + string key = 1; +} + +message SIsMemberRequest { + string key = 1; + string member = 2; +} + +message SCardRequest { + string key = 1; +} + +// --------------------------------------------------------------------------- +// sorted sets +// --------------------------------------------------------------------------- + +message ZAddRequest { + string key = 1; + repeated ScoreMember members = 2; + bool nx = 3; + bool xx = 4; + bool gt = 5; + bool lt = 6; + bool ch = 7; +} + +message ScoreMember { + double score = 1; + string member = 2; +} + +message ZRemRequest { + string key = 1; + repeated string members = 2; +} + +message ZScoreRequest { + string key = 1; + string member = 2; +} + +message OptionalFloatResponse { + optional double value = 1; +} + +message ZRankRequest { + string key = 1; + string member = 2; +} + +message OptionalIntResponse { + optional int64 value = 1; +} + +message ZCardRequest { + string key = 1; +} + +message ZRangeRequest { + string key = 1; + int64 start = 2; + int64 stop = 3; + bool with_scores = 4; +} + +message ZRangeResponse { + // when with_scores is false, only member is populated. + repeated ScoreMember members = 1; +} + +// --------------------------------------------------------------------------- +// vectors +// --------------------------------------------------------------------------- + +enum VectorMetric { + VECTOR_METRIC_COSINE = 0; + VECTOR_METRIC_EUCLIDEAN = 1; + VECTOR_METRIC_INNER_PRODUCT = 2; +} + +enum VectorQuantization { + VECTOR_QUANTIZATION_NONE = 0; + VECTOR_QUANTIZATION_F16 = 1; + VECTOR_QUANTIZATION_I8 = 2; +} + +message VAddRequest { + string key = 1; + string element = 2; + // packed IEEE 754 floats — no parsing overhead. + repeated float vector = 3 [packed = true]; + VectorMetric metric = 4; + VectorQuantization quantization = 5; + optional uint32 connectivity = 6; + optional uint32 ef_construction = 7; +} + +message VSimRequest { + string key = 1; + repeated float query = 2 [packed = true]; + uint32 count = 3; + optional uint32 ef_search = 4; +} + +message VSimResponse { + repeated VSimResult results = 1; +} + +message VSimResult { + string element = 1; + float distance = 2; +} + +message VRemRequest { + string key = 1; + string element = 2; +} + +message VGetRequest { + string key = 1; + string element = 2; +} + +message VGetResponse { + optional bool exists = 1; + repeated float vector = 2 [packed = true]; +} + +message VCardRequest { + string key = 1; +} + +message VDimRequest { + string key = 1; +} + +message VInfoRequest { + string key = 1; +} + +message VInfoResponse { + bool exists = 1; + repeated FieldValue info = 2; +} + +// --------------------------------------------------------------------------- +// server +// --------------------------------------------------------------------------- + +message PingRequest { + optional string message = 1; +} + +message PingResponse { + string message = 1; +} + +message FlushDbRequest { + bool async = 1; +} + +message DbSizeRequest {} + +message InfoRequest { + optional string section = 1; +} + +message InfoResponse { + string info = 1; +} + +// --------------------------------------------------------------------------- +// pipeline (bidirectional streaming) +// --------------------------------------------------------------------------- + +message PipelineRequest { + uint64 id = 1; + oneof command { + GetRequest get = 2; + SetRequest set = 3; + DelRequest del = 4; + ExistsRequest exists = 5; + IncrRequest incr = 6; + IncrByRequest incr_by = 7; + DecrByRequest decr_by = 8; + IncrByFloatRequest incr_by_float = 9; + AppendRequest append = 10; + StrlenRequest strlen = 11; + ExpireRequest expire = 12; + PExpireRequest pexpire = 13; + PersistRequest persist = 14; + TtlRequest ttl = 15; + PTtlRequest pttl = 16; + TypeRequest type = 17; + LPushRequest lpush = 18; + RPushRequest rpush = 19; + LPopRequest lpop = 20; + RPopRequest rpop = 21; + LRangeRequest lrange = 22; + LLenRequest llen = 23; + HSetRequest hset = 24; + HGetRequest hget = 25; + HGetAllRequest hgetall = 26; + HDelRequest hdel = 27; + HExistsRequest hexists = 28; + HLenRequest hlen = 29; + HIncrByRequest hincr_by = 30; + HKeysRequest hkeys = 31; + HValsRequest hvals = 32; + HMGetRequest hmget = 33; + SAddRequest sadd = 34; + SRemRequest srem = 35; + SMembersRequest smembers = 36; + SIsMemberRequest sismember = 37; + SCardRequest scard = 38; + ZAddRequest zadd = 39; + ZRemRequest zrem = 40; + ZScoreRequest zscore = 41; + ZRankRequest zrank = 42; + ZCardRequest zcard = 43; + ZRangeRequest zrange = 44; + VAddRequest vadd = 45; + VSimRequest vsim = 46; + VRemRequest vrem = 47; + VGetRequest vget = 48; + VCardRequest vcard = 49; + VDimRequest vdim = 50; + VInfoRequest vinfo = 51; + PingRequest ping = 52; + FlushDbRequest flushdb = 53; + DbSizeRequest dbsize = 54; + MGetRequest mget = 55; + MSetRequest mset = 56; + KeysRequest keys = 57; + RenameRequest rename = 58; + ScanRequest scan = 59; + } +} + +message PipelineResponse { + uint64 id = 1; + oneof result { + GetResponse get = 2; + SetResponse set = 3; + DelResponse del = 4; + IntResponse int_val = 5; + BoolResponse bool_val = 6; + FloatResponse float_val = 7; + StatusResponse status = 8; + TtlResponse ttl = 9; + TypeResponse type = 10; + ArrayResponse array = 11; + HashResponse hash = 12; + OptionalArrayResponse optional_array = 13; + KeysResponse keys = 14; + ScanResponse scan = 15; + OptionalFloatResponse optional_float = 16; + OptionalIntResponse optional_int = 17; + ZRangeResponse zrange = 18; + VSimResponse vsim = 19; + VGetResponse vget = 20; + VInfoResponse vinfo = 21; + MGetResponse mget = 22; + MSetResponse mset = 23; + PingResponse ping = 24; + ErrorResponse error = 25; + InfoResponse info = 26; + } +} + +message ErrorResponse { + string message = 1; + ErrorKind kind = 2; +} + +enum ErrorKind { + ERROR_KIND_UNSPECIFIED = 0; + ERROR_KIND_WRONG_TYPE = 1; + ERROR_KIND_OUT_OF_MEMORY = 2; + ERROR_KIND_INTERNAL = 3; + ERROR_KIND_INVALID_ARGUMENT = 4; +} From f63bf8f967b9fbdfb3f1d7930011aaf6194b404f Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Sat, 14 Feb 2026 10:38:46 -0500 Subject: [PATCH 2/3] add tonic/prost deps and grpc feature flag workspace dependencies: tonic 0.13, prost 0.13, tonic-build 0.13. ember-server gets a new `grpc` feature (not in defaults) that pulls in tonic, prost, and tonic-build. build.rs runs tonic codegen when the feature is enabled. --- Cargo.lock | 269 ++++++++++++++++++++++++++++++++- Cargo.toml | 5 + crates/ember-server/Cargo.toml | 8 + crates/ember-server/build.rs | 10 ++ 4 files changed, 286 insertions(+), 6 deletions(-) create mode 100644 crates/ember-server/build.rs diff --git a/Cargo.lock b/Cargo.lock index 372c06be..963c37ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,17 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.115", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -190,6 +201,49 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "base64" version = "0.22.1" @@ -770,6 +824,7 @@ dependencies = [ "futures", "metrics", "metrics-exporter-prometheus", + "prost 0.13.5", "rustls", "rustls-pki-types", "subtle", @@ -777,6 +832,8 @@ dependencies = [ "tikv-jemallocator", "tokio", "tokio-rustls", + "tonic", + "tonic-build", "tracing", "tracing-subscriber", ] @@ -873,6 +930,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "fnv" version = "1.0.7" @@ -1214,6 +1277,19 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -1228,7 +1304,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2", + "socket2 0.6.2", "tokio", "tower-service", "tracing", @@ -1425,6 +1501,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.8.0" @@ -1478,6 +1560,12 @@ dependencies = [ "sketches-ddsketch", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "mio" version = "1.1.1" @@ -1489,6 +1577,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1632,6 +1726,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -1747,6 +1857,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive 0.13.5", +] + [[package]] name = "prost" version = "0.14.3" @@ -1754,7 +1874,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.14.3", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.5", + "prost-types 0.13.5", + "regex", + "syn 2.0.115", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.115", ] [[package]] @@ -1776,8 +1929,17 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89455ef41ed200cafc47c76c552ee7792370ac420497e551f16123a9135f76e" dependencies = [ - "prost", - "prost-types", + "prost 0.14.3", + "prost-types 0.14.3", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost 0.13.5", ] [[package]] @@ -1786,7 +1948,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ - "prost", + "prost 0.14.3", ] [[package]] @@ -2347,6 +2509,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.2" @@ -2391,6 +2563,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "tap" version = "1.0.1" @@ -2525,7 +2703,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -2551,6 +2729,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -2594,6 +2783,74 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.5", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac6f67be712d12f0b41328db3137e0d0757645d8904b4cb7d51cd9c2279e847" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types 0.13.5", + "quote", + "syn 2.0.115", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index 7cfb312a..ef979bce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,11 @@ prost-reflect = "0.16" # HNSW vector similarity search usearch = "2.23" +# grpc +tonic = "0.13" +prost = "0.13" +tonic-build = "0.13" + # internal crates (version required for crates.io publishing) emberkv-core = { version = "0.4.3", path = "crates/ember-core" } ember-protocol = { version = "0.4.3", path = "crates/ember-protocol" } diff --git a/crates/ember-server/Cargo.toml b/crates/ember-server/Cargo.toml index d893e2f0..7ab73625 100644 --- a/crates/ember-server/Cargo.toml +++ b/crates/ember-server/Cargo.toml @@ -15,6 +15,7 @@ jemalloc = ["tikv-jemallocator"] encryption = ["emberkv-core/encryption", "ember-persistence/encryption"] protobuf = ["emberkv-core/protobuf", "ember-persistence/protobuf"] vector = ["emberkv-core/vector", "ember-persistence/vector"] +grpc = ["dep:tonic", "dep:prost", "dep:tonic-build"] [dependencies] bytes = { workspace = true } @@ -38,5 +39,12 @@ tokio-rustls = { workspace = true } rustls = { workspace = true } rustls-pki-types = { workspace = true } +# grpc (optional) +tonic = { workspace = true, optional = true } +prost = { workspace = true, optional = true } + # optional: better multi-threaded allocation performance tikv-jemallocator = { version = "0.6", optional = true } + +[build-dependencies] +tonic-build = { workspace = true, optional = true } diff --git a/crates/ember-server/build.rs b/crates/ember-server/build.rs new file mode 100644 index 00000000..9909371c --- /dev/null +++ b/crates/ember-server/build.rs @@ -0,0 +1,10 @@ +fn main() -> Result<(), Box> { + #[cfg(feature = "grpc")] + { + tonic_build::configure() + .build_server(true) + .build_client(false) + .compile_protos(&["../../proto/ember/v1/ember.proto"], &["../../proto"])?; + } + Ok(()) +} From ad5c4a22b26cacbc3221888b4aa2691a4eada21d Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Sat, 14 Feb 2026 10:38:52 -0500 Subject: [PATCH 3/3] update build infra for grpc support - makefile: add proto-gen target, include grpc in test/clippy features - ci: install protoc, enable grpc feature in check/test jobs - dockerfile: add protobuf-compiler, build with grpc, expose port 6380 --- .github/workflows/ci.yml | 16 ++++++++++++---- Dockerfile | 4 +++- Makefile | 11 ++++++++--- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c7958cc..dfd6d778 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,12 +20,16 @@ jobs: with: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 + - uses: arduino/setup-protoc@v3 + with: + version: '28.x' + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: fmt run: cargo fmt --all --check - name: clippy - run: cargo clippy --workspace --features protobuf -- -D warnings + run: cargo clippy --workspace --features protobuf,grpc -- -D warnings - name: check - run: cargo check --workspace --features protobuf + run: cargo check --workspace --features protobuf,grpc test: name: test @@ -37,10 +41,14 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 + - uses: arduino/setup-protoc@v3 + with: + version: '28.x' + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: build - run: cargo build --workspace --features protobuf + run: cargo build --workspace --features protobuf,grpc - name: test - run: cargo test --workspace --features protobuf + run: cargo test --workspace --features protobuf,grpc build: name: build diff --git a/Dockerfile b/Dockerfile index b52a4ae8..853c2544 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pkg-config \ libssl-dev \ make \ + protobuf-compiler \ && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/ember COPY . . -RUN cargo build --release --bin ember-server +RUN cargo build --release --bin ember-server --features grpc # --- @@ -33,6 +34,7 @@ USER ember ENV EMBER_HOST=0.0.0.0 EXPOSE 6379 +EXPOSE 6380 EXPOSE 9100 LABEL org.opencontainers.image.title="ember" \ diff --git a/Makefile b/Makefile index 40b210b7..5864f161 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: build release test fmt fmt-check clippy check clean docker-build docker-run \ release-patch release-minor release-major github-release \ publish publish-dry-run bench bench-core bench-protocol bench-compare bench-quick \ - helm-lint helm-template + helm-lint helm-template proto-gen # extract the workspace version from the root Cargo.toml VERSION = $(shell sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml) @@ -13,7 +13,7 @@ release: cargo build --release test: - cargo test --workspace --features protobuf + cargo test --workspace --features protobuf,grpc fmt: cargo fmt --all @@ -22,7 +22,7 @@ fmt-check: cargo fmt --all --check clippy: - cargo clippy --workspace --features protobuf -- -D warnings + cargo clippy --workspace --features protobuf,grpc -- -D warnings check: fmt-check clippy test @@ -130,6 +130,11 @@ publish: @echo "" @echo "all crates published successfully" +# --- proto --- + +proto-gen: + cargo build -p ember-server --features grpc + # --- helm --- helm-lint: