When Slow Clients Become Your Bottleneck (ClientRead Waits Explained)

When diagnosing performance issues in YugabyteDB, it’s easy to focus on:

  • ● Query plans
  • ● Tablet distribution
  • ● RPC latency

But sometimes (or most of the time πŸ˜‰)… the database isn’t the problem at all.

πŸ‘‰ The bottleneck can be the client itself.

In this tip, we’ll simulate a slow client ingesting data via COPY, and show how it surfaces in yb_active_session_history as ClientRead waits.

πŸ’‘ What does ClientRead mean?
When you see ClientRead, YugabyteDB is waiting on the client to send more data.
βœ… The database is ready
❌ The client is slow
This is not a database performance issue… it’s a client-side throughput or behavior problem.

πŸ§ͺ Demo: Simulating a Slow Client with COPY

Here’s a Python script that simulates a very slow data stream using psycopg2.

🐍 Python Script
				
					import psycopg2
import time

conn = psycopg2.connect("host=127.0.0.1 port=5433 dbname=yugabyte user=yugabyte")
cur = conn.cursor()

cur.execute("CREATE TABLE IF NOT EXISTS test_copy(id int, data text)")

# Custom file-like object that sends data very slowly
class SlowStream:
    def __init__(self):
        self.counter = 0

    def read(self, size):
        if self.counter >= 100:
            return b''  # EOF
        time.sleep(5)  # simulate slow client (5 sec delay)
        self.counter += 1
        return f"{self.counter}\tsome data\n".encode()

    def readline(self):
        return self.read(0)

cur.copy_from(SlowStream(), 'test_copy', columns=('id', 'data'))
conn.commit()
				
			

πŸ” Observing the Impact in ASH

Now let’s query Active Session History:

				
					SELECT
    query_id,
    wait_event_component,
    wait_event,
    wait_event_type,
    COUNT(*)
FROM
    yb_active_session_history
WHERE
    sample_time >= current_timestamp - interval '20 minutes'
GROUP BY
    query_id,
    wait_event_component,
    wait_event,
    wait_event_type
ORDER BY
    query_id,
    wait_event_component,
    wait_event_type;
				
			

πŸ“Š Sample Output

				
					.      query_id       | wait_event_component |  wait_event  | wait_event_type | count 
----------------------+----------------------+--------------+-----------------+-------
 -7661153383196697986 | YSQL                 | OnCpu_Active | Cpu             |    29
  -500256776759016306 | YSQL                 | ClientRead   | Client          |    38
                    5 | YSQL                 | CatalogRead  | RPCWait         |     1
  7453200712565570663 | YSQL                 | ClientWrite  | Client          |   134
				
			

🧠 What’s Happening Here?

πŸ”Ž Interpretation
ClientRead β†’ Server is waiting for client input (our slow stream)
ClientWrite β†’ Server is trying to send results to client
OnCpu_Active β†’ Actual execution work
πŸ‘‰ In this demo, the database spends significant time idle, waiting on the client.

⚑ Why This Matters

This pattern shows up in real systems when:

  • ● Applications stream data too slowly (batching issues)
  • ● Network throughput is limited
  • ● Clients process data before sending next chunk
  • ● Misconfigured ingestion pipelines (e.g., small chunk sizes)

πŸ‘‰ The result:

  • The database appears β€œslow”… but it’s actually waiting.

πŸ› οΈ How to Fix It

βœ… Optimization Tips
  • ● Increase batch sizes / buffer sizes
  • ● Avoid per-row delays or transformations
  • ● Use faster ingestion methods (parallel COPY, bulk loaders)
  • ● Check network latency and throughput
  • ● Ensure client is not CPU-bound or blocked

🧾 TL;DR

Signal Meaning Action
ClientRead DB waiting for client input ⚑ Speed up ingestion / batching
ClientWrite DB waiting on client to receive data 🌐 Check network / client processing
OnCpu_Active Actual query execution 🧠 Tune queries if dominant

🎯 Final Takeaway

When you see ClientRead, the database is not the bottleneck.

  • βœ… YugabyteDB is ready
  • ❌ The client is slow

πŸ‘‰ Focus on the application, not the database.

Have Fun!

Bob is my colleague at work, and today is his special day! πŸŽ‰