mount: improve read throughput by enabling parallel HTTP connections

This addresses issue #7504 where a single weed mount FUSE instance does
not fully utilize node network bandwidth when reading large files.

The root cause is that HTTP GET streaming reads were getting serialized
even with multiple concurrent goroutines, due to:

1. HTTP/2 multiplexing all requests over a single TCP connection
2. The streaming loop using a 64KB intermediate buffer with extra copies

Changes:

http/client: Enable truly parallel connections
- Set ForceAttemptHTTP2: false to disable HTTP/2 multiplexing
- Set MaxConnsPerHost: 0 (unlimited) to allow parallel connections per host
- This lets multiple goroutines use separate TCP connections to the same
  volume server

filer/stream: Add direct-to-buffer fast path
- For full, uncompressed, unencrypted chunks, use RetriedFetchChunkData
  to read directly into the destination buffer
- This skips the 64KB staged streaming loop and avoids an extra memory copy
- Falls back to streaming path on partial fill or error

These changes should improve bandwidth utilization when reading large files
through a single mount instance.

Ref: #7504
This commit is contained in:
chrislu
2025-12-09 17:14:36 -08:00
parent 4f382b77c8
commit a4368b751e
2 changed files with 22 additions and 1 deletions
+18
View File
@@ -374,6 +374,24 @@ func (c *ChunkStreamReader) fetchChunkToBuffer(chunkView *ChunkView) error {
glog.V(1).Infof("operation LookupFileId %s failed, err: %v", chunkView.FileId, err)
return err
}
// Fast path: for full, plain chunks, read directly into a single buffer to avoid
// the streaming loop (which stages 64KB at a time and adds an extra copy).
if chunkView.CipherKey == nil && !chunkView.IsGzipped && chunkView.IsFullChunk() {
buf := make([]byte, chunkView.ViewSize)
n, fetchErr := util_http.RetriedFetchChunkData(context.Background(), buf, urlStrings, nil, false, true, chunkView.OffsetInChunk, chunkView.FileId)
if fetchErr == nil && n == len(buf) {
c.buffer = buf
c.bufferOffset = chunkView.ViewOffset
c.chunk = chunkView.FileId
return nil
}
// Fall back to the streaming path on partial fill or error
if fetchErr != nil {
glog.V(1).Infof("read %s direct failed, err: %v, falling back to stream", chunkView.FileId, fetchErr)
}
}
var buffer bytes.Buffer
var shouldRetry bool
jwt := JwtForVolumeServer(chunkView.FileId)
+4 -1
View File
@@ -130,7 +130,10 @@ func NewHttpClient(clientName ClientName, opts ...HttpClientOpt) (*HTTPClient, e
httpClient.Transport = &http.Transport{
MaxIdleConns: 1024,
MaxIdleConnsPerHost: 1024,
TLSClientConfig: tlsConfig,
// Allow truly parallel per-host connections and avoid HTTP/2 multiplexing
MaxConnsPerHost: 0, // unlimited
ForceAttemptHTTP2: false, // disable HTTP/2 to avoid implicit single-connection multiplexing
TLSClientConfig: tlsConfig,
}
httpClient.Client = &http.Client{
Transport: httpClient.Transport,