- 
                Notifications
    You must be signed in to change notification settings 
- Fork 231
Description
Observed behavior
Calling:
await obj.get(key)is extremely slow w.r.t. the same call in Go, or even with respect to a put in Python.
Moreover, during the get call, the CPU consumption of the Python process goes to 100%.
Expected behavior
No performance problems should be present.
Server and client version
NATS server: 2.11.4
NATS Python client version: 2.10.0
Host environment
I tried with multiple environments: a MacOS machine with both the server and the client, a NATS server on K8s with Python code deployed in other pods, and another Ubuntu VM on a proprietary infrastructure with both the server and the client.
Steps to reproduce
You need a NATS Server with JetStream enabled.
Run the following Python code:
import asyncio
import os
import time
import hashlib
import nats
def generate_blob(size_mb=200):
    return os.urandom(size_mb * 1024 * 1024)
async def main():
    key = "test-200mb"
    nats_url = os.getenv("NATS_HOST", "nats://localhost:4222")
    # Connect to NATS
    nc = await nats.connect(servers=[nats_url])
    js = nc.jetstream()
    try:
        obj = await js.object_store("benchmark1-files")
    except nats.js.errors.BucketNotFoundError:
        obj = await js.create_object_store("benchmark1-files")
    # Generate data
    data = generate_blob()
    md5sum = hashlib.md5(data).hexdigest()
    # Upload
    start_put = time.time()
    await obj.put(key, data)
    elapsed_put = time.time() - start_put
    size_mb = len(data) / (1024 * 1024)
    speed_put = size_mb / elapsed_put
    print(f"⬆️  Uploaded key: {key}")
    print(f"Size:     {size_mb:.2f} MB")
    print(f"Duration: {elapsed_put:.2f} s")
    print(f"Speed:    {speed_put:.2f} MB/s")
    print(f"MD5:      {md5sum}")
    # Sleep before download
    await asyncio.sleep(3)
    # Download
    start_get = time.time()
    result = await obj.get(key)
    received = result.data
    elapsed_get = time.time() - start_get
    speed_get = len(received) / (1024 * 1024) / elapsed_get
    # Validate
    md5_check = hashlib.md5(received).hexdigest()
    ok = md5_check == md5sum
    print(f"\n⬇️  Downloaded key: {key}")
    print(f"Duration: {elapsed_get:.2f} s")
    print(f"Speed:    {speed_get:.2f} MB/s")
    print(f"MD5 OK:   {ok}")
if __name__ == "__main__":
    asyncio.run(main())You should observe that the download speed is extremely slow, I guess depending on the client CPU capabilities. Note that, during the processing, the CPU process of the Python process goes to 100%.
In contrast, run the following code with Go:
package main
import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"math/rand"
	"os"
	"time"
	"github.com/nats-io/nats.go"
)
func generateBlob(sizeMB int) []byte {
	size := sizeMB * 1024 * 1024
	buf := make([]byte, size)
	_, _ = rand.Read(buf)
	return buf
}
func main() {
	key := "test-200mb"
	natsURL := os.Getenv("NATS_HOST")
	if natsURL == "" {
		natsURL = "nats://localhost:4222"
	}
	// Connect
	nc, err := nats.Connect(natsURL)
	if err != nil {
		panic(err)
	}
	js, err := nc.JetStream()
	if err != nil {
		panic(err)
	}
	// Access object store
	obj, err := js.ObjectStore("benchmark1-files")
	if err != nil {
		panic(err)
	}
	// Generate data
	data := generateBlob(200)
	md5sum := md5.Sum(data)
	md5hex := hex.EncodeToString(md5sum[:])
	// Upload
	startPut := time.Now()
	_, err = obj.PutBytes(key, data)
	if err != nil {
		panic(err)
	}
	elapsedPut := time.Since(startPut).Seconds()
	speedPut := float64(len(data)) / (1024 * 1024) / elapsedPut
	fmt.Printf("⬆️  Uploaded key: %s\n", key)
	fmt.Printf("Size:     %.2f MB\n", float64(len(data))/(1024*1024))
	fmt.Printf("Duration: %.2f s\n", elapsedPut)
	fmt.Printf("Speed:    %.2f MB/s\n", speedPut)
	fmt.Printf("MD5:      %s\n", md5hex)
	// Sleep before download
	time.Sleep(3 * time.Second)
	// Download
	startGet := time.Now()
	received, err := obj.GetBytes(key)
	if err != nil {
		panic(err)
	}
	elapsedGet := time.Since(startGet).Seconds()
	speedGet := float64(len(received)) / (1024 * 1024) / elapsedGet
	// Validate
	md5Check := md5.Sum(received)
	ok := hex.EncodeToString(md5Check[:]) == md5hex
	fmt.Printf("\n⬇️  Downloaded key: %s\n", key)
	fmt.Printf("Duration: %.2f s\n", elapsedGet)
	fmt.Printf("Speed:    %.2f MB/s\n", speedGet)
	fmt.Printf("MD5 OK:   %v\n", ok)
}The speeds are much higher, with a particularly high difference in the download one.