Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bea12c2
Remove options for using high performance Codec in C++ client
pjdotson Apr 27, 2026
d757ad6
Update GRPC Framer Core code
pjdotson Apr 27, 2026
6152cbf
Update error returning
pjdotson Apr 27, 2026
b7e9524
Update all Go code
pjdotson Apr 27, 2026
f3ca2b9
Remove options of using codec in client
pjdotson Apr 27, 2026
caeb8b0
Fix all bugs
pjdotson Apr 27, 2026
eaa225b
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson Apr 28, 2026
b9fef97
Fix circular dependency and linting
pjdotson Apr 28, 2026
d2a85e8
Address comments
pjdotson Apr 28, 2026
55aa5f7
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson Apr 28, 2026
8d206a2
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson Apr 28, 2026
57c4baf
Address some PR comments
pjdotson Apr 29, 2026
462583c
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson Apr 30, 2026
10ff171
Fix and format TS files
pjdotson Apr 30, 2026
6cd761e
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson Apr 30, 2026
ce59019
Fix TS client linting issue
pjdotson Apr 30, 2026
53d732b
Address Greptile comment
pjdotson Apr 30, 2026
b465420
Add other bench test
pjdotson May 4, 2026
8b2f3e4
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson May 4, 2026
26c41c9
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson May 5, 2026
9b4cb72
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson May 6, 2026
5d7369b
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson May 6, 2026
847e335
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson May 6, 2026
89d8f43
Add benchmarks for gRPC and http
pjdotson May 6, 2026
83c9c22
Update for keys going out of sync
pjdotson May 6, 2026
fabe8a2
Add test for when frame is not initialized
pjdotson May 6, 2026
679e2d7
Remove needless comment
pjdotson May 6, 2026
ff086ff
Add seq num check
pjdotson May 6, 2026
57fbb69
Fix formatting
pjdotson May 6, 2026
a303af0
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson May 7, 2026
3501a70
Merge branch 'rc' into sy-3556-use-high-performance-codecs-for-iterators
pjdotson May 7, 2026
3184b35
Add nil guard on writer
pjdotson May 7, 2026
354fb58
Fix formatting
pjdotson May 7, 2026
7ad6f6e
Fix formatting
pjdotson May 7, 2026
e237f1a
Remove unused receivers
pjdotson May 7, 2026
faf63ee
Remove dead field
pjdotson May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 2 additions & 22 deletions client/cpp/framer/framer.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,6 @@ class StreamerConfig {
std::vector<channel::Key> channels;
/// @brief the downsample factor for the streamer.
int downsample_factor = 1;
/// @brief enable experimental high-performance codec for the writer.
bool enable_experimental_codec = true;
/// @brief writer group IDs whose frames should be filtered out by the server. Used
/// for telemetry bypass deduplication.
std::vector<std::uint32_t> exclude_groups;
Expand Down Expand Up @@ -239,8 +237,7 @@ class Streamer {

StreamerConfig cfg;

/// @brief custom framing codec. only used when cfg.enable_experimental_codec is
/// set to true.
/// @brief custom framing codec.
Codec codec;

/// @brief throws if methods have been called on the streamer before it is open.
Expand Down Expand Up @@ -312,20 +309,6 @@ struct WriterConfig {
/// Defaults to 1s when auto-commit is enabled.
x::telem::TimeSpan auto_index_persist_interval = 1 * x::telem::SECOND;

/// @brief enable protobuf frame caching for the writer. This allows
/// the writer to avoid repeated allocation and deallocation of protobuf frames,
/// releasing significant heap pressure.
///
/// @details IMPORTANT: This option should only be used for writers that write
/// a frame with the EXACT same dimensions on every write i.e. same number of
/// channels and series in the same order. Each series must have the same data
/// type and the same number of samples. BEHAVIOR IS UNDEFINED IF YOU DO NOT
/// FOLLOW THIS RULE.
bool enable_proto_frame_caching = false;

/// @brief enable experimental high-performance codec for the writer.
bool enable_experimental_codec = true;

private:
/// @brief binds the configuration fields to it's protobuf representation.
[[nodiscard]] x::errors::Error to_proto(grpc::framer::WriterConfig *f) const;
Expand Down Expand Up @@ -416,8 +399,7 @@ class Writer {
/// @brief the configuration used to open the writer.
WriterConfig cfg;

/// @brief the custom synnax frame codec for encoding/decoding frames. This codec
/// is only used when cfg.enable_experimental_codec is true.
/// @brief the custom synnax frame codec for encoding/decoding frames.
Codec codec;
/// @brief the data buffer for storing encoded frames.
std::vector<std::uint8_t> codec_data;
Expand All @@ -427,8 +409,6 @@ class Writer {

/// @brief cached request for reuse during writes
std::unique_ptr<grpc::framer::WriterRequest> cached_write_req;
/// @brief cached frame within the request for reuse
::x::telem::pb::Frame *cached_frame = nullptr;

/// @brief internal function that waits until an ack is received for a
/// particular command.
Expand Down
9 changes: 3 additions & 6 deletions client/cpp/framer/streamer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace synnax::framer {
void StreamerConfig::to_proto(grpc::framer::StreamerRequest &f) const {
f.mutable_keys()->Add(channels.begin(), channels.end());
f.set_downsample_factor(downsample_factor);
f.set_enable_experimental_codec(enable_experimental_codec);
f.mutable_exclude_groups()->Add(
this->exclude_groups.begin(),
this->exclude_groups.end()
Expand All @@ -31,11 +30,9 @@ Client::open_streamer(const StreamerConfig &config) const {
if (!net_stream->send(req).ok()) net_stream->close_send();
auto [_, res_err] = net_stream->receive();
auto streamer = Streamer(std::move(net_stream), config);
if (config.enable_experimental_codec) {
streamer.codec = Codec(this->channel_client);
if (const auto codec_err = streamer.codec.update(config.channels))
return {Streamer(), codec_err};
}
streamer.codec = Codec(this->channel_client);
if (const auto codec_err = streamer.codec.update(config.channels))
return {Streamer(), codec_err};
return {std::move(streamer), res_err};
}

Expand Down
38 changes: 9 additions & 29 deletions client/cpp/framer/writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,8 @@ enum WriterCommand : uint32_t {
};

std::pair<Writer, x::errors::Error> Client::open_writer(const WriterConfig &cfg) const {
Codec codec;
if (cfg.enable_experimental_codec) {
codec = Codec(this->channel_client);
if (const auto codec_err = codec.update(cfg.channels))
return {Writer(), codec_err};
}
Codec codec(this->channel_client);
if (const auto codec_err = codec.update(cfg.channels)) return {Writer(), codec_err};
auto [net_writer, err] = this->writer_client->stream("/frame/write");
if (err) return {Writer(), err};
grpc::framer::WriterRequest req;
Expand Down Expand Up @@ -99,30 +95,14 @@ x::errors::Error Writer::close() {
}

x::errors::Error Writer::init_request(const x::telem::Frame &fr) {
if (this->cfg.enable_experimental_codec) {
if (this->cached_write_req == nullptr)
this->cached_write_req = std::make_unique<grpc::framer::WriterRequest>();
this->cached_write_req->set_command(WRITE);
if (const auto err = this->codec.encode(fr, this->codec_data)) return err;
this->cached_write_req->set_buffer(
this->codec_data.data(),
this->codec_data.size()
);
return x::errors::NIL;
}

if (this->cached_write_req != nullptr && this->cfg.enable_proto_frame_caching) {
for (size_t i = 0; i < fr.series->size(); i++)
*cached_frame->mutable_series(
static_cast<int>(i)
) = fr.series->at(i).to_proto();
return x::errors::NIL;
}
this->cached_write_req = nullptr;
this->cached_write_req = std::make_unique<grpc::framer::WriterRequest>();
if (this->cached_write_req == nullptr)
this->cached_write_req = std::make_unique<grpc::framer::WriterRequest>();
this->cached_write_req->set_command(WRITE);
this->cached_frame = cached_write_req->mutable_frame();
*cached_frame = fr.to_proto();
if (const auto err = this->codec.encode(fr, this->codec_data)) return err;
this->cached_write_req->set_buffer(
this->codec_data.data(),
this->codec_data.size()
);
return x::errors::NIL;
}

Expand Down
4 changes: 0 additions & 4 deletions client/py/synnax/framer/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def open_writer(
enable_auto_commit: bool = True,
auto_index_persist_interval: TimeSpan = 1 * TimeSpan.SECOND,
err_on_extra_chans: bool = True,
use_experimental_codec: bool = True,
) -> Writer:
"""Opens a new writer on the given channels.

Expand Down Expand Up @@ -128,7 +127,6 @@ def open_writer(
err_on_unauthorized=err_on_unauthorized,
enable_auto_commit=enable_auto_commit,
auto_index_persist_interval=auto_index_persist_interval,
use_experimental_codec=use_experimental_codec,
)

def open_iterator(
Expand Down Expand Up @@ -302,7 +300,6 @@ def open_streamer(
channels: channel.Params,
downsample_factor: int = 1,
throttle_rate: float = 0,
use_experimental_codec: bool = True,
exclude_groups: list[int] | None = None,
) -> Streamer:
"""Opens a new streamer on the given channels. The streamer will immediately
Expand All @@ -323,7 +320,6 @@ def open_streamer(
client=self.__stream_client,
downsample_factor=downsample_factor,
throttle_rate=throttle_rate,
use_experimental_codec=use_experimental_codec,
exclude_groups=exclude_groups,
)

Expand Down
34 changes: 25 additions & 9 deletions client/py/synnax/framer/codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,24 +268,24 @@ def decode(self, data: bytes, offset: int = 0) -> FramePayload:
keys = list()
series_list = list()

for key in state.keys:
if not flags.all_channels_present:
if idx >= len(buffer):
break
frame_key = struct.unpack_from("<I", buffer, idx)[0]
if frame_key != key:
continue
idx += KEY_SIZE
data_type = state.data_types[key]
def decode_series(key: channel.Key) -> bool:
nonlocal idx
data_type = state.data_types.get(key)
if data_type is None:
return False
curr_len = data_len
if not flags.eq_len:
if idx + DATA_LENGTH_SIZE > len(buffer):
return False
curr_len = struct.unpack_from("<I", buffer, idx)[0]
idx += DATA_LENGTH_SIZE

data_byte_len = curr_len
if not data_type.is_variable:
data_byte_len = curr_len * data_type.density

if idx + data_byte_len > len(buffer):
return False
series_data = bytes(buffer[idx : idx + data_byte_len])
idx += data_byte_len

Expand All @@ -294,12 +294,16 @@ def decode(self, data: bytes, offset: int = 0) -> FramePayload:
elif flags.eq_tr:
tr = TimeRange(start=start_time, end=end_time)
else:
if idx + TIME_RANGE_SIZE > len(buffer):
return False
s, e = struct.unpack_from("<QQ", buffer, idx)
tr = TimeRange(start=s, end=e)
idx += TIME_RANGE_SIZE

curr_alignment = alignment
if not flags.eq_align and not flags.zero_alignments:
if idx + ALIGNMENT_SIZE > len(buffer):
return False
curr_alignment = Alignment(struct.unpack_from("<Q", buffer, idx)[0])
idx += ALIGNMENT_SIZE

Expand All @@ -312,6 +316,18 @@ def decode(self, data: bytes, offset: int = 0) -> FramePayload:
alignment=curr_alignment,
)
)
return True

if flags.all_channels_present:
for key in state.keys:
if not decode_series(key):
break
else:
while idx + KEY_SIZE <= len(buffer):
frame_key = struct.unpack_from("<I", buffer, idx)[0]
idx += KEY_SIZE
if not decode_series(frame_key):
break

return FramePayload(keys=keys, series=series_list)

Expand Down
58 changes: 45 additions & 13 deletions client/py/synnax/framer/iterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
from __future__ import annotations

from enum import Enum
from typing import cast

from pydantic import BaseModel

import synnax.channel.payload as channel
from alamos import NOOP, Instrumentation
from freighter import EOF, Stream, StreamClient
from freighter import EOF, ExceptionPayload, Stream, WebsocketClient
from freighter.transport import P
from freighter.websocket import Message
from synnax.exceptions import UnexpectedError
from synnax.framer.adapter import ReadFrameAdapter
from synnax.framer.codec import LOW_PERF_SPECIAL_CHAR, WSFramerCodec
from synnax.framer.frame import Frame, FramePayload
from synnax.telem import TimeRange, TimeSpan, TimeStamp

Expand Down Expand Up @@ -56,10 +60,33 @@ class _Response(BaseModel):
variant: _ResponseVariant
command: _Command
ack: bool
error: str | None
error: ExceptionPayload | None
frame: FramePayload


class WSIteratorCodec(WSFramerCodec):
def encode(self, data: BaseModel) -> bytes:
return self.lower_perf_codec.encode(data)

def decode(self, data: bytes, pld_t: type[P]) -> P:
if data[0] == LOW_PERF_SPECIAL_CHAR:
return self.lower_perf_codec.decode(data[1:], pld_t)
frame = self.codec.decode(data, 1)
return cast(
P,
Message(
type="data",
payload=_Response(
variant=_ResponseVariant.DATA,
command=_Command.OPEN,
ack=False,
error=None,
frame=frame,
),
),
)
Comment thread
pjdotson marked this conversation as resolved.


class Iterator:
"""Used to iterate over a databases telemetry in time-order. It should not be
instantiated directly, and should instead be instantiated using the segment Client.
Expand All @@ -82,7 +109,7 @@ class Iterator:
def __init__(
self,
tr: TimeRange,
client: StreamClient,
client: WebsocketClient,
adapter: ReadFrameAdapter,
chunk_size: int = 100000,
downsample_factor: int = 1,
Expand All @@ -91,6 +118,7 @@ def __init__(
self.tr = tr
self.instrumentation = instrumentation
self.__adapter = adapter
client = client.with_codec(WSIteratorCodec(self.__adapter.codec))
self.__stream = client.stream("/frame/iterate", _Request, _Response)
self._chunk_size = chunk_size
self._downsample_factor = downsample_factor
Expand Down Expand Up @@ -193,16 +221,20 @@ def close(self) -> None:
exc = self.__stream.close_send()
if exc is not None:
raise exc
r, exc = self.__stream.receive()
if exc is None:
raise UnexpectedError(
f"""Unexpected missing close acknowledgement from server.
Please report this issue to the Synnax team.
Response: {r}
"""
)
elif not isinstance(exc, EOF):
raise exc
while True:
r, exc = self.__stream.receive()
if r is not None:
continue
if exc is None:
raise UnexpectedError(
f"""Unexpected missing close acknowledgement from server.
Please report this issue to the Synnax team.
Response: {r}
"""
)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
if not isinstance(exc, EOF):
raise exc
break

def __iter__(self) -> Iterator:
self.seek_first()
Expand Down
5 changes: 1 addition & 4 deletions client/py/synnax/framer/streamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,10 @@ def __init__(
adapter: ReadFrameAdapter,
downsample_factor: int = 1,
throttle_rate: float = 0,
use_experimental_codec: bool = True,
exclude_groups: list[int] | None = None,
) -> None:
self._adapter = adapter
if use_experimental_codec:
client = client.with_codec(WSStreamerCodec(self._adapter.codec))

client = client.with_codec(WSStreamerCodec(self._adapter.codec))
self._stream = client.stream(_ENDPOINT, _Request, _Response)
self._downsample_factor = downsample_factor
self._throttle_rate = throttle_rate
Expand Down
4 changes: 1 addition & 3 deletions client/py/synnax/framer/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,11 @@ def __init__(
err_on_unauthorized: bool = False,
enable_auto_commit: bool = True,
auto_index_persist_interval: TimeSpan = 1 * TimeSpan.SECOND,
use_experimental_codec: bool = True,
group: int = 0,
) -> None:
self.start = start
self._adapter = adapter
if use_experimental_codec:
client = client.with_codec(WSWriterCodec(adapter.codec))
client = client.with_codec(WSWriterCodec(adapter.codec))
self._stream = client.stream("/frame/write", WriterRequest, WriterResponse)
config = WriterConfig(
control_subject=Subject(name=name, key=str(uuid4()), group=group),
Expand Down
Loading
Loading