diff --git a/changelog/20.0/20.0.0/summary.md b/changelog/20.0/20.0.0/summary.md
index 54179429bd5..bb376c6e721 100644
--- a/changelog/20.0/20.0.0/summary.md
+++ b/changelog/20.0/20.0.0/summary.md
@@ -11,11 +11,11 @@
- **[Flag changes](#flag-changes)**
- [`pprof-http` default change](#pprof-http-default)
- **[Minor Changes](#minor-changes)**
-
+ - **[New Stats](#new-stats)**
+ - [VTTablet Query Cache Hits and Misses](#vttablet-query-cache-hits-and-misses)
## Major Changes
-
### Query Compatibility
#### Vindex Hints
@@ -64,3 +64,11 @@ To continue enabling these endpoints, explicitly set `--pprof-http` when startin
## Minor Changes
+### New Stats
+
+#### VTTablet Query Cache Hits and Misses
+
+VTTablet exposes two new counter stats:
+
+ * `QueryCacheHits`: Query engine query cache hits
+ * `QueryCacheMisses`: Query engine query cache misses
diff --git a/go/vt/vttablet/tabletserver/query_engine.go b/go/vt/vttablet/tabletserver/query_engine.go
index b87c5cf97ba..f009a72ceee 100644
--- a/go/vt/vttablet/tabletserver/query_engine.go
+++ b/go/vt/vttablet/tabletserver/query_engine.go
@@ -188,6 +188,7 @@ type QueryEngine struct {
// stats
// Note: queryErrorCountsWithCode is similar to queryErrorCounts except it contains error code as an additional dimension
queryCounts, queryCountsWithTabletType, queryTimes, queryErrorCounts, queryErrorCountsWithCode, queryRowsAffected, queryRowsReturned *stats.CountersWithMultiLabels
+ queryCacheHits, queryCacheMisses *stats.CounterFunc
// stats flags
enablePerWorkloadTableMetrics bool
@@ -280,6 +281,12 @@ func NewQueryEngine(env tabletenv.Env, se *schema.Engine) *QueryEngine {
env.Exporter().NewCounterFunc("QueryCacheEvictions", "Query engine query cache evictions", func() int64 {
return qe.plans.Metrics.Evicted()
})
+ qe.queryCacheHits = env.Exporter().NewCounterFunc("QueryCacheHits", "Query engine query cache hits", func() int64 {
+ return qe.plans.Metrics.Hits()
+ })
+ qe.queryCacheMisses = env.Exporter().NewCounterFunc("QueryCacheMisses", "Query engine query cache misses", func() int64 {
+ return qe.plans.Metrics.Misses()
+ })
labels := []string{"Table", "Plan"}
if config.EnablePerWorkloadTableMetrics {
diff --git a/go/vt/vttablet/tabletserver/query_engine_test.go b/go/vt/vttablet/tabletserver/query_engine_test.go
index 8dbe18ef13c..604f65f3311 100644
--- a/go/vt/vttablet/tabletserver/query_engine_test.go
+++ b/go/vt/vttablet/tabletserver/query_engine_test.go
@@ -186,11 +186,27 @@ func TestQueryPlanCache(t *testing.T) {
ctx := context.Background()
logStats := tabletenv.NewLogStats(ctx, "GetPlanStats")
+ initialHits := qe.queryCacheHits.Get()
+ initialMisses := qe.queryCacheMisses.Get()
+
firstPlan, err := qe.GetPlan(ctx, logStats, firstQuery, false)
require.NoError(t, err)
require.NotNil(t, firstPlan, "plan should not be nil")
assertPlanCacheSize(t, qe, 1)
+
+ require.Equal(t, int64(0), qe.queryCacheHits.Get()-initialHits)
+ require.Equal(t, int64(1), qe.queryCacheMisses.Get()-initialMisses)
+
+ secondPlan, err := qe.GetPlan(ctx, logStats, firstQuery, false)
+ require.NoError(t, err)
+ require.NotNil(t, secondPlan, "plan should not be nil")
+
+ assertPlanCacheSize(t, qe, 1)
+
+ require.Equal(t, int64(1), qe.queryCacheHits.Get()-initialHits)
+ require.Equal(t, int64(1), qe.queryCacheMisses.Get()-initialMisses)
+
qe.ClearQueryPlanCache()
}