Skip to content

Initial implementation of the local cache layer - #3678

Merged
andrewnester merged 91 commits into
mainfrom
anton-107/cache-control-flow
Dec 10, 2025
Merged

Initial implementation of the local cache layer#3678
andrewnester merged 91 commits into
mainfrom
anton-107/cache-control-flow

Conversation

@anton-107

@anton-107anton-107 commented Sep 29, 2025

Copy link
Copy Markdown
Contributor

Changes

This PR introduces a local file-based cache layer for the Databricks CLI to improve performance of repeated operations.

New libs/cache package:

  • Standalone generic function GetOrCompute[T any](ctx, cache, fingerprint, compute) that works with any type
  • File-based cache implementation (fileCache) storing JSON-encoded data
  • SHA256-based fingerprinting for cache keys from any struct
  • Automatic cleanup of expired cache files on initialization
  • Fail-open behavior: cache errors never block operations, just trigger recomputation
  • Cache isolation by CLI version: ~/.cache/databricks///

Cache Modes:

  • Measurement mode (default): Cache disabled, but still measures potential savings via telemetry
  • Enabled mode: Set DATABRICKS_CACHE_ENABLED=true to actually use cached values

New CLI command:

  • databricks cache clear - Removes all cached files across all CLI versions

Bundle Integration:

  • New InitializeCache() mutator to set up cache in bundle initialization phase
  • PopulateCurrentUser now uses cache for CurrentUser.Me() API call

Why

This is the first attempt to speed up subsequent databricks bundle commands that a bundle developer runs while developing a bundle

Tests

  • changed existing acceptance tests to use a dedicated cache folder
  • added new acceptance test for overall caching functionality and telemetry
  • added new acceptance test for clearing the cache
  • added unit tests for libs/cache

"bundle_mode": "TYPE_UNSPECIFIED",
"workspace_artifact_path_type": "WORKSPACE_FILE_SYSTEM"
"workspace_artifact_path_type": "WORKSPACE_FILE_SYSTEM",
"local_cache_measurements_ms": [...redacted...]

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.

(optional) maybe worth recording the keys for the int map.

Comment threadlibs/cache/fingerprint_test.go
// TestFingerprintStability tests that the fingerprintToHash function returns the same hash for the same input.
func TestFingerprintStability(t *testing.T) {
fingerprint1 := struct {
Key string `json:"key"`

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.

What happens if there's an embedded struct with an override for the same field:

struct foo {
A string
B string
}
struct bar {
foo
A string
}

Do bar{A:"abc"} and bar{foo{A:"abc"}} compute to the same has or different?

This is a scenario that arises in dashboards so atleast we should document what happens in a unit test.

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.

This has to be a separate hash, but why dashboards matter here?

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.

but why dashboards matter here?

Maybe someday we might want to cache dashboards... Not a real usecase but atleast worth pointing out that the hashing is not bulletproof.

Comment threadlibs/cache/file_cache_clear.go
Comment threadlibs/cache/file_cache.go Outdated
Comment threadlibs/cache/file_cache.go
Comment threadlibs/cache/file_cache_env_test.go
Comment threadbundle/fingerprint.go

// getAuthorizationHeader extracts the Authorization header from the workspace client configuration.
// If it fails to authenticate, it returns an empty string.
func (b *Bundle) getAuthorizationHeader() string {

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.

When doing the cache miss analysis, it'll be interesting to corellate that with auth type. I would expect oauth-u2m to have more misses due to short lived API tokens.

=== First call in a session is expected to be a cache miss:
[DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH]
[DEBUG_TIMESTAMP] Debug: [Local Cache] failed to stat cache file: (redacted)
[DEBUG_TIMESTAMP] Debug: [Local Cache] cache miss, computing

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.

nit: should include function name that is being computed? This maybe more relevant in the future where is more than one function.

account Databricks Account Commands
api Perform Databricks API call
auth Authentication related commands
cache Local cache related commands

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.

From the description it's not obvious that is user-level cache (and not whatever we have in .databricks). Perhaps make it explicit "user-level cache"? cc @juliacrawf-db

Comment threadbundle/bundle.go Outdated
Comment threadbundle/config/mutator/populate_current_user.go Outdated
Comment threadlibs/cache/file_cache.go
Comment threadlibs/cache/file_cache.go Outdated
Comment threadlibs/cache/file_cache.go
Comment threadlibs/cache/file_cache.go
Comment threadlibs/cache/file_cache.go
@pieternpietern changed the title [local cache] Initial implementation of the local cache layerInitial implementation of the local cache layerDec 10, 2025
Comment threadbundle/config/mutator/populate_current_user.go Outdated
Comment threadbundle/config/mutator/populate_current_user.go Outdated
Comment threadcmd/cache/cache.go
Use: "cache",
Short: "Local cache related commands",
Long: "Manage local cache used by the Databricks CLI for improved performance",
}

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.

Please update the short/long for this.

Comment threadinternal/build/info.go Outdated
Comment threadinternal/build/info.go
Comment threadlibs/cache/cache.go
Comment threadlibs/cache/file_cache_env_test.go Outdated

@pieternpietern 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.

@andrewnester Please take a look at the PR summary as well.

The implementation has drifted from the original summary.

Comment threadlibs/cache/file_cache.go
Comment threadlibs/cache/file_cache_clear.go
@andrewnester
andrewnester added this pull request to the merge queueDec 10, 2025
Merged via the queue into main with commit 100a4a6Dec 10, 2025
19 checks passed
@andrewnester
andrewnester deleted the anton-107/cache-control-flow branch December 10, 2025 11:23
@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Commit: 100a4a6

Run: 20096926907

Env🔄​flaky💚​RECOVERED🙈​SKIP✅​pass🙈​skipTime
💚​aws linux10141362747:06
💚​aws windows10141562543:06
💚​aws-ucws linux10157450664:19
🔄​aws-ucws windows39157450487:30
🔄​azure linux54340962561:12
🔄​azure windows42341462357:22
🔄​azure-ucws linux33356950462:34
🔄​azure-ucws windows33357150259:14
💚​gcp linux4339463439:34
💚​gcp windows4339663243:22
22 interesting tests: 14 flaky, 7 RECOVERED, 1 SKIP
Test Nameaws linuxaws windowsaws-ucws linuxaws-ucws windowsazure linuxazure windowsazure-ucws linuxazure-ucws windowsgcp linuxgcp windows
🔄​TestAccept💚​R💚​R💚​R🔄​f💚​R💚​R🔄​f🔄​f💚​R💚​R
🔄​TestAccept/bundle/integration_whl/base✅​p✅​p✅​p🔄​f✅​p✅​p✅​p✅​p✅​p✅​p
🔄​TestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p🔄​f✅​p✅​p✅​p✅​p✅​p✅​p
🔄​TestAccept/bundle/resources/clusters/run/spark_python_task✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p
🔄​TestAccept/bundle/resources/clusters/run/spark_python_task/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p
🙈​TestAccept/bundle/resources/permissions🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🔄​TestAccept/bundle/resources/permissions/factcheck✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f🙈​s🙈​s
🔄​TestAccept/bundle/resources/permissions/factcheck/DATABRICKS_BUNDLE_ENGINE=terraform✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f
💚​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions💚​R💚​R💚​R💚​R🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
💚​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct💚​R💚​R💚​R💚​R
💚​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform💚​R💚​R💚​R💚​R
💚​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions💚​R💚​R💚​R💚​R🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
💚​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct💚​R💚​R💚​R💚​R
💚​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform💚​R💚​R💚​R💚​R
🔄​TestAccept/bundle/resources/secret_scopes/permissions✅​p✅​p✅​p✅​p🔄​f🔄​f✅​p✅​p🙈​s🙈​s
🔄​TestAccept/bundle/resources/secret_scopes/permissions/DATABRICKS_BUNDLE_ENGINE=terraform✅​p✅​p✅​p✅​p🔄​f🔄​f✅​p✅​p
🔄​TestAccept/bundle/run/app-with-job💚​R💚​R💚​R💚​R💚​R🔄​f💚​R💚​R💚​R💚​R
💚​TestAccept/bundle/run/app-with-job/DATABRICKS_BUNDLE_ENGINE=direct💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R
🔄​TestAccept/bundle/run/app-with-job/DATABRICKS_BUNDLE_ENGINE=terraform💚​R💚​R💚​R💚​R💚​R🔄​f💚​R💚​R💚​R💚​R
🔄​TestAccept/bundle/templates/default-python/combinations/classic/DATABRICKS_BUNDLE_ENGINE=terraform/DLT=no/NBOOK=yes/PY=yes✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p✅​p✅​p
🔄​TestAccept/bundle/templates/default-python/combinations/classic/DATABRICKS_BUNDLE_ENGINE=terraform/DLT=yes/NBOOK=no/PY=no✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p✅​p✅​p
🔄​TestAccept/bundle/templates/default-python/combinations/classic/DATABRICKS_BUNDLE_ENGINE=terraform/DLT=yes/NBOOK=no/PY=yes✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p✅​p✅​p
Top 50 slowest tests (at least 2 minutes):
durationenvtestname
22:23azure linuxTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=terraform
20:19azure linuxTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=direct
18:42azure windowsTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=USER_ISOLATION
18:00azure windowsTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=terraform
16:58azure windowsTestAccept/bundle/integration_whl/interactive_cluster/DATABRICKS_BUNDLE_ENGINE=terraform
14:15azure linuxTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=direct/DATA_SECURITY_MODE=USER_ISOLATION
14:03gcp windowsTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=SINGLE_USER
12:23azure-ucws linuxTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=SINGLE_USER
12:16azure-ucws linuxTestAccept/bundle/integration_whl/interactive_cluster/DATABRICKS_BUNDLE_ENGINE=direct
11:26aws-ucws windowsTestAccept/bundle/resources/model_serving_endpoints/running-endpoint/DATABRICKS_BUNDLE_ENGINE=terraform
11:18aws-ucws windowsTestAccept/bundle/resources/model_serving_endpoints/running-endpoint/DATABRICKS_BUNDLE_ENGINE=direct
11:16azure linuxTestAccept/bundle/resources/permissions/factcheck/DATABRICKS_BUNDLE_ENGINE=terraform
11:14aws-ucws linuxTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=direct
11:02gcp windowsTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=terraform
10:41gcp windowsTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=direct
10:13gcp linuxTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=terraform
10:03aws-ucws linuxTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=terraform
9:59gcp linuxTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=SINGLE_USER
9:54azure-ucws linuxTestAccept/bundle/resources/model_serving_endpoints/running-endpoint/DATABRICKS_BUNDLE_ENGINE=direct
9:38azure-ucws linuxTestAccept/bundle/resources/permissions/factcheck/DATABRICKS_BUNDLE_ENGINE=terraform
9:35aws-ucws windowsTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=terraform
9:08azure-ucws windowsTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=direct
9:08azure-ucws windowsTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=terraform
9:02aws windowsTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=USER_ISOLATION
8:47aws linuxTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=SINGLE_USER
8:43aws-ucws linuxTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=SINGLE_USER
8:39aws linuxTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=terraform
8:39aws windowsTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=SINGLE_USER
8:32azure linuxTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=SINGLE_USER
8:31gcp linuxTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=terraform
8:28aws windowsTestAccept/bundle/integration_whl/interactive_cluster/DATABRICKS_BUNDLE_ENGINE=direct
8:28aws-ucws linuxTestAccept/bundle/resources/model_serving_endpoints/running-endpoint/DATABRICKS_BUNDLE_ENGINE=terraform
8:19aws-ucws linuxTestAccept/bundle/resources/model_serving_endpoints/running-endpoint/DATABRICKS_BUNDLE_ENGINE=direct
8:15azure-ucws linuxTestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
8:12gcp windowsTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=terraform
8:06azure-ucws windowsTestAccept/bundle/integration_whl/interactive_single_user/DATABRICKS_BUNDLE_ENGINE=terraform
8:06aws-ucws windowsTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=SINGLE_USER
8:05azure-ucws linuxTestAccept/bundle/resources/model_serving_endpoints/running-endpoint/DATABRICKS_BUNDLE_ENGINE=terraform
8:04aws-ucws windowsTestAccept/bundle/integration_whl/interactive_cluster/DATABRICKS_BUNDLE_ENGINE=terraform
8:03aws linuxTestAccept/bundle/resources/clusters/run/spark_python_task/DATABRICKS_BUNDLE_ENGINE=terraform
8:02azure linuxTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=terraform
7:59azure windowsTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=direct/DATA_SECURITY_MODE=USER_ISOLATION
7:58aws-ucws windowsTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=terraform
7:55aws-ucws windowsTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=direct
7:50aws-ucws linuxTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=terraform
7:49aws windowsTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=direct/DATA_SECURITY_MODE=SINGLE_USER
7:44azure-ucws linuxTestAccept/bundle/integration_whl/base/DATABRICKS_BUNDLE_ENGINE=direct
7:40gcp windowsTestAccept/bundle/integration_whl/interactive_cluster/DATABRICKS_BUNDLE_ENGINE=terraform
7:40azure-ucws windowsTestAccept/bundle/resources/model_serving_endpoints/running-endpoint/DATABRICKS_BUNDLE_ENGINE=direct
7:35gcp windowsTestAccept/bundle/integration_whl/interactive_cluster_dynamic_version/DATABRICKS_BUNDLE_ENGINE=terraform/DATA_SECURITY_MODE=USER_ISOLATION

denik pushed a commit that referenced this pull request May 20, 2026
## Changes
This PR introduces a local file-based cache layer for the Databricks CLI
to improve performance of repeated operations.
New libs/cache package:
- Standalone generic function GetOrCompute[T any](ctx, cache,
fingerprint, compute) that works with any type
- File-based cache implementation (fileCache) storing JSON-encoded data
- SHA256-based fingerprinting for cache keys from any struct
- Automatic cleanup of expired cache files on initialization
- Fail-open behavior: cache errors never block operations, just trigger
recomputation
- Cache isolation by CLI version:
~/.cache/databricks/<version>/<component>/
Cache Modes:
- Measurement mode (default): Cache disabled, but still measures
potential savings via telemetry
- Enabled mode: Set DATABRICKS_CACHE_ENABLED=true to actually use cached
values
New CLI command:
- databricks cache clear - Removes all cached files across all CLI
versions
Bundle Integration:
- New InitializeCache() mutator to set up cache in bundle initialization
phase
- PopulateCurrentUser now uses cache for CurrentUser.Me() API call
## Why
<!-- Why are these changes needed? Provide the context that the reviewer
might be missing.
For example, were there any decisions behind the change that are not
reflected in the code itself? -->
This is the first attempt to speed up subsequent `databricks bundle`
commands that a bundle developer runs while developing a bundle
## Tests
<!-- How have you tested the changes? -->
- changed existing acceptance tests to use a dedicated cache folder
- added new acceptance test for overall caching functionality and
telemetry
- added new acceptance test for clearing the cache
- added unit tests for `libs/cache`
<!-- If your PR needs to be included in the release notes for next
release,
add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
---------
Co-authored-by: Andrew Nester <andrew.nester@databricks.com>
Co-authored-by: Andrew Nester <andrew.nester.dev@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@anton-107@eng-dev-ecosystem-bot@denik@andrewnester@pietern@shreyas-goenka