ydb-go-sdk
ydb-go-sdk copied to clipboard
dev: table.Session.Execute does X3 allocations for every string(or bytes) byte in result
Every byte loaded from YDB via query gets allocated in heap three time. For example if client reads from YDB 2 GiB/sec of bytes, the go runtime will have to allocate 6 GiB/sec of memory. Which adds additional pressure on GC.
These allocations are:
- GRPC allocation for response message (https://github.com/grpc/grpc-go/blob/v1.63.x/rpc_util.go#L630, they are moving buffer around rn, so I use specific tag here for clarity). Basically to read every response GRPC allocates slice of bytes. It supports pooling, but by default the pool is noop, it simply allocates new slice every time.
- Proto unmarshalling of
Ydb_Table.ExecuteDataQueryResponse
. Protobuf doesn't use zero-copy in standard implementation. So it is allocates new byte[] for every bytes field and copy values there. - Proto unmarshalling of
Ydb_Table.ExecuteDataQueryResponse.Result
intoYdb_Table.ExecuteQueryResult
. Resul hasAny
type, which is essentially a byte array with type name attached, so it undergoes one more unmarshalling.
The possible improvements. For (1) it is pretty easy to allow sdk users to enable grpc pool. It is experimental though (https://github.com/grpc/grpc-go/blob/v1.63.x/experimental/experimental.go#L46). And right now it doesn't work without additional hacks, because pool can't be used when stats.Handler is used. And sdk uses ststs.Handler internally. (Maybe this gets fixed in grpc code itself)
For (3) the easiest solution would be to stop using Any for query response. And instead switching to something like oneof to specialize more common/important response types. Otherwise (2) and (3) can be resolved only by switching from standard proto unmarshalling to something custom, to have zero copy. But probably resolving just (1) and (3) can be enough.