Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Time To Live (TTL) to the inventory cache #605

Merged
merged 3 commits into from
Dec 21, 2023

Conversation

marckhouzam
Copy link
Contributor

@marckhouzam marckhouzam commented Dec 4, 2023

The 3 commits are kept separated just for the review until we agree on the approach.
Once we agree I will squash and update the commit message accordingly.

What this PR does / why we need it

Normally, the digest of the plugin inventory is checked every time the DB needs to be read. Although this is faster than downloading the DB each time, there is still between a 2.5s to 4.5s delay in checking the digest. This makes every plugin search and plugin group search command slower. It also makes some plugin install --group and plugin sync commands much slower when all plugins are available in the cache, because the installation of each plugin in the group causes a digest check, even if the plugin binary is already in the cache; for example with a group of 10 plugins already the cache, the installation can take 30 seconds because of the 10 digest checks instead of 5 seconds.

This PR adds a Time To Live for the DB cache. This means that when within the TTL, the digest is not checked and the DB cache is considered valid. The time the digest was last checked is stored as the modification time of the digest.* file of the discovery cache. Whenever the DB needs to be read, if the TTL has not expired since the last time the digest was verified, the DB is directly read from cache; if the TTL has expired, the digest is checked, the TTL reset and the DB downloaded if required.

On a plugin source update or plugin source init the DB cache is automatically refreshed ignoring the TTL. This provides a way to force an immediate refresh of the cache without having to wait for the TTL to expire.

Note that for discovery sources added through the TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY we needed a way to know for what URI the cache was created. This was tricky because such test discovery sources are not stored in the configuration file (which would have allowed to map a name to a URI). To support this case, commit "Store URI in the main plugin inventory digest file" starts storing the URI directly in the cache as a single line within the main digest.* file. This provides a cache-centric way of knowing if a DB must be refreshed when a discovery OCI image URI changes.

Finally, the default value of the digest TTL is set to 30 minutes in this PR. It can be overridden using the new variable TANZU_CLI_PLUGIN_DB_CACHE_TTL_SECONDS (meant for testing). A cache TTL of 30 minutes implies that by default it can take a CLI up to 30 minutes to notice the publication of new plugins in the central repository. If for some reason a user wants to force a refresh immediately, they can simply do tanzu plugin source init or tanzu plugin source update ... to the current URI.

Which issue(s) this PR fixes

Fixes # N/A

Describe testing done for PR

Unit tests were added as well as E2E tests.
Manual tests . Most commands below are run using time which allows to show the commands are very fast (< 1 sec) thanks to this PR. When a command takes 2 seconds, it means the digest is being refreshed.

First the speedup:

# Cleanup
$ rm ~/.cache/tanzu/plugin_inventory/
$ rm ~/.config/tanzu/config*
$ rm ~/.cache/tanzu/catalog.yaml

$ tz ceip set false
$ tz config eula accept
[ok] Marking agreement as accepted.

########################
# Run different commands that access the cache and check the speed
########################
# The first one is long as it has to download the DB
$ time tz plugin search --name builder
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Reading plugin inventory for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.15s user 0.06s system 2% cpu 8.137 total

# The second one is very fast, even though we are installing the essential plugin since the plugin is in the cache
$ time tz plugin search --name builder
[i] The tanzu cli essential plugins have not been installed and are being installed now. The install may take a few seconds.
[i] Installing plugins from plugin group 'vmware-tanzucli/essentials:v1.0.0'
[i] Installing plugin 'telemetry:v1.1.0' with target 'global' (from cache)

  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.37s user 0.17s system 144% cpu 0.374 total

# All follow-up commands are fast during the 30 minute TTL
$ time tz plugin search --name builder
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.07s user 0.02s system 99% cpu 0.092 total
$ time tz plugin search --name builder
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.07s user 0.02s system 79% cpu 0.117 total
$ time tz plugin search --name builder
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.07s user 0.02s system 85% cpu 0.105 total

# Install plugins from a group and look at the time it takes.
# One of the plugins is not in the cache, which is why the command takes 10 seconds.
$ time tz plugin install --group vmware-tap/default
[i] The following plugins will be installed from plugin group 'vmware-tap/default:v1.7.2'
  NAME              TARGET      VERSION
  accelerator       kubernetes  v1.7.0
  apps              kubernetes  v0.13.0
  build-service     kubernetes  v1.0.0
  external-secrets  kubernetes  v0.1.0
  insight           kubernetes  v1.7.2
  package           kubernetes  v0.32.1
  secret            kubernetes  v0.32.0
  services          kubernetes  v0.8.0
[i] Installing plugin 'accelerator:v1.7.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'apps:v0.13.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'build-service:v1.0.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'external-secrets:v0.1.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'insight:v1.7.2' with target 'kubernetes'
[i] Installing plugin 'package:v0.32.1' with target 'kubernetes' (from cache)
[i] Installing plugin 'secret:v0.32.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'services:v0.8.0' with target 'kubernetes' (from cache)
[ok] successfully installed all plugins from group 'vmware-tap/default:v1.7.2'
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin install --group vmware-tap/defaul  3.21s user 1.80s system 47% cpu 10.592 total

# Re-install the same group now that all plugins are in the cache
# This takes less than a second
$ time tz plugin install --group vmware-tap/default
[i] The following plugins will be installed from plugin group 'vmware-tap/default:v1.7.2'
  NAME              TARGET      VERSION
  accelerator       kubernetes  v1.7.0
  apps              kubernetes  v0.13.0
  build-service     kubernetes  v1.0.0
  external-secrets  kubernetes  v0.1.0
  insight           kubernetes  v1.7.2
  package           kubernetes  v0.32.1
  secret            kubernetes  v0.32.0
  services          kubernetes  v0.8.0
[i] Plugin 'accelerator:v1.7.0' with target 'kubernetes' is already installed. Reinitializing...
[i] Plugin 'apps:v0.13.0' with target 'kubernetes' is already installed. Reinitializing...
[i] Plugin 'build-service:v1.0.0' with target 'kubernetes' is already installed. Reinitializing...
[i] Plugin 'external-secrets:v0.1.0' with target 'kubernetes' is already installed. Reinitializing...
[i] Plugin 'insight:v1.7.2' with target 'kubernetes' is already installed. Reinitializing...
[i] Plugin 'package:v0.32.1' with target 'kubernetes' is already installed. Reinitializing...
[i] Plugin 'secret:v0.32.0' with target 'kubernetes' is already installed. Reinitializing...
[i] Plugin 'services:v0.8.0' with target 'kubernetes' is already installed. Reinitializing...
[ok] successfully installed all plugins from group 'vmware-tap/default:v1.7.2'
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin install --group vmware-tap/defaul  0.36s user 0.17s system 71% cpu 0.752 total

# uninstall all the plugins
$ rm ~/.cache/tanzu/catalog.yaml
# Re-install the same group with all plugins still in the cache
# This takes less than a second
$ time tz plugin install --group vmware-tap/default
[i] The tanzu cli essential plugins have not been installed and are being installed now. The install may take a few seconds.
[i] Installing plugins from plugin group 'vmware-tanzucli/essentials:v1.0.0'
[i] Installing plugin 'telemetry:v1.1.0' with target 'global' (from cache)

[i] The following plugins will be installed from plugin group 'vmware-tap/default:v1.7.2'
  NAME              TARGET      VERSION
  accelerator       kubernetes  v1.7.0
  apps              kubernetes  v0.13.0
  build-service     kubernetes  v1.0.0
  external-secrets  kubernetes  v0.1.0
  insight           kubernetes  v1.7.2
  package           kubernetes  v0.32.1
  secret            kubernetes  v0.32.0
  services          kubernetes  v0.8.0
[i] Installing plugin 'accelerator:v1.7.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'apps:v0.13.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'build-service:v1.0.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'external-secrets:v0.1.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'insight:v1.7.2' with target 'kubernetes' (from cache)
[i] Installing plugin 'package:v0.32.1' with target 'kubernetes' (from cache)
[i] Installing plugin 'secret:v0.32.0' with target 'kubernetes' (from cache)
[i] Installing plugin 'services:v0.8.0' with target 'kubernetes' (from cache)
[ok] successfully installed all plugins from group 'vmware-tap/default:v1.7.2'
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin install --group vmware-tap/defaul  0.37s user 0.18s system 73% cpu 0.752 total

# Now run a similar test but with plugins installed from a context
# It take less than 10 seconds which is very fast compared to before.
# The delay is probably because the REST endpoint is accessed.
# There may be a future improvement possible here.
$ time tz context create --endpoint unstable.tmc-dev.cloud.vmware.com --staging tmc
[i] API token env var is set

[ok] successfully created a TMC context
[i] Checking for required plugins for context 'tmc'...
[i] The following plugins will be installed for context 'tmc' of contextType 'mission-control':
  NAME                  TARGET           VERSION
  account               mission-control  v0.1.18
  policy                mission-control  v0.1.15
  cluster               mission-control  v0.2.10
  provider-aks-cluster  mission-control  v0.1.16
  provider-eks-cluster  mission-control  v0.1.15
  context               mission-control  v0.1.10
  tanzupackage          mission-control  v0.4.0
  integration           mission-control  v0.1.15
  events                mission-control  v0.1.15
  setting               mission-control  v0.2.13
  apply                 mission-control  v0.3.12
  ekscluster            mission-control  v0.2.0
  inspection            mission-control  v0.1.15
  agentartifacts        mission-control  v0.1.15
  workspace             mission-control  v0.1.17
  aks-cluster           mission-control  v0.2.5
  clustergroup          mission-control  v0.1.15
  helm                  mission-control  v0.1.16
  management-cluster    mission-control  v0.2.16
  iam                   mission-control  v0.1.15
  audit                 mission-control  v0.1.15
  continuousdelivery    mission-control  v0.1.15
  data-protection       mission-control  v0.1.15
  secret                mission-control  v0.1.17
[i] Installing plugin 'account:v0.1.18' with target 'mission-control' (from cache)
[i] Installing plugin 'policy:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'cluster:v0.2.10' with target 'mission-control' (from cache)
[i] Installing plugin 'provider-aks-cluster:v0.1.16' with target 'mission-control' (from cache)
[i] Installing plugin 'provider-eks-cluster:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'context:v0.1.10' with target 'mission-control' (from cache)
[i] Installing plugin 'tanzupackage:v0.4.0' with target 'mission-control' (from cache)
[i] Installing plugin 'integration:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'events:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'setting:v0.2.13' with target 'mission-control' (from cache)
[i] Installing plugin 'apply:v0.3.12' with target 'mission-control' (from cache)
[i] Installing plugin 'ekscluster:v0.2.0' with target 'mission-control' (from cache)
[i] Installing plugin 'inspection:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'agentartifacts:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'workspace:v0.1.17' with target 'mission-control' (from cache)
[i] Installing plugin 'aks-cluster:v0.2.5' with target 'mission-control' (from cache)
[i] Installing plugin 'clustergroup:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'helm:v0.1.16' with target 'mission-control' (from cache)
[i] Installing plugin 'management-cluster:v0.2.16' with target 'mission-control' (from cache)
[i] Installing plugin 'iam:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'audit:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'continuousdelivery:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'data-protection:v0.1.15' with target 'mission-control' (from cache)
[i] Installing plugin 'secret:v0.1.17' with target 'mission-control' (from cache)
[i] Successfully installed all required plugins
/Users/kmarc/git/tanzu-cli/bin/tanzu context create --endpoint  --staging tmc  9.47s user 5.16s system 158% cpu 9.224 total

# Check the speedup for "plugin group" commands
$ time tz plugin group get vmware-tkg/default
Plugins in Group:  vmware-tkg/default:v2.4.1
  NAME                TARGET      VERSION
  isolated-cluster    global      v0.31.1
  management-cluster  kubernetes  v0.31.1
  package             kubernetes  v0.31.1
  pinniped-auth       global      v0.31.1
  secret              kubernetes  v0.31.1
  telemetry           kubernetes  v0.31.1
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin group get vmware-tkg/default  0.11s user 0.02s system 96% cpu 0.132 total

$ time tz plugin group get vmware-tkg/default --all
Plugins in Group:  vmware-tkg/default:v2.4.1

Standalone Plugins
  NAME                TARGET      VERSION
  isolated-cluster    global      v0.31.1
  management-cluster  kubernetes  v0.31.1
  package             kubernetes  v0.31.1
  pinniped-auth       global      v0.31.1
  secret              kubernetes  v0.31.1
  telemetry           kubernetes  v0.31.1

Note: The standalone plugins in this plugin group are installed when the 'tanzu plugin install --group vmware-tkg/default' command is invoked.

Contextual Plugins
  NAME                TARGET      VERSION
  cluster             kubernetes  v0.31.1
  feature             kubernetes  v0.31.1
  kubernetes-release  kubernetes  v0.31.1

Note: The contextual plugins in this plugin group are automatically installed, and only available for use, when a Tanzu context which supports them is created or activated/used.
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin group get vmware-tkg/default --al  0.12s user 0.03s system 79% cpu 0.179 total

$ time tz plugin group search
  GROUP                       DESCRIPTION                          LATEST
  vmware-tanzucli/essentials  Essential plugins for the Tanzu CLI  v1.0.0
  vmware-tap/default          Plugins for TAP                      v1.7.2
  vmware-tkg/default          Plugins for TKG                      v2.4.1
  vmware-tmc/default          Plugins for TMC                      v1.0.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin group search  0.11s user 0.02s system 90% cpu 0.141 total

Now check correctness when updating the plugin source:

# Change the plugin source to staging.  The new DB should be read even though the TTL (30 minutes) didn't expire.
$ tz plugin source update default -u projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Reading plugin inventory for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest", this will take a few seconds.
[ok] updated discovery source default

# Check the content and the speed
$ time tz plugin search --name builder
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0-alpha.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.11s user 0.02s system 87% cpu 0.150 total
$ time tz plugin group search
  GROUP                                           DESCRIPTION                     LATEST
  vmware-tap/default                              Plugins for TAP                 v1.7.1-rc.1
  vmware-tkg/default                              Plugins for TKG                 v2.2.0
  vmware-tkg/tkg-ob-464796462228756226-g885cc5ad  Plugin group for Halifax.0 RC1  v2.3.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin group search  0.11s user 0.02s system 86% cpu 0.154 total

# Make sure we can force a DB refresh using `tanzu plugin update` to the same URI
$ tz plugin source update default -u projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest", this will take a few seconds.
[ok] updated discovery source default

# Reset the plugin source to production
$ tz plugin source init
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Reading plugin inventory for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
[ok] successfully initialized discovery source
$ time tz plugin search --name builder
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.10s user 0.02s system 80% cpu 0.150 total

# Now as above, also check that `tanzu plugin source init` always checks the digest
$ tz plugin source init
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
[ok] successfully initialized discovery source

Now test TTL expiry:

# Now test the actual digest TTL expiry by making it is smaller than the 30 minute default
$ export TANZU_CLI_PLUGIN_DB_CACHE_TTL_SECONDS=10

# The first command will reset the digest because 10 seconds has passed since the last digest check
# so we should see the 2 second delay
# The second command is 4 seconds later so no digest check, so no delay
# The third command is 8 seconds later so no digest check, so no delay
# The forth command is 12 seconds later so TTL expires so digest check, so 2 seconds delay
$ time tz plugin search --name builder >/dev/null;\
sleep 4;\
time tz plugin search --name builder >/dev/null;\
sleep 4;\
time tz plugin search --name builder >/dev/null;\
sleep 4;\
time tz plugin search --name builder >/dev/null

[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder > /dev/null  0.11s user 0.04s system 6% cpu 2.371 total
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder > /dev/null  0.12s user 0.03s system 85% cpu 0.173 total
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder > /dev/null  0.12s user 0.03s system 86% cpu 0.174 total
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder > /dev/null  0.16s user 0.04s system 9% cpu 2.160 total

# Now do the same check with the real 30 minute TTL, just to be really sure
$ unset TANZU_CLI_PLUGIN_DB_DIGEST_TTL_SECONDS

$ tz plugin source list
  NAME     IMAGE
  default  projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest

# The first command resets the cache TTL
# The second command is 10 minutes later so no digest check, so no delay for the "plugin search" command
# The third command is 20 minutes later so no digest check, so no delay for the "plugin search" command
# The forth command is 29 minutes 50 seconds later so no digest check, so no delay for the "plugin search" command
# The fifth command is 30 minutes 2 seconds later so TTL expires so digest check, so 2 seconds delay to refresh the cache
tz plugin source init;\
sleep 600; \
time tz plugin search --name builder >/dev/null;\
sleep 600; \
time tz plugin search --name builder >/dev/null;\
sleep 590; \
time tz plugin search --name builder >/dev/null;\
sleep 12; \
time tz plugin search --name builder >/dev/null

[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
[ok] successfully initialized discovery source
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder > /dev/null  0.15s user 0.03s system 68% cpu 0.260 total
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder > /dev/null  0.15s user 0.03s system 94% cpu 0.194 total
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder > /dev/null  0.15s user 0.03s system 69% cpu 0.255 total
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder > /dev/null  0.18s user 0.05s system 10% cpu 2.324 total

Now test with TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY:

$ make start-test-central-repo
[...]

# Set two additional test discovery sources
$ tz config set env.TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest,localhost:9876/tanzu-cli/plugins/central:small
$ tz config set env.TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_VERIFICATION_SKIP_LIST localhost:9876/tanzu-cli/plugins/central:small

$ tz plugin source list
  NAME                IMAGE
  default             projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest
  disc_0 (test only)  projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest
  disc_1 (test only)  localhost:9876/tanzu-cli/plugins/central:small

# Here the TTL was still overridden to 10 seconds, so even the Central Repo discovery is refreshed
$ tz plugin search --name builder --show-details
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Reading plugin inventory for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Refreshing plugin inventory cache for "localhost:9876/tanzu-cli/plugins/central:small", this will take a few seconds.
[i] Reading plugin inventory for "localhost:9876/tanzu-cli/plugins/central:small", this will take a few seconds.
[!] Skipping the plugins discovery image signature verification for "localhost:9876/tanzu-cli/plugins/central:small"

name: builder
description: Build Tanzu components
target: global
latest: v1.1.0
versions:
    - v0.1.0-dev-8-g6087ea00
    - v0.82.5-alpha.0
    - v0.89.1
    - v0.90.0-alpha.0
    - v0.90.0-alpha.1
    - v0.90.0-alpha.2
    - v0.90.0-beta.0
    - v0.90.0
    - v0.90.1
    - v1.0.0-rc.0
    - v1.0.0-rc.1
    - v1.0.0
    - v1.1.0-alpha.0
    - v1.1.0

# Reset TTL to 30 minutes
$ unset TANZU_CLI_PLUGIN_DB_CACHE_TTL_SECONDS

# Notice how fast teh commands that use the DB are, even using
# TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY
$ time tz plugin search --name builder
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.11s user 0.02s system 85% cpu 0.157 total
$ time tz plugin group search
  GROUP                                           DESCRIPTION                                 LATEST
  vmware-tanzucli/essentials                      Desc for vmware-tanzucli/essentials:v9.9.9  v9.9.9
  vmware-tap/default                              Plugins for TAP                             v1.7.2
  vmware-tkg/default                              Desc for vmware-tkg/default:v9.9.9          v9.9.9
  vmware-tkg/shortversion                         Desc for vmware-tkg/shortversion:v9.9.9     v9.9.9
  vmware-tkg/tkg-ob-464796462228756226-g885cc5ad  Plugin group for Halifax.0 RC1              v2.3.0
  vmware-tmc/default                              Plugins for TMC                             v1.0.0
  vmware-tmc/tmc-user                             Desc for vmware-tmc/tmc-user:v9.9.9         v9.9.9
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin group search  0.11s user 0.02s system 80% cpu 0.171 total

Test correctness for TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY:

# Switch the two discovery sources so that the cache don't match anymore
# The CLI knows to refresh the cache for both sources because each cache now stores
# the source URI in the digest file.
$ tz config set env.TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY localhost:9876/tanzu-cli/plugins/central:small,projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest
$ tz plugin source list
  NAME                IMAGE
  default             projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest
  disc_0 (test only)  localhost:9876/tanzu-cli/plugins/central:small
  disc_1 (test only)  projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest
$ # Now make sure both test discovery caches are refreshed
$ tz plugin search --name builder --show-details
[i] Refreshing plugin inventory cache for "localhost:9876/tanzu-cli/plugins/central:small", this will take a few seconds.
[i] Reading plugin inventory for "localhost:9876/tanzu-cli/plugins/central:small", this will take a few seconds.
[!] Skipping the plugins discovery image signature verification for "localhost:9876/tanzu-cli/plugins/central:small"

[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Reading plugin inventory for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest", this will take a few seconds.
name: builder
description: Build Tanzu components
target: global
latest: v1.1.0
versions:
    - v0.1.0-dev-8-g6087ea00
    - v0.82.5-alpha.0
    - v0.89.1
    - v0.90.0-alpha.0
    - v0.90.0-alpha.1
    - v0.90.0-alpha.2
    - v0.90.0-beta.0
    - v0.90.0
    - v0.90.1
    - v1.0.0-rc.0
    - v1.0.0-rc.1
    - v1.0.0
    - v1.1.0-alpha.0
    - v1.1.0

# Check the content of the digest files
$ cat ~/.cache/tanzu/plugin_inventory/default/digest.*
projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest%
$ cat ~/.cache/tanzu/plugin_inventory/disc_0/digest.*
localhost:9876/tanzu-cli/plugins/central:small%
$ cat ~/.cache/tanzu/plugin_inventory/disc_1/digest.*
projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest%

# Check corner case where the URI changes but not the digest
$ tz plugin source list
  NAME                IMAGE
  default             projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest
  disc_0 (test only)  localhost:9876/tanzu-cli/plugins/central:small
  disc_1 (test only)  projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory:latest
# Update "default" discovery to a new URI that points to the same image (by removing the ":latest" tag)
# and notice the DB is refreshed because the CLI sees the URI change
$ tz plugin source update default -u projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory
[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory", this will take a few seconds.
[i] Reading plugin inventory for "projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory", this will take a few seconds.
[ok] updated discovery source default

# Notice that the digest file *content* is updated, which is important
# so the CLI knows the cache matches the current configure discovery URI
$ cat ~/.cache/tanzu/plugin_inventory/default/digest.*
projects.registry.vmware.com/tanzu_cli_stage/plugins/plugin-inventory%

# Confirm the commands are fast
$ tz plugin search --name builder
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0-alpha.0
$ time tz plugin search --name builder
  NAME     DESCRIPTION             TARGET  LATEST
  builder  Build Tanzu components  global  v1.1.0-alpha.0
/Users/kmarc/git/tanzu-cli/bin/tanzu plugin search --name builder  0.11s user 0.03s system 82% cpu 0.162 total

Release note

Improve CLI responsiveness for plugin life-cycle commands by introducing a time-to-live of 30 minutes for the plugin inventory cache (can be changed through the environment variable `TANZU_CLI_PLUGIN_DB_CACHE_TTL_SECONDS`).  To force a plugin inventory cache refresh the `tanzu plugin source init` command can be used.

Additional information

Special notes for your reviewer

Point 1
The event that causes the plugin inventory DB to change is the publication of new plugin versions or plugin group versions. It is important to realize that such publications do not actually need to be detected immediately by CLIs in the field; what is important is that once a new plugin version is available, it becomes accessible in a reasonable amount of time. In fact, publication of plugins is already subject to mirroring delays to CDNs. Furthermore, in normal operation, end-users do not perform plugin life-cycle commands very often so don't need the plugin cache to immediately be up-to-date.

With this in mind, it is acceptable that the enhancement of the PR makes it possible for a CLI to not notice new plugins for up to 30 minutes.

However, we will need to communicate this to plugin publishing teams so they understand that they may not see their plugins immediately after publishing them but that they can do a tanzu plugin source init/update to work around it.

Point 2

I chose 30 minutes as the TTL, but it could just as much be a different value (5 minutes, 24 hours, etc). Opinions are welcomed.

Point 3

Now that plugin search and plugin group search are very fast, it is a noticeable different when the TTL has expired and they take 2 seconds to check the digest. I chose to add a printout when the digest is check. This printout would be seen at most every TTL (30 minutes):

[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds

If the DB has actually changed, it would look like this:

[i] Refreshing plugin inventory cache for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Reading plugin inventory for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.

Suggestions on the wording, or if the extra printout is necessary are welcomed.

@marckhouzam marckhouzam requested a review from a team as a code owner December 4, 2023 22:19
@marckhouzam marckhouzam force-pushed the feat/betterCaching branch 3 times, most recently from 0347c18 to 8410442 Compare December 5, 2023 15:00
@marckhouzam marckhouzam added this to the v1.2.0 milestone Dec 6, 2023
@vuil
Copy link
Contributor

vuil commented Dec 8, 2023

Thanks for the changes. Very nice PR description, tests and notes.

Great that you brought up Point 3 because that was something I wanted to understand.

Whether it is worth supporting a similar optimization for TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY-based source is certainly up for debate (++), but dropping support because this source is more transient (not configured in a file) may not be the only option.
Some other options I can think of:

  1. If we can provide a means for the user to force the recheck of the digest (e.g. tweak plugin source init to effectively invalidate all cached digests), then the onus is on the user to trigger the force in the corner case situation where TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY is changed.
  2. We can store the value of TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY used in the last digest check somewhere and use it to validate the next time we need to consider if the cached db is 'valid' for the current TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY value or not.

q: do we have concerns that it is not easy to tell which dir in ~/.cache/tanzu/plugin_inventory the test_only one points to?

Just some thoughts, but going back to (++), it is possible we feel it is not worth going this length to support a non-production use-case.

@marckhouzam marckhouzam added the kind/feature Categorizes issue or PR as related to a new feature label Dec 11, 2023
@marckhouzam
Copy link
Contributor Author

marckhouzam commented Dec 13, 2023

Following @vuil suggestion, this latest forced push provides the TTL for the inventory cache without needing to use the configuration file. I haven't yet updated the description of the PR to match.

I will now work at enabling the TTL for plugin sources added through TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY

@marckhouzam marckhouzam marked this pull request as draft December 13, 2023 02:16
@marckhouzam
Copy link
Contributor Author

Marking as draft until it is ready again

@marckhouzam marckhouzam force-pushed the feat/betterCaching branch 2 times, most recently from 6211f12 to 4670ef9 Compare December 14, 2023 15:53
@marckhouzam marckhouzam marked this pull request as ready for review December 14, 2023 16:15
@marckhouzam
Copy link
Contributor Author

marckhouzam commented Dec 14, 2023

This is ready for review. The PR description has been updated. except for the manual testing section which I'm working on now

Copy link
Contributor

@vuil vuil left a comment

Choose a reason for hiding this comment

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

Thanks for this really nice bit of optimization. Love the tests as well. LGTM.
Most users likely would not need to do anything to take advantage of the change, but for completeness, I'd suggest a few bits of bookkeeping

  • Add docs-impact label to the PR.
  • Quick mentions of TANZU_CLI_PLUGIN_DB_CACHE_TTL_SECONDS and tanzu source init in the release notes

Copy link
Contributor

@anujc25 anujc25 left a comment

Choose a reason for hiding this comment

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

This looks great. Thanks for all the details as part of the description which made reviewing this change easier. 👍

@marckhouzam marckhouzam added the docs-impact issues with documentation impact label Dec 21, 2023
Normally, the digest of the plugin inventory is checked every time the
DB needs to be read.  Although this is faster than downloading the DB
each time, there is still between a 2.5s to 4.5s delay in checking the
digest. This makes every `plugin search` and `plugin group search`
command slower.  It also makes some `plugin install --group` commands
much slower when all plugins are available in the cache, since the
installation of each plugin in the group causes a digest check, even
if the plugin binary is already in the cache.

This commit provides a type of Time To Live for the DB. This means that
when within the TTL, the digest is not checked and the DB is considered
valid. The time the digest was last checked is stored as the
modification time (mtime) of the digest file. So, whenever the DB needs
to be read, if the TTL has not expired since the last time the digest
was verified, the DB is directly read from cache; if the TTL has
expired, the digest is checked and the DB downloaded if required.

On a "plugin source update" or "plugin source init" the TTL is ignored
and the digest automatically checked.  This is important as either of
these commands usually modify the URI of the plugin discovery and
therefore invalidates the DB.

Note that for any discovery source added through the
TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY variable, the
digest is checked every time (this TTL feature does not apply); this is
because there is no way to know if current cache was downloaded from
the same URIs as what is currently in the variable.  This is different
than for "plugin source update/init" because these two commands can
actively force a cache refresh but changing the
TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY variable cannot
do that.

The value of the cache TTL is of 30 minutes.  This means that it can
take a CLI up to 30 minutes to notice the publication of new plugins in
the central repository.  If for some reason a user wants to force a
refresh immediately, they can simply do `tanzu plugin source init` (or
`tanzu plugin source update default -u ...` if the discovery source is
not the default central repository). The TTL value can be overriden
using the environment variable TANZU_CLI_PLUGIN_DB_DIGEST_TTL_SECONDS.

Signed-off-by: Marc Khouzam <[email protected]>
To allow the CLI to know if a plugin inventory cache has become invalid
because it represents a different URI, we now store the OCI image URI
inside the main digest file.  Whenever the TTL is checked to know if
the digest must be checked, the URI is also checked; if the URI has
changed, the digest must be checked.

Signed-off-by: Marc Khouzam <[email protected]>
Signed-off-by: Marc Khouzam <[email protected]>
@marckhouzam marckhouzam merged commit 6b1c899 into vmware-tanzu:main Dec 21, 2023
7 checks passed
@marckhouzam marckhouzam deleted the feat/betterCaching branch December 21, 2023 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla-not-required docs-impact issues with documentation impact kind/feature Categorizes issue or PR as related to a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants