The Cross-Node Retrieval Problem: A Diagnostic Pivot in Distributed S3 Architecture

Message Context and Raw Content

The following message, sent by the assistant in a coding session for the Filecoin Gateway (FGW) distributed storage system, represents a critical diagnostic turning point. It was written after the team discovered that a load test was only hitting one node (kuri1) and that cross-node S3 reads were failing—objects written to kuri1 returned empty responses when read from kuri2, despite both nodes sharing a common S3 metadata index in YugabyteDB.

Subject Message (assistant, index 2064):

>

Ah - kuri2 finds the object in the shared S3 index but can't get the actual block data because it's on kuri1 and there's no cross-node retrieval happening. The nodes need to be able to fetch blocks from each other.

>

This is expected behavior for a multi-node setup - usually you need either: 1. A proxy/frontend layer that routes requests to the correct node based on node_id 2. Peer-to-peer block exchange between nodes (bitswap)

>

Let me check if bitswap peering is set up between the nodes: ``bash ssh 10.1.232.83 "curl -s http://127.0.0.1:5001/api/v0/swarm/peers" 2>&1 | head -20 405 - Method Not Allowed ``

This message, though brief, encapsulates a deep architectural realization and sets the stage for the subsequent redesign of the cluster topology. To understand its significance, one must trace the reasoning, assumptions, and decisions that led to this moment.

Why This Message Was Written: The Motivation and Context

The message was written in direct response to an operational failure observed during load testing. The user had run a load test against the QA cluster and noticed that only kuri1 was receiving traffic. When the assistant attempted to verify cross-node access by writing an object to kuri1 and reading it from kuri2, the read returned empty. The assistant then investigated the S3 CQL keyspace and discovered that the object metadata correctly existed in the shared filecoingw_s3 keyspace with node_id='kuri_01', but kuri2 could not serve the actual block data.

The core motivation for this message was diagnostic: the assistant needed to explain why the system was failing despite the shared metadata appearing correct. The message represents the moment when the assistant synthesized the observed symptoms (empty reads from kuri2, error logs showing "block was not found locally") with the architectural knowledge of how the system was designed, and articulated the fundamental gap: there was no mechanism for cross-node block retrieval.

This was not a trivial bug fix. It was an architectural gap—a missing layer in the deployment topology that had been overlooked during the initial QA cluster setup. The assistant had deployed kuri nodes as standalone S3 endpoints, each with its own local blockstore, but without any mechanism to fetch blocks from peer nodes when the object's metadata pointed to a different storage location.

The Reasoning and Thinking Process Visible in the Message

The assistant's reasoning follows a clear diagnostic chain:

  1. Observation: kuri2 can read the S3 object metadata from the shared YugabyteDB keyspace (the s3objects table shows the entry with node_id='kuri_01'), but the actual GET request returns empty.
  2. Root cause identification: The error log from kuri2 reads: "block was not found locally (offline): ipld: could not find bafkreicq725qu5x2tcre6ff6jb7cbuu4msnmmdceannrzsk6spdmom3gb4". This confirms that kuri2 knows the object should exist (it has the metadata) but cannot retrieve the underlying IPLD block because that block resides in kuri1's local blockstore.
  3. Architectural framing: The assistant immediately contextualizes this as "expected behavior for a multi-node setup." This is a crucial framing—it normalizes the failure as a missing feature rather than a bug, which guides the solution space toward architectural additions rather than code fixes.
  4. Solution enumeration: The assistant identifies two canonical approaches: - A proxy/frontend layer that routes S3 requests to the correct backend node based on node_id stored in the shared metadata - Peer-to-peer block exchange between kuri nodes (using IPFS bitswap protocol)
  5. Immediate action: The assistant attempts to check if bitswap peering is already configured by querying the IPFS swarm peers API on port 5001. The 405 Method Not Allowed response indicates that the standard IPFS API is not exposed on that port, or the node doesn't have an IPFS API server running.

Assumptions Made in This Message

Several assumptions underpin the assistant's analysis:

Assumption 1: The architecture should support cross-node block retrieval. The assistant assumes that the system design intends for any kuri node to be able to serve any object, regardless of which node originally stored the blocks. This is a reasonable assumption for a distributed storage system, but it was not explicitly validated against the project roadmap at this point.

Assumption 2: Bitswap is the appropriate peer-to-peer mechanism. The assistant assumes that IPFS bitswap (the block exchange protocol used in IPFS networks) is the right solution for cross-node block transfer. This is consistent with the system's IPFS-based architecture (the blocks are content-addressed using CIDs), but it may not be the most efficient or correct approach for a tightly coupled cluster with low-latency interconnects.

Assumption 3: The IPFS swarm API would be available on port 5001. The assistant assumes that kuri nodes expose a standard IPFS API on port 5001. The 405 response suggests this assumption is incorrect—either the API is not exposed, the port is different, or the kuri node doesn't run an IPFS API server at all.

Assumption 4: The problem is purely about block retrieval, not routing. The assistant frames the problem as "kuri2 can't get the block data" rather than "the request should have been routed to kuri1." This framing implicitly biases toward a peer-to-peer solution (bitswap) rather than a proxy-based routing solution.

Mistakes and Incorrect Assumptions

The most significant issue in this message is not an outright mistake, but a subtle misdirection. The assistant's framing of the problem as a block retrieval issue (kuri2 needs to fetch blocks from kuri1) leads toward a bitswap-based solution. However, the project's roadmap—as revealed in subsequent conversation—explicitly called for a separate stateless S3 frontend proxy layer that would route requests to the correct backend node. The assistant was not yet aware of this architectural requirement at the time of this message.

The assumption that bitswap peering should be checked on port 5001 was also incorrect. The kuri nodes do not expose a standard IPFS API on that port; the bitswap protocol, if used, would operate on different ports and through different mechanisms. The 405 response should have been a stronger signal that the bitswap approach was not the intended path.

Additionally, the assistant implicitly assumed that the current deployment topology (kuri nodes as direct S3 endpoints) was the correct one. In reality, the roadmap specified that kuri nodes should be backend storage nodes only, with a separate s3-proxy frontend handling all S3 API requests and routing them to the appropriate backend. This architectural misunderstanding would be corrected shortly after this message when the user pushed back and the assistant redesigned the deployment.

Input Knowledge Required to Understand This Message

To fully grasp the significance of this message, one needs:

  1. Understanding of the FGW architecture: The Filecoin Gateway uses a multi-tier storage model where kuri nodes store actual block data in local blockstores, while metadata (S3 object listings, bucket information) is stored in a shared YugabyteDB cluster using CQL (Cassandra Query Language).
  2. Knowledge of IPFS and content-addressed storage: The system uses IPFS CIDs (Content Identifiers) to address blocks. The error message "ipld: could not find bafkreicq725qu5x2tcre6ff6jb7cbuu4msnmmdceannrzsk6spdmom3gb4" references an IPLD (InterPlanetary Linked Data) lookup failure, which is the IPFS data model.
  3. Familiarity with bitswap: Bitswap is IPFS's block exchange protocol, where nodes request blocks from peers and serve blocks to peers. The assistant's mention of bitswap indicates an assumption that kuri nodes would use this protocol to exchange blocks.
  4. Knowledge of the deployment topology: The QA cluster consists of three physical nodes: a head node (10.1.232.82) running YugabyteDB, and two kuri storage nodes (10.1.232.83 and 10.1.232.84). Each kuri node runs a kuri daemon process that serves an S3-compatible API on port 8079.
  5. Understanding of the shared S3 index: The filecoingw_s3 keyspace in YugabyteDB contains a s3objects table that stores object metadata including bucket, key, cid, size, and node_id. This allows any node to discover which node holds a given object's blocks.

Output Knowledge Created by This Message

This message creates several important pieces of knowledge:

  1. Diagnosis of the cross-node read failure: The message explicitly identifies that the root cause is the lack of a cross-node block retrieval mechanism, not a metadata or indexing problem.
  2. Enumeration of solution approaches: By listing the proxy/frontend and bitswap options, the message creates a decision framework for the next steps. This is valuable because it forces explicit architectural choices rather than ad-hoc fixes.
  3. Evidence that bitswap is not configured: The failed API call to port 5001 provides empirical evidence that bitswap peering is not set up, which informs the decision to pursue the proxy/frontend approach instead.
  4. A clear articulation of the architectural gap: The message crystallizes the difference between "shared metadata" and "shared data" in a distributed storage system. Having a shared index is necessary but not sufficient—you also need a mechanism to retrieve data from the node that holds it.
  5. A foundation for the subsequent redesign: This diagnostic message directly leads to the assistant's later work of deploying the s3-proxy frontend on the head node, configuring it with backend node lists, and restructuring the deployment to use proper request routing. Without this diagnosis, the proxy deployment would have been a speculative addition rather than a targeted fix.

The Thinking Process: A Window into Diagnostic Reasoning

The message reveals a sophisticated diagnostic process that moves through several stages:

Stage 1: Symptom confirmation. The assistant has already verified that kuri2 returns empty for objects written to kuri1, and that the shared S3 index correctly shows the object with node_id='kuri_01'. This eliminates metadata inconsistency as a possible cause.

Stage 2: Error analysis. The assistant has read kuri2's logs and found the specific error: "block was not found locally (offline)." This error message is rich with information—it tells us that kuri2 attempted to look up the block in its local blockstore, failed, and had no fallback mechanism to fetch it from elsewhere.

Stage 3: Architectural categorization. The assistant categorizes the failure as "expected behavior for a multi-node setup." This is a crucial cognitive step—it reframes the problem from "something is broken" to "something is missing." This reframing reduces panic and opens up systematic solution design.

Stage 4: Solution space mapping. The assistant identifies two canonical approaches from distributed systems design: request routing (proxy) and data replication/sharing (bitswap). This shows an understanding of the fundamental trade-off between routing requests to data versus moving data to requests.

Stage 5: Hypothesis testing. The assistant tests the bitswap hypothesis by querying the IPFS swarm API. The 405 response is a negative result that helps narrow the solution space. While the assistant doesn't immediately abandon the bitswap approach (in subsequent messages, the assistant will explore both options further), this test provides valuable information.

Conclusion

Message 2064 is a textbook example of diagnostic reasoning in distributed systems debugging. It demonstrates how to move from observed symptoms (empty reads from kuri2) to root cause identification (no cross-node block retrieval) to solution enumeration (proxy routing vs. peer-to-peer exchange) to hypothesis testing (checking bitswap configuration). The message is concise but dense with architectural insight.

The most valuable aspect of this message is its role as a pivot point. Before this message, the assistant was focused on getting the basic cluster running—installing binaries, configuring systemd services, initializing databases. After this message, the focus shifts to the architectural question of how nodes should communicate and share data. This pivot ultimately leads to the deployment of the s3-proxy frontend and the proper three-layer architecture (S3 proxy → Kuri nodes → YugabyteDB) that the roadmap specified.

The message also reveals the assistant's thinking style: systematic, hypothesis-driven, and grounded in empirical observation. Rather than guessing at the cause, the assistant reads logs, queries databases, tests APIs, and synthesizes the evidence into a coherent explanation. This is the hallmark of effective distributed systems debugging.