Skip to content

lakectl: share a single API client across a command invocation - #10509

Closed
idanovo wants to merge 1 commit into
masterfrom
fix/lakectl-share-api-client
Closed

lakectl: share a single API client across a command invocation#10509
idanovo wants to merge 1 commit into
masterfrom
fix/lakectl-share-api-client

Conversation

@idanovo

@idanovo idanovo commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Change Description

Background

Every call to getClient() constructs a new apigen client, and each one gets its own http.Client with a cloned http.Transport — i.e. its own connection pool. Two places construct clients during a single lakectl run:

  1. sendStats in PersistentPreRun (the usage-stats POST sent on every invocation), and
  2. the command body itself (fs cat, fs download, etc.).

Because the two clients never share a pool, every lakectl invocation opens two TCP+TLS connections to the lakeFS server where one would do. For interactive use this is invisible; for fleets that spawn lakectl per work item it doubles the connection load on the server side. We observed this in production with a pipeline invoking lakectl ~1,500 times/second: the NLB in front of lakeFS recorded almost exactly 2 new flows per invocation, contributing to connection-tracking exhaustion on the hosts behind it. (Ironically, getHTTPClientWithRetryConfig already tunes MaxIdleConnsPerHost to avoid TIME_WAIT churn — a tuning that only pays off if callers actually share the transport.)

Bug Fix

  1. Problem — each lakectl invocation dials the server twice (stats client + command client), doubling per-invocation connection cost.
  2. Root causegetClient() builds a new client + transport per call; the pre-run stats reporter and the command each call it once.
  3. Solution — memoize the client with sync.Once (same pattern as getTokenOnce), renaming the constructor to newAPIClient(). All callers now share one client and one connection pool, so an invocation dials once. Config is finalized in preRunCmd before the first getClient() call, so construction inputs are identical to today's second call — no behavior change beyond connection reuse.

Testing Details

  • go build ./cmd/lakectl/..., go vet ./cmd/lakectl/..., go test ./cmd/lakectl/... pass.
  • TestMaybeWarnEnterprise switches the server endpoint between subtests; added a resetAPIClientCache() test helper and per-subtest reset so the memoized client follows the endpoint change.

Breaking Change?

No. Same requests, same auth, same User-Agent — just over one connection instead of two.

Additional info

For a process that makes a single API call (the most common CLI case: one command + one stats POST), this halves connections per invocation. It does not change the process-per-invocation cost itself — callers invoking lakectl once per file may still want to batch — but it makes the floor 1 connection instead of 2 for every lakectl user.

🤖 Generated with Claude Code

getClient() built a fresh apigen client — each with its own cloned
http.Transport and therefore its own connection pool — on every call.
Since sendStats runs in PersistentPreRun with its own getClient() and the
command body calls getClient() again, every lakectl invocation opened two
TCP+TLS connections to the server where one suffices.

Memoize the client with sync.Once (same pattern as getTokenOnce): all
callers now share one client and one connection pool, so an invocation
dials the server once. Config is finalized in preRunCmd before the first
getClient() call, so behavior is otherwise unchanged.

At high invocation rates (pipelines spawning lakectl per file) this halves
the new-connection load on the server's load balancer and on host
connection tracking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added area/testing Improvements or additions to tests area/lakectl Issues related to lakeFS' command line interface (lakectl) labels Jul 29, 2026
@idanovo idanovo closed this Jul 29, 2026
@idanovo
idanovo deleted the fix/lakectl-share-api-client branch July 29, 2026 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/lakectl Issues related to lakeFS' command line interface (lakectl) area/testing Improvements or additions to tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant