The $CQLSH_PYTHON Gambit: A Debugging Dead-End in YugabyteDB Cluster Deployment

The Message

[assistant] [bash] ssh 10.1.232.82 "CQLSH_PYTHON=/usr/bin/python3 /opt/yugabyte/bin/ycqlsh 10.1.232.82 9042 -e \"SELECT keyspace_name FROM system_schema.keyspaces;\"" 2>&1
Traceback (most recent call last):
  File "/opt/yugabyte/bin/ycqlsh.py", line 147, in <module>
    from six.moves import configparser, input
ModuleNotFoundError: No module named 'six.moves'

This single command, executed at index 1966 of a long coding session, captures a pivotal moment of debugging friction in the deployment of a distributed storage cluster. On its surface, it is a failed attempt to run YugabyteDB's CQL shell against a freshly started database node. But beneath that surface lies a rich story about assumptions, environment configuration, the hidden complexity of Python packaging in distributed systems, and the iterative detective work required to bring up a production-grade infrastructure from scratch.

The Broader Mission: Deploying a QA Test Cluster

To understand why this message was written, one must understand the larger context. The assistant was in the middle of deploying a QA (quality assurance) test environment for the Filecoin Gateway (FGW) distributed storage system across three physical nodes: a head node at 10.1.232.82 and two Kuri storage nodes at 10.1.232.83 and 10.1.232.84. This was a significant infrastructure undertaking — installing YugabyteDB, creating SQL and CQL databases, building and deploying Kuri binaries, setting up systemd services, and eventually configuring an S3 proxy frontend for cross-node object reads.

The assistant had already successfully installed YugabyteDB on the head node, started the yugabyted process, and created the SQL databases (filecoingw_kuri_01, filecoingw_kuri_02) using ysqlsh. The next logical step was to create the corresponding CQL keyspaces, which the Kuri storage nodes require for their Cassandra-style data access patterns. This is where the trouble began.

The Debugging Trail That Led Here

The message at index 1966 did not emerge from nowhere. It was the latest in a chain of increasingly frustrated attempts to get ycqlsh — YugabyteDB's CQL shell — to work. Earlier attempts had all failed with the same ModuleNotFoundError: No module named &#39;six.moves&#39; error. The assistant had tried:

The Assumption Behind CQLSH_PYTHON

The CQLSH_PYTHON environment variable is a well-known mechanism in the Cassandra ecosystem. The standard cqlsh tool (and by extension, YugabyteDB's ycqlsh) respects this variable to determine which Python interpreter to launch. Setting it to /usr/bin/python3 was a completely reasonable debugging step: if the problem is that ycqlsh is picking up Python 2 (where six.moves might not be installed or might be incompatible), then explicitly pointing it at Python 3 should resolve the issue.

This assumption rested on several layers of reasoning:

  1. The error is a Python import error, which means the Python interpreter is running but cannot find the module.
  2. The module exists on the system (verified in message 1962), so the issue is about which Python is running or how its path is configured.
  3. ycqlsh is a shell script that locates a Python interpreter (visible in messages 1954 and 1965), and CQLSH_PYTHON is a standard way to override that selection.
  4. Python 3 has six installed (verified), while Python 2 might not, so forcing Python 3 should work. The logic was sound. The hypothesis was reasonable. And yet the error persisted unchanged.

Why It Failed: The Hidden Complexity

The fact that CQLSH_PYTHON=/usr/bin/python3 did not change the outcome reveals something important about the architecture of ycqlsh. The script at /opt/yugabyte/bin/ycqlsh is a shell wrapper that eventually execs ycqlsh.py. But the Python file itself may manipulate sys.path in ways that override the system-installed packages. YugabyteDB ships with its own pylib directory containing Cassandra driver libraries, and the ycqlsh.py script likely modifies the Python path to prefer these bundled libraries over system packages. If the bundled libraries include an older or incompatible version of six — or if they exclude it entirely — then no amount of interpreter switching will help.

Alternatively, the CQLSH_PYTHON variable might not be supported by this particular version of ycqlsh. The script's Python detection logic (visible in message 1954) uses a hardcoded search: it tries python first, then python3, then python2.7. The CQLSH_PYTHON override might be a feature of the upstream Cassandra cqlsh that YugabyteDB's fork does not implement, or it might be read but then ignored due to the script's own path manipulation.

There is also the possibility of a Python version mismatch within the six module itself. The six.moves submodule is dynamically generated and its contents depend on the Python version. If ycqlsh.py was written for Python 2 and expects six.moves to contain certain attributes that Python 3's six provides differently, the import could fail even with the correct interpreter.

The Thinking Process Visible in This Message

What makes this message particularly instructive is what it reveals about the assistant's problem-solving strategy. The assistant is working through a classic debugging funnel:

  1. Observe the symptom: ycqlsh fails with ModuleNotFoundError.
  2. Form a hypothesis: The wrong Python interpreter is being used.
  3. Design an experiment: Set CQLSH_PYTHON to force Python 3.
  4. Execute and observe: The error persists.
  5. Refine the hypothesis: The problem is not (just) the interpreter selection. The assistant does not give up after this failure. In the very next messages (1967–1969), the assistant pivots to a completely different approach: bypassing ycqlsh entirely and using the Python Cassandra driver directly via a script. This alternative approach succeeds, and the keyspaces are created. The debugging dead-end at message 1966 was not a waste — it was a necessary elimination that forced the assistant to find a more robust solution.

The Resolution and What It Teaches

The ultimate resolution came through a direct Python script that imported the Cassandra driver from YugabyteDB's pylib, connected to the cluster, and executed the CREATE KEYSPACE statements programmatically. This approach worked because it sidestepped the fragile shell-wrapper layer entirely, giving the assistant direct control over the Python environment and import paths.

This episode illustrates several enduring lessons for infrastructure engineering:

Conclusion

The message at index 1966 is, in one sense, a failure: a command that did not produce the desired result. But in a deeper sense, it is a success — a necessary step in a disciplined debugging process that ultimately led to a working cluster. It reminds us that infrastructure deployment is rarely a straight line. Every error message is a clue, every failed experiment narrows the search space, and the path to a running system is paved with dead ends that teach us something about the tools we rely on.