[ 
https://issues.apache.org/jira/browse/NIFI-16359?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Jannik Rebmann updated NIFI-16359:
----------------------------------
    Description: 
h2. Summary

GitLabFlowRegistryClient against a self-hosted GitLab instance generates high 
load on the
GitLab server as the number of versioned process groups grows. The load does 
not come from
one expensive operation but from a per-process-group multiplier on the 
version-check path,
compounded by a GitLab-specific full-history listing. This ticket proposes 
removing that
multiplier at the shared base-class level so all Git-based registry clients 
benefit.
h2. Environment / Observation

Self-hosted GitLab, many versioned process groups. Our infrastructure team 
flagged a high
volume of GitLab REST API calls (repository/commits, repository/tree, 
repository/files)
originating from NiFi. The volume scales with the number of versioned process 
groups, even
when most of them are bound to the *same* flow at the *same* version.
h2. Root cause

The dominant driver is a per-process-group multiplier, not the cost of a single 
listing:

1. The periodic "up to date" check and version listing run {*}per process 
group{*}. In
AbstractGitFlowRegistryClient, getLatestVersion(...), getFlowVersions(...) and 
the
latest-version comparison in getFlowContents(...) all call
repositoryClient.getCommits(filePath, branch). With N process groups bound to 
the same
flow (identical filePath+branch), each cycle issues N identical 
repository/commits
requests. There is no deduplication across process groups.

2. repositoryClient is a singleton: getRepositoryClient(context) builds the 
client once
(guarded by clientInitialized) and reuses the volatile instance. A cache placed 
on the
base class is therefore shared across every process group using the component - 
i.e. the
multiplier can be removed with a shared, in-memory cache.

3. GitLab-specific amplification: GitLabRepositoryClient.getCommits(path, 
branch) uses the
gitlab4j List overload that delegates to Pager.all(), which pages the *entire* 
commit
history for the path (the client sets per_page=100). Each of the N calls above 
is thus
itself several page requests.

4. Content resolution (repository/tree, repository/files) is likewise performed 
per process
group when materializing a version.
h2. Relationship to NIFI-14837 / PR #10186

NIFI-14837 (fixed in 2.6.0, PR #10186) improved the GitHub client by (a) 
limiting the commit
listing to the first page and (b) adding an in-memory SHA->commit-detail cache. 
Two points:
 * Those changes were applied only to GitHubRepositoryClient. GitLab has 
neither, and
additionally pages the full history (point 3 above).
 * PR #10186 reduced GitHub load from O(processGroups x commits) to 
O(processGroups): it
removed the full-history paging and the per-commit N+1 detail fetch. It did NOT 
remove the
per-process-group multiplier - GitHubRepositoryClient.getCommits(path, branch) 
still issues
a live getRef + first-page listing on every call, with no (path, branch) cache 
and no TTL.
This simply was not visible at a small number of process groups. So GitHub 
scales linearly
with process-group count as well.
 * The SHA->commit-detail cache is not the load-bearing part for GitLab. 
gitlab4j's commit
listing returns fully-populated Commit objects (author, message, committed_date 
inline),
so there is no per-commit detail call to eliminate. Porting that cache 1:1 to 
GitLab would
add a dependency and complexity without reducing API calls.

h2. Proposed solution

1. Primary, general fix - cache the commit listing at the base class. Add a 
short-TTL,
size-bounded in-memory cache keyed by (filePath, branch) in 
AbstractGitFlowRegistryClient,
wrapping the getCommits calls in getLatestVersion / getFlowVersions / 
getFlowContents.
This collapses the per-process-group calls to one remote call per (flow, 
branch) per TTL
window and benefits all Git clients (GitHub, GitLab, Bitbucket, Azure DevOps). 
TTL bounds
the staleness for detecting a *remote* new version; invalidate eagerly on local 
writes
(createContent, deleteContent) and on client (re)initialization so locally 
committed
versions are visible immediately. TTL and size hard-coded initially; can be 
promoted to
properties in a follow-up.

2. GitLab-specific - bound the commit listing to the first page (a small 
COMMIT_PAGE_SIZE)
instead of Pager.all(), to stop full-history paging and cap payload size, 
mirroring the
listing limit from PR #10186.

3. Optional follow-up - immutable content cache. For the repository/tree and
repository/files load, add a cache keyed by (commitSha, path). Content at a 
fixed SHA is
immutable, so this can be cached without a TTL.
h2. Scope question for reviewers

The primary fix lives in the shared base class and benefits all four Git 
clients, so this
ticket's scope effectively broadens beyond GitLab. Should NIFI-16359 own the 
base-class
caching (with GitLab as the motivating case), or should the base-class change 
be split into a
dedicated framework ticket that per-client tickets reference? Input welcome 
before
implementation.
h2. References

NIFI-14837, PR [https://github.com/apache/nifi/pull/10186]

  was:
We are using GitLabFlowRegistryClient against a self-hosted GitLab instance. 
Our infrastructure team flagged a high load on the GitLab server that is being 
generated by the NiFi flow registry client. With a larger number of versioned 
process groups, NiFi's periodic version checks and version listings translate 
into a high volume of GitLab REST API calls (repository/commits, 
repository/tree, repository/files).

Looking into it, NIFI-14837 (fixed in 2.6.0, PR #10186) already reduced GitHub 
API load in GitHubFlowRegistryClient by

(a) adding an in-memory LRU cache of commit details keyed by SHA (max 1000), and

(b) limiting commit listing to the first page instead of paging the full 
history.

These optimizations were applied only to GitHubRepositoryClient. The sibling 
GitLabRepositoryClient (nifi-gitlab-bundle) still has no caching and, in 
getCommits(path, branch), calls CommitsApi.getCommits(projectPath, branch, 
resolvedPath), which pages the entire commit history for the path (default 
100/page). This is the most likely driver of the load our GitLab team is seeing.

Proposal: apply the same two improvements to GitLabRepositoryClient - an 
internal commit cache keyed by commit SHA, and a bounded commit listing (e.g. 
first page / configurable or hard limit) - mirroring the approach in PR #10186.

Reference: NIFI-14837, PR [https://github.com/apache/nifi/pull/10186]


> Port GitHubFlowRegistryClient API-call optimizations (NIFI-14837) to 
> GitLabFlowRegistryClient
> ---------------------------------------------------------------------------------------------
>
>                 Key: NIFI-16359
>                 URL: https://issues.apache.org/jira/browse/NIFI-16359
>             Project: Apache NiFi
>          Issue Type: Improvement
>          Components: Flow Versioning
>    Affects Versions: 2.12.0
>            Reporter: Jannik Rebmann
>            Priority: Major
>
> h2. Summary
> GitLabFlowRegistryClient against a self-hosted GitLab instance generates high 
> load on the
> GitLab server as the number of versioned process groups grows. The load does 
> not come from
> one expensive operation but from a per-process-group multiplier on the 
> version-check path,
> compounded by a GitLab-specific full-history listing. This ticket proposes 
> removing that
> multiplier at the shared base-class level so all Git-based registry clients 
> benefit.
> h2. Environment / Observation
> Self-hosted GitLab, many versioned process groups. Our infrastructure team 
> flagged a high
> volume of GitLab REST API calls (repository/commits, repository/tree, 
> repository/files)
> originating from NiFi. The volume scales with the number of versioned process 
> groups, even
> when most of them are bound to the *same* flow at the *same* version.
> h2. Root cause
> The dominant driver is a per-process-group multiplier, not the cost of a 
> single listing:
> 1. The periodic "up to date" check and version listing run {*}per process 
> group{*}. In
> AbstractGitFlowRegistryClient, getLatestVersion(...), getFlowVersions(...) 
> and the
> latest-version comparison in getFlowContents(...) all call
> repositoryClient.getCommits(filePath, branch). With N process groups bound to 
> the same
> flow (identical filePath+branch), each cycle issues N identical 
> repository/commits
> requests. There is no deduplication across process groups.
> 2. repositoryClient is a singleton: getRepositoryClient(context) builds the 
> client once
> (guarded by clientInitialized) and reuses the volatile instance. A cache 
> placed on the
> base class is therefore shared across every process group using the component 
> - i.e. the
> multiplier can be removed with a shared, in-memory cache.
> 3. GitLab-specific amplification: GitLabRepositoryClient.getCommits(path, 
> branch) uses the
> gitlab4j List overload that delegates to Pager.all(), which pages the 
> *entire* commit
> history for the path (the client sets per_page=100). Each of the N calls 
> above is thus
> itself several page requests.
> 4. Content resolution (repository/tree, repository/files) is likewise 
> performed per process
> group when materializing a version.
> h2. Relationship to NIFI-14837 / PR #10186
> NIFI-14837 (fixed in 2.6.0, PR #10186) improved the GitHub client by (a) 
> limiting the commit
> listing to the first page and (b) adding an in-memory SHA->commit-detail 
> cache. Two points:
>  * Those changes were applied only to GitHubRepositoryClient. GitLab has 
> neither, and
> additionally pages the full history (point 3 above).
>  * PR #10186 reduced GitHub load from O(processGroups x commits) to 
> O(processGroups): it
> removed the full-history paging and the per-commit N+1 detail fetch. It did 
> NOT remove the
> per-process-group multiplier - GitHubRepositoryClient.getCommits(path, 
> branch) still issues
> a live getRef + first-page listing on every call, with no (path, branch) 
> cache and no TTL.
> This simply was not visible at a small number of process groups. So GitHub 
> scales linearly
> with process-group count as well.
>  * The SHA->commit-detail cache is not the load-bearing part for GitLab. 
> gitlab4j's commit
> listing returns fully-populated Commit objects (author, message, 
> committed_date inline),
> so there is no per-commit detail call to eliminate. Porting that cache 1:1 to 
> GitLab would
> add a dependency and complexity without reducing API calls.
> h2. Proposed solution
> 1. Primary, general fix - cache the commit listing at the base class. Add a 
> short-TTL,
> size-bounded in-memory cache keyed by (filePath, branch) in 
> AbstractGitFlowRegistryClient,
> wrapping the getCommits calls in getLatestVersion / getFlowVersions / 
> getFlowContents.
> This collapses the per-process-group calls to one remote call per (flow, 
> branch) per TTL
> window and benefits all Git clients (GitHub, GitLab, Bitbucket, Azure 
> DevOps). TTL bounds
> the staleness for detecting a *remote* new version; invalidate eagerly on 
> local writes
> (createContent, deleteContent) and on client (re)initialization so locally 
> committed
> versions are visible immediately. TTL and size hard-coded initially; can be 
> promoted to
> properties in a follow-up.
> 2. GitLab-specific - bound the commit listing to the first page (a small 
> COMMIT_PAGE_SIZE)
> instead of Pager.all(), to stop full-history paging and cap payload size, 
> mirroring the
> listing limit from PR #10186.
> 3. Optional follow-up - immutable content cache. For the repository/tree and
> repository/files load, add a cache keyed by (commitSha, path). Content at a 
> fixed SHA is
> immutable, so this can be cached without a TTL.
> h2. Scope question for reviewers
> The primary fix lives in the shared base class and benefits all four Git 
> clients, so this
> ticket's scope effectively broadens beyond GitLab. Should NIFI-16359 own the 
> base-class
> caching (with GitLab as the motivating case), or should the base-class change 
> be split into a
> dedicated framework ticket that per-client tickets reference? Input welcome 
> before
> implementation.
> h2. References
> NIFI-14837, PR [https://github.com/apache/nifi/pull/10186]



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to