Skip to content

feat(metrics): Implement and capture the metadata cache read count metric. - #5093

Open
thrivikram-karur-g wants to merge 17 commits into
masterfrom
vikram-metadata-read-cache-simple-implementation
Open

thrivikram-karur-g wants to merge 17 commits into
masterfrom
vikram-metadata-read-cache-simple-implementation

Conversation

@thrivikram-karur-g

@thrivikram-karur-g thrivikram-karur-g commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

Implementing the metadata_cache/read_count metric to capture the hits and misses with detailed reason for miss or hit as a counter metric to understand the metadata_cache performance.
metadata_cache/read_count was already defined in metrics/metrics.yaml but nothing ever
recorded it. This PR wires up emission and export.

Summary of the code changes:

  • Emitted from exactly one place — dirInode.LookUpChild — with cache_hit,
    entry_status (positive/negative) and lookup_detail (found/ttl_expired/not_found).
  • stat_cache.go derives the classification, fast_stat_bucket.go carries the miss reason
    up via CacheMissError, dir.go emits, otelexporters.go allowlists the prefix.

Perf test results on the PR:

Screenshot 2026-09-23 at 12 56 32 PM

we do not see any regression and all bandwidth fluctuations seems normal and within healthy range.

Link to the issue in case of a bug fix.

b/512002923

Testing details

  1. Manual

Executed the below manual test plan for checking the metrics count updates for the metadata_cache/read_count metric.

Ran GCSFuse with the below command.

Start GCSFuse with Prometheus metric reporting enabled on port 8080:

go run . --foreground \
  --enable-buffered-read \
  --implicit-dirs \
  --client-protocol http1 \
  --prometheus-port=8080 \
  --enable-metadata-prefetch=false \
  --metadata-cache-negative-ttl-secs=120 \
  --metadata-cache-ttl-secs=120 \
  --log-severity=trace \
  <BUCKET_NAME> <MOUNT_DIR>

Query the exposed Prometheus metrics endpoint to inspect the metadata_cache/read_count counter and its attributes:

bash
curl -s http://localhost:8080/metrics | grep "metadata_cache/read_count"

Verified manually the below test cases for corresponding change in metric counts queried from the prometheus end point.

Scenario Test Case Executed Command(s) Expected Metric Output Delta
1 Cold File Miss stat <MOUNT_DIR>/nonexistent_file.txt metadata_cache_read_count_total{cache_hit="false",lookup_detail="not_found"} +1
2 Cold Directory Miss stat <MOUNT_DIR>/nonexistent_dir/ metadata_cache_read_count_total{cache_hit="false",lookup_detail="not_found"} +1
3 Warm File Hit (Positive) 1. stat <MOUNT_DIR>/existing_file.txt
2. stat <MOUNT_DIR>/existing_file.txt
1st stat: {cache_hit="false",lookup_detail="not_found"} +1
2nd stat: {cache_hit="true",entry_status="positive",lookup_detail="found"} +1
4 Warm Directory Hit (Positive) 1. stat <MOUNT_DIR>/existing_dir/
2. stat <MOUNT_DIR>/existing_dir/
1st stat: {cache_hit="false",lookup_detail="not_found"} +1
2nd stat: {cache_hit="true",entry_status="positive",lookup_detail="found"} +1
5 Warm File Hit (Negative) 1. stat <MOUNT_DIR>/nonexistent_file.txt
2. stat <MOUNT_DIR>/nonexistent_file.txt
1st stat: {cache_hit="false",lookup_detail="not_found"} +1
2nd stat: {cache_hit="true",entry_status="negative",lookup_detail="found"} +1
6 Warm Directory Hit (Negative) 1. stat <MOUNT_DIR>/nonexistent_dir/
2. stat <MOUNT_DIR>/nonexistent_dir/
1st stat: {cache_hit="false",lookup_detail="not_found"} +1
2nd stat: {cache_hit="true",entry_status="negative",lookup_detail="found"} +1
7 Positive File TTL Expired 1. stat <MOUNT_DIR>/existing_file.txt
2. sleep 125 && stat <MOUNT_DIR>/existing_file.txt
1st stat: {cache_hit="false",lookup_detail="not_found"} +1
2nd stat: {cache_hit="false",entry_status="positive",lookup_detail="ttl_expired"} +1
8 Positive Directory TTL Expired 1. stat <MOUNT_DIR>/existing_dir/
2. sleep 125 && stat <MOUNT_DIR>/existing_dir/
1st stat: {cache_hit="false",lookup_detail="not_found"} +1
2nd stat: {cache_hit="false",entry_status="positive",lookup_detail="ttl_expired"} +1
9 Negative Entry TTL Expired 1. stat <MOUNT_DIR>/nonexistent_file.txt
2. sleep 125 && stat <MOUNT_DIR>/nonexistent_file.txt
1st stat: {cache_hit="false",lookup_detail="not_found"} +1
2nd stat: {cache_hit="false",entry_status="negative",lookup_detail="ttl_expired"} +1
10 Directory Listing (No Stat Inflation) ls --color=never <MOUNT_DIR>/ No metric delta for metadata_cache_read_count (ReadDir does not issue per-entry stat calls)
  1. Unit tests - Added
  2. Integration tests - NA

Any backward incompatible change? If so, please explain.

N/A

@thrivikram-karur-g
thrivikram-karur-g requested a review from a team as a code owner September 15, 2026 05:50
@github-actions github-actions Bot added the remind-reviewers Auto remind reviewers in attention set for review post 24hrs of inactivity on PR. label Sep 15, 2026
@thrivikram-karur-g
thrivikram-karur-g removed the request for review from kislaykishore September 15, 2026 05:50

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces detailed metadata cache metrics tracking (such as cache hits, misses, TTL expirations, and positive/negative entry statuses) across gcsfuse. It updates the stat cache, directory inodes, and file inodes to record these metrics, and adds comprehensive end-to-end tests. A critical correctness issue was identified in internal/fs/inode/dir.go where using || instead of && in the negative entry check could lead to false ENOENT errors and data invisibility.

Comment thread internal/fs/inode/dir.go
Comment thread internal/storage/caching/fast_stat_bucket.go
Comment thread internal/storage/caching/fast_stat_bucket.go
Comment thread metrics/helper.go Outdated
Comment thread internal/fs/inode/file.go Outdated
Comment thread internal/fs/inode/file.go Outdated
Comment thread internal/fs/inode/dir.go Outdated
@codecov

codecov Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.77%. Comparing base (e67225a) to head (1a83e98).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
metrics/metrics_test_utils.go 0.00% 4 Missing ⚠️
internal/cache/metadata/stat_cache.go 89.47% 2 Missing ⚠️
internal/fs/inode/metadata_cache_outcome.go 84.61% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5093      +/-   ##
==========================================
- Coverage   86.79%   86.77%   -0.03%     
==========================================
  Files         176      177       +1     
  Lines       18868    18936      +68     
==========================================
+ Hits        16377    16431      +54     
- Misses       2490     2504      +14     
  Partials        1        1              
Flag Coverage Δ
unittests 86.77% <90.90%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@thrivikram-karur-g

Copy link
Copy Markdown
Contributor Author

Passed Integration tests.

@thrivikram-karur-g thrivikram-karur-g added execute-perf-test Execute performance test in PR and removed execute-integration-tests Run only integration tests labels Sep 21, 2026
…vikram-metadata-read-cache-simple-implementation
@thrivikram-karur-g

Copy link
Copy Markdown
Contributor Author

Perf tests also have been run

Comment thread internal/fs/inode/dir.go Outdated
Comment thread internal/fs/inode/dir.go Outdated
Comment thread internal/fs/inode/cache_outcome.go Outdated
@thrivikram-karur-g

Copy link
Copy Markdown
Contributor Author

Updated the perf results run on the PR with the latest changes.

@thrivikram-karur-g thrivikram-karur-g added execute-integration-tests Run only integration tests and removed execute-perf-test Execute performance test in PR labels Sep 23, 2026
@thrivikram-karur-g

Copy link
Copy Markdown
Contributor Author

Re-Ran the integration tests with the updated PR changes and they passed.


require.True(t, foundMetric, "metric %s not found", metricName)
require.Fail(t, "Data point for attributes %v not found in %s metric", attrs, metricName)
require.Failf(t, "data point not found", "no data point matching attributes %v in metric %s", attrs, metricName)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the reason behind making these changes?

LookUp(name string, now time.Time) (hit bool, m *gcs.MinObject)

// LookUpDetail returns the current object entry along with entry status and lookup detail.
LookUpDetail(name string, now time.Time) (hit bool, m *gcs.MinObject, entryStatus metrics.EntryStatus, detail metrics.LookupDetail)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find EntryKind or EntryType better than EntryStatus. Status should represent things like whether the entry is expired or not.

}

if req.FetchOnlyFromCache {
} else if req.FetchOnlyFromCache {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change if to else if? It adds more cognitive load. Same comment elsewhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will keep variables out of if scope

return
}

func (b *fastStatBucket) lookUpDetail(name string) (hit bool, m *gcs.MinObject, entryStatus metrics.EntryStatus, detail metrics.LookupDetail) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename it LookupFileDetail

Comment thread internal/fs/inode/dir.go
// 3. Negative entry check:
// Both lookups must be cache hits (no cacheMiss errors) with no results found for us to
// conclude the entry does not exist. If only one candidate is a negative hit, the other
// candidate may still exist in GCS, so we must fall through and query GCS.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the GCS call is still happening - should it be recorded as a cache-hit or a miss?

// This is a no-op when the context carries no outcome, which is the case when
// metrics are disabled or for unit tests that drive LookUpChild directly.
//
// Not safe for concurrent use. All call sites are on LookUpChild's own

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fragile. While currently it might work, it's a maintenance hazard. I understand that performance could be a concern in which case, we should see how to fix that. But without that, it could lead to hard-to-find bugs.

@github-actions

Copy link
Copy Markdown

Hi @vadlakondaswetha, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

execute-integration-tests Run only integration tests remind-reviewers Auto remind reviewers in attention set for review post 24hrs of inactivity on PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants