diff --git a/CHANGELOG.md b/CHANGELOG.md index 36eac477bf..3bc1dc3c51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Changed +- Cache: switch to [otter](https://maypok86.github.io/otter/) as the primary cache implementation (https://github.com/authzed/spicedb/pull/3112) + ### Fixed - Watch consumers that request `WatchCheckpoints` now eventually observe every revision returned by `WriteRelationships` as a checkpoint. MemDB regressed this in https://github.com/authzed/spicedb/pull/2578 for no-op writes and MySQL never emitted checkpoints at all prior to now. Both now emit a checkpoint at the new revision. (https://github.com/authzed/spicedb/pull/3114) diff --git a/docs/spicedb.md b/docs/spicedb.md index 991e3abdfc..708a931889 100644 --- a/docs/spicedb.md +++ b/docs/spicedb.md @@ -475,14 +475,12 @@ spicedb serve [flags] --dispatch-cache-enabled enable caching of dispatch calls this server makes to other servers (default true) --dispatch-cache-max-cost string upper bound (in bytes or as a percent of available memory) of the cache for dispatch calls this server makes to other servers (default "30%") --dispatch-cache-metrics enable metrics for the cache for dispatch calls this server makes to other servers (default true) - --dispatch-cache-num-counters int number of counters for tracking access frequency in the cache for dispatch calls this server makes to other servers. A higher number means more accurate eviction decisions but more memory usage (default 10000) --dispatch-check-permission-concurrency-limit uint16 maximum number of parallel goroutines to create for each check request or subrequest. defaults to --dispatch-concurrency-limit --dispatch-chunk-size uint16 maximum number of object IDs in a dispatched request (default 100) --dispatch-cluster-addr string address to listen on to serve dispatch (default ":50053") --dispatch-cluster-cache-enabled enable caching of dispatch calls this server receives from other servers (default true) --dispatch-cluster-cache-max-cost string upper bound (in bytes or as a percent of available memory) of the cache for dispatch calls this server receives from other servers (default "70%") --dispatch-cluster-cache-metrics enable metrics for the cache for dispatch calls this server receives from other servers (default true) - --dispatch-cluster-cache-num-counters int number of counters for tracking access frequency in the cache for dispatch calls this server receives from other servers. A higher number means more accurate eviction decisions but more memory usage (default 100000) --dispatch-cluster-enabled enable dispatch gRPC server --dispatch-cluster-max-conn-age duration how long a connection serving dispatch should be able to live (default 30s) --dispatch-cluster-max-workers uint32 set the number of workers for this server (0 value means 1 worker per request) @@ -527,7 +525,6 @@ spicedb serve [flags] --lookup-resources-chunk-cache-enabled enable caching of LookupResources3 chunks (default true) --lookup-resources-chunk-cache-max-cost string upper bound (in bytes or as a percent of available memory) of the cache for LookupResources3 chunks (default "50MiB") --lookup-resources-chunk-cache-metrics enable metrics for the cache for LookupResources3 chunks - --lookup-resources-chunk-cache-num-counters int number of counters for tracking access frequency in the cache for LookupResources3 chunks. A higher number means more accurate eviction decisions but more memory usage (default 10000) --max-bulk-export-relationships-limit uint32 maximum number of relationships that can be exported in a single request (default 10000) --max-caveat-context-size int maximum allowed size of request caveat context in bytes. A value of zero or less means no limit (default 4096) --max-datastore-read-page-size uint limit on the maximum page size that we will load into memory from the datastore at one time (default 1000) @@ -543,7 +540,6 @@ spicedb serve [flags] --ns-cache-enabled enable caching of schema (default true) --ns-cache-max-cost string upper bound (in bytes or as a percent of available memory) of the cache for schema (default "32MiB") --ns-cache-metrics enable metrics for the cache for schema (default true) - --ns-cache-num-counters int number of counters for tracking access frequency in the cache for schema. A higher number means more accurate eviction decisions but more memory usage (default 1000) --otel-endpoint string OpenTelemetry collector endpoint - the endpoint can also be set by using enviroment variables --otel-insecure connect to the OpenTelemetry collector in plaintext --otel-provider string OpenTelemetry provider for tracing ("none", "otlphttp", "otlpgrpc") (default "none") @@ -556,7 +552,6 @@ spicedb serve [flags] --stored-schema-cache-enabled enable caching of stored schema (default true) --stored-schema-cache-max-cost string upper bound (in bytes or as a percent of available memory) of the cache for stored schema (default "32MiB") --stored-schema-cache-metrics enable metrics for the cache for stored schema (default true) - --stored-schema-cache-num-counters int number of counters for tracking access frequency in the cache for stored schema. A higher number means more accurate eviction decisions but more memory usage (default 1000) --streaming-api-response-delay-timeout duration maximum time that streaming APIs (LookupSubjects, LookupResources, ReadRelationships and ExportBulkRelationships) can be allowed to run but no response be sent to the client before the stream times out (default 30s) --telemetry-ca-override-path string path to a custom CA to use with the telemetry endpoint --telemetry-endpoint string endpoint to which telemetry is reported, empty string to disable (default "https://telemetry.authzed.com") diff --git a/go.mod b/go.mod index 6a2a36f0ce..ca2db56523 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/KimMachineGun/automemlimit v0.7.5 github.com/Masterminds/semver v1.5.0 github.com/Masterminds/squirrel v1.5.4 - github.com/Yiling-J/theine-go v0.6.2 github.com/authzed/authzed-go v1.9.0 github.com/authzed/consistent v0.2.0 github.com/authzed/ctxkey v0.0.0-20250226155515-d49f99185584 @@ -76,7 +75,6 @@ require ( github.com/ngrok/sqlmw v0.0.0-20220520173518-97c9c04efc79 github.com/odigos-io/go-rtml v0.0.1 github.com/ory/dockertest/v3 v3.12.0 - github.com/outcaste-io/ristretto v0.2.3 github.com/pganalyze/pg_query_go/v6 v6.1.0 github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 github.com/prometheus/client_golang v1.23.2 @@ -206,7 +204,6 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect @@ -227,7 +224,6 @@ require ( github.com/opencontainers/runc v1.2.8 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml/v2 v2.3.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.20.1 // indirect @@ -248,7 +244,6 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/zeebo/xxh3 v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect diff --git a/go.sum b/go.sum index 9611c936bb..6d0e74536b 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/Yiling-J/theine-go v0.6.2 h1:1GeoXeQ0O0AUkiwj2S9Jc0Mzx+hpqzmqsJ4kIC4M9AY= -github.com/Yiling-J/theine-go v0.6.2/go.mod h1:08QpMa5JZ2pKN+UJCRrCasWYO1IKCdl54Xa836rpmDU= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/authzed/authzed-go v1.9.0 h1:wscQbRD+OLW+Jg2wfZgmPeEKkaXFyVb3+nbtjUS++JA= @@ -119,7 +117,6 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= @@ -158,8 +155,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= @@ -168,7 +163,6 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ecordell/optgen v0.2.6 h1:qPglm/JyuW6vL9IYnSwgFMces2dLFCuEd2Yw6B0VRmU= @@ -328,8 +322,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -405,8 +397,6 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= -github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= -github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= @@ -414,7 +404,6 @@ github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8 github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY= github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= @@ -522,10 +511,6 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -561,7 +546,6 @@ go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLh go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -635,7 +619,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/datastore/proxy/schemacaching/caching.go b/internal/datastore/proxy/schemacaching/caching.go index 53a809a605..568183aee2 100644 --- a/internal/datastore/proxy/schemacaching/caching.go +++ b/internal/datastore/proxy/schemacaching/caching.go @@ -27,8 +27,7 @@ const ( // DatastoreProxyTestCache returns a cache used for testing. func DatastoreProxyTestCache(t testing.TB) cache.Cache[cache.StringKey, CacheEntry] { cache, err := cache.NewStandardCache[cache.StringKey, CacheEntry](&cache.Config{ - NumCounters: 1000, - MaxCost: 1 * humanize.MiByte, + MaxCost: 1 * humanize.MiByte, }) require.NoError(t, err) return cache diff --git a/internal/datastore/proxy/schemacaching/watchingcache_test.go b/internal/datastore/proxy/schemacaching/watchingcache_test.go index bebfe03101..f8b2d1aeb7 100644 --- a/internal/datastore/proxy/schemacaching/watchingcache_test.go +++ b/internal/datastore/proxy/schemacaching/watchingcache_test.go @@ -578,9 +578,8 @@ func TestWatchingCacheFallbackToStandardCache(t *testing.T) { } c, err := cache.NewStandardCache[cache.StringKey, *cacheEntry](&cache.Config{ - NumCounters: 1000, - MaxCost: 10000, - DefaultTTL: 10000 * time.Second, + MaxCost: 10000, + DefaultTTL: 10000 * time.Second, }) require.NoError(t, err) @@ -623,9 +622,8 @@ func TestOldWatchingCacheFallbackToStandardCache(t *testing.T) { } c, err := cache.NewStandardCache[cache.StringKey, *cacheEntry](&cache.Config{ - NumCounters: 1000, - MaxCost: 10000, - DefaultTTL: 10000 * time.Second, + MaxCost: 10000, + DefaultTTL: 10000 * time.Second, }) require.NoError(t, err) @@ -682,9 +680,8 @@ func TestOldWatchingCachePrepopulated(t *testing.T) { } c, err := cache.NewStandardCache[cache.StringKey, *cacheEntry](&cache.Config{ - NumCounters: 1000, - MaxCost: 1000, - DefaultTTL: 1000 * time.Second, + MaxCost: 1000, + DefaultTTL: 1000 * time.Second, }) require.NoError(t, err) @@ -728,9 +725,8 @@ func TestWatchingCachePrepopulated(t *testing.T) { } c, err := cache.NewStandardCache[cache.StringKey, *cacheEntry](&cache.Config{ - NumCounters: 1000, - MaxCost: 1000, - DefaultTTL: 1000 * time.Second, + MaxCost: 1000, + DefaultTTL: 1000 * time.Second, }) require.NoError(t, err) diff --git a/internal/dispatch/keys/computed.go b/internal/dispatch/keys/computed.go index 84caa94f37..c44eb7e8f2 100644 --- a/internal/dispatch/keys/computed.go +++ b/internal/dispatch/keys/computed.go @@ -7,13 +7,6 @@ import ( "github.com/authzed/spicedb/pkg/tuple" ) -type dispatchCacheKeyHashComputeOption int - -const ( - computeOnlyStableHash dispatchCacheKeyHashComputeOption = 0 - computeBothHashes dispatchCacheKeyHashComputeOption = 1 -) - // cachePrefix defines a unique prefix for a type of cache key. type cachePrefix string @@ -42,8 +35,8 @@ var cachePrefixes = []cachePrefix{ } // checkRequestToKey converts a check request into a cache key based on the relation -func checkRequestToKey(req *v1.DispatchCheckRequest, option dispatchCacheKeyHashComputeOption) DispatchCacheKey { - return dispatchCacheKeyHash(checkViaRelationPrefix, req.Metadata.AtRevision, option, +func checkRequestToKey(req *v1.DispatchCheckRequest) DispatchCacheKey { + return dispatchCacheKeyHash(checkViaRelationPrefix, req.Metadata.AtRevision, hashableRelationReference{req.ResourceRelation}, hashableIds(req.ResourceIds), hashableOnr{req.Subject}, @@ -55,7 +48,7 @@ func checkRequestToKey(req *v1.DispatchCheckRequest, option dispatchCacheKeyHash // on the canonical key. func checkRequestToKeyWithCanonical(req *v1.DispatchCheckRequest, canonicalKey string) (DispatchCacheKey, error) { // NOTE: canonical cache keys are only unique *within* a version of a namespace. - cacheKey := dispatchCacheKeyHash(checkViaCanonicalPrefix, req.Metadata.AtRevision, computeBothHashes, + cacheKey := dispatchCacheKeyHash(checkViaCanonicalPrefix, req.Metadata.AtRevision, hashableString(req.ResourceRelation.Namespace), hashableString(canonicalKey), hashableIds(req.ResourceIds), @@ -71,19 +64,19 @@ func checkRequestToKeyWithCanonical(req *v1.DispatchCheckRequest, canonicalKey s } // expandRequestToKey converts an expand request into a cache key -func expandRequestToKey(req *v1.DispatchExpandRequest, option dispatchCacheKeyHashComputeOption) DispatchCacheKey { - return dispatchCacheKeyHash(expandPrefix, req.Metadata.AtRevision, option, +func expandRequestToKey(req *v1.DispatchExpandRequest) DispatchCacheKey { + return dispatchCacheKeyHash(expandPrefix, req.Metadata.AtRevision, hashableOnr{req.ResourceAndRelation}, ) } // lookupResourcesRequest2ToKey converts a lookup request into a cache key -func lookupResourcesRequest2ToKey(req *v1.DispatchLookupResources2Request, option dispatchCacheKeyHashComputeOption) (DispatchCacheKey, error) { +func lookupResourcesRequest2ToKey(req *v1.DispatchLookupResources2Request) (DispatchCacheKey, error) { stableContextString, err := caveats.StableContextStringForHashing(req.Context) if err != nil { - return DispatchCacheKey{}, err + return emptyDispatchCacheKey, err } - return dispatchCacheKeyHash(lookupPrefix, req.Metadata.AtRevision, option, + return dispatchCacheKeyHash(lookupPrefix, req.Metadata.AtRevision, hashableRelationReference{req.ResourceRelation}, hashableRelationReference{req.SubjectRelation}, hashableIds(req.SubjectIds), @@ -95,12 +88,12 @@ func lookupResourcesRequest2ToKey(req *v1.DispatchLookupResources2Request, optio } // lookupResourcesRequest3ToKey converts a lookup request into a cache key -func lookupResourcesRequest3ToKey(req *v1.DispatchLookupResources3Request, option dispatchCacheKeyHashComputeOption) (DispatchCacheKey, error) { +func lookupResourcesRequest3ToKey(req *v1.DispatchLookupResources3Request) (DispatchCacheKey, error) { stableContextString, err := caveats.StableContextStringForHashing(req.Context) if err != nil { - return DispatchCacheKey{}, err + return emptyDispatchCacheKey, err } - return dispatchCacheKeyHash(lookupPrefix, req.Metadata.AtRevision, option, + return dispatchCacheKeyHash(lookupPrefix, req.Metadata.AtRevision, hashableRelationReference{req.ResourceRelation}, hashableRelationReference{req.SubjectRelation}, hashableIds(req.SubjectIds), @@ -112,8 +105,8 @@ func lookupResourcesRequest3ToKey(req *v1.DispatchLookupResources3Request, optio } // lookupSubjectsRequestToKey converts a lookup subjects request into a cache key -func lookupSubjectsRequestToKey(req *v1.DispatchLookupSubjectsRequest, option dispatchCacheKeyHashComputeOption) DispatchCacheKey { - return dispatchCacheKeyHash(lookupSubjectsPrefix, req.Metadata.AtRevision, option, +func lookupSubjectsRequestToKey(req *v1.DispatchLookupSubjectsRequest) DispatchCacheKey { + return dispatchCacheKeyHash(lookupSubjectsPrefix, req.Metadata.AtRevision, hashableRelationReference{req.ResourceRelation}, hashableRelationReference{req.SubjectRelation}, hashableIds(req.ResourceIds), @@ -121,8 +114,8 @@ func lookupSubjectsRequestToKey(req *v1.DispatchLookupSubjectsRequest, option di } // planCheckRequestToKey converts a plan check request into a cache key -func planCheckRequestToKey(req *v1.DispatchQueryPlanRequest, option dispatchCacheKeyHashComputeOption) DispatchCacheKey { - return dispatchCacheKeyHash(planCheckPrefix, req.PlanContext.Revision, option, +func planCheckRequestToKey(req *v1.DispatchQueryPlanRequest) DispatchCacheKey { + return dispatchCacheKeyHash(planCheckPrefix, req.PlanContext.Revision, hashableString(req.CanonicalKey), hashableOnr{req.Resource}, hashableOnr{req.Subject}, @@ -131,8 +124,8 @@ func planCheckRequestToKey(req *v1.DispatchQueryPlanRequest, option dispatchCach } // planLookupResourcesRequestToKey converts a plan lookup resources request into a cache key -func planLookupResourcesRequestToKey(req *v1.DispatchQueryPlanRequest, option dispatchCacheKeyHashComputeOption) DispatchCacheKey { - return dispatchCacheKeyHash(planLookupResourcesPrefix, req.PlanContext.Revision, option, +func planLookupResourcesRequestToKey(req *v1.DispatchQueryPlanRequest) DispatchCacheKey { + return dispatchCacheKeyHash(planLookupResourcesPrefix, req.PlanContext.Revision, hashableString(req.CanonicalKey), hashableOnr{req.Subject}, hashableContext{Struct: req.PlanContext.CaveatContext}, @@ -140,8 +133,8 @@ func planLookupResourcesRequestToKey(req *v1.DispatchQueryPlanRequest, option di } // planLookupSubjectsRequestToKey converts a plan lookup subjects request into a cache key -func planLookupSubjectsRequestToKey(req *v1.DispatchQueryPlanRequest, option dispatchCacheKeyHashComputeOption) DispatchCacheKey { - return dispatchCacheKeyHash(planLookupSubjectsPrefix, req.PlanContext.Revision, option, +func planLookupSubjectsRequestToKey(req *v1.DispatchQueryPlanRequest) DispatchCacheKey { + return dispatchCacheKeyHash(planLookupSubjectsPrefix, req.PlanContext.Revision, hashableString(req.CanonicalKey), hashableOnr{req.Resource}, hashableContext{Struct: req.PlanContext.CaveatContext}, diff --git a/internal/dispatch/keys/computed_test.go b/internal/dispatch/keys/computed_test.go index a131502505..8647521a5f 100644 --- a/internal/dispatch/keys/computed_test.go +++ b/internal/dispatch/keys/computed_test.go @@ -47,7 +47,7 @@ func TestStableCacheKeys(t *testing.T) { AtRevision: "1234", SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, - }, computeBothHashes) + }) }, "e09cbca18290f7afae01", }, @@ -62,7 +62,7 @@ func TestStableCacheKeys(t *testing.T) { AtRevision: "1234", SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, - }, computeBothHashes) + }) }, "e09cbca18290f7afae01", }, @@ -77,7 +77,7 @@ func TestStableCacheKeys(t *testing.T) { AtRevision: "123456", SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, - }, computeBothHashes) + }) }, "d586cee091f9e591c301", }, @@ -106,7 +106,7 @@ func TestStableCacheKeys(t *testing.T) { AtRevision: "1234", SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, - }, computeBothHashes) + }) }, "8afff68e91a7cbb3ef01", }, @@ -119,7 +119,7 @@ func TestStableCacheKeys(t *testing.T) { AtRevision: "1234", SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, - }, computeBothHashes) + }) }, "9dd1e0c9cba88edc6b", }, @@ -132,7 +132,7 @@ func TestStableCacheKeys(t *testing.T) { AtRevision: "1235", SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, - }, computeBothHashes) + }) }, "f1b396da87bdeae2bd01", }, @@ -147,7 +147,7 @@ func TestStableCacheKeys(t *testing.T) { AtRevision: "1234", SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, - }, computeBothHashes) + }) }, "d699c5b5d3a6dfade601", }, @@ -163,7 +163,7 @@ func TestStableCacheKeys(t *testing.T) { AtRevision: "1234", SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, - }, computeBothHashes) + }) return key }, "9884bbb3acd3b3ca1a", @@ -181,7 +181,7 @@ func TestStableCacheKeys(t *testing.T) { SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, OptionalLimit: 0, - }, computeBothHashes) + }) return key }, "9884bbb3acd3b3ca1a", @@ -199,7 +199,7 @@ func TestStableCacheKeys(t *testing.T) { SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, OptionalLimit: 42, - }, computeBothHashes) + }) return key }, "dba285cdd9caeef36e", @@ -217,7 +217,7 @@ func TestStableCacheKeys(t *testing.T) { SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, Context: nil, - }, computeBothHashes) + }) return key }, "9884bbb3acd3b3ca1a", @@ -238,7 +238,7 @@ func TestStableCacheKeys(t *testing.T) { v, _ := structpb.NewStruct(map[string]any{}) return v }(), - }, computeBothHashes) + }) return key }, "9884bbb3acd3b3ca1a", @@ -262,7 +262,7 @@ func TestStableCacheKeys(t *testing.T) { }) return v }(), - }, computeBothHashes) + }) return key }, "a3dad09ce9d690b78401", @@ -286,7 +286,7 @@ func TestStableCacheKeys(t *testing.T) { }) return v }(), - }, computeBothHashes) + }) return key }, "f6d4bc92bae9e9d64b", @@ -309,7 +309,7 @@ func TestStableCacheKeys(t *testing.T) { }) return v }(), - }, computeBothHashes) + }) return key }, "a0aebfb9a8abd1b802", @@ -336,7 +336,7 @@ func TestStableCacheKeys(t *testing.T) { }) return v }(), - }, computeBothHashes) + }) return key }, "8e8ddfd8affeecc918", @@ -354,7 +354,7 @@ func TestStableCacheKeys(t *testing.T) { SchemaHash: []byte(datalayer.NoSchemaHashForTesting), }, OptionalCursor: &v1.Cursor{}, - }, computeBothHashes) + }) return key }, "9884bbb3acd3b3ca1a", @@ -374,7 +374,7 @@ func TestStableCacheKeys(t *testing.T) { OptionalCursor: &v1.Cursor{ Sections: []string{"foo"}, }, - }, computeBothHashes) + }) return key }, "9e82ddefb6ccbfd6aa01", @@ -394,7 +394,7 @@ func TestStableCacheKeys(t *testing.T) { OptionalCursor: &v1.Cursor{ Sections: []string{"foo", "bar"}, }, - }, computeBothHashes) + }) return key }, "e593e789a89a9acd13", @@ -414,7 +414,7 @@ func TestStableCacheKeys(t *testing.T) { OptionalCursor: &v1.Cursor{ Sections: []string{"foo", "bar"}, }, - }, computeBothHashes) + }) return key }, "f6cf8df7bdc7959520", @@ -434,7 +434,7 @@ func TestStableCacheKeys(t *testing.T) { OptionalCursor: &v1.Cursor{ Sections: []string{"foo", "bar"}, }, - }, computeBothHashes) + }) return key }, "de839ec8eea2f7bf19", @@ -471,7 +471,7 @@ var generatorFuncs = map[string]generatorFunc{ ResourceIds: resourceIds, Subject: ONR(subjectRelation.Namespace, subjectIds[0], subjectRelation.Relation), Metadata: metadata, - }, computeBothHashes), []string{ + }), []string{ resourceRelation.Namespace, resourceRelation.Relation, subjectRelation.Namespace, @@ -517,7 +517,7 @@ var generatorFuncs = map[string]generatorFunc{ SubjectIds: subjectIds, TerminalSubject: ONR(subjectRelation.Namespace, subjectIds[0], subjectRelation.Relation), Metadata: metadata, - }, computeBothHashes) + }) return key, []string{ resourceRelation.Namespace, resourceRelation.Relation, @@ -538,7 +538,7 @@ var generatorFuncs = map[string]generatorFunc{ return expandRequestToKey(&v1.DispatchExpandRequest{ ResourceAndRelation: ONR(resourceRelation.Namespace, resourceIds[0], resourceRelation.Relation), Metadata: metadata, - }, computeBothHashes), []string{ + }), []string{ resourceRelation.Namespace, resourceIds[0], resourceRelation.Relation, @@ -558,7 +558,7 @@ var generatorFuncs = map[string]generatorFunc{ Resource: ONR(resourceRelation.Namespace, resourceIds[0], resourceRelation.Relation), Subject: ONR(subjectRelation.Namespace, subjectIds[0], subjectRelation.Relation), PlanContext: &v1.PlanContext{Revision: metadata.AtRevision}, - }, computeBothHashes), []string{ + }), []string{ resourceRelation.Relation, resourceRelation.Namespace, resourceIds[0], @@ -580,7 +580,7 @@ var generatorFuncs = map[string]generatorFunc{ CanonicalKey: resourceRelation.Relation, Subject: ONR(subjectRelation.Namespace, subjectIds[0], subjectRelation.Relation), PlanContext: &v1.PlanContext{Revision: metadata.AtRevision}, - }, computeBothHashes), []string{ + }), []string{ resourceRelation.Relation, subjectRelation.Namespace, subjectIds[0], @@ -600,7 +600,7 @@ var generatorFuncs = map[string]generatorFunc{ CanonicalKey: resourceRelation.Relation, Resource: ONR(resourceRelation.Namespace, resourceIds[0], resourceRelation.Relation), PlanContext: &v1.PlanContext{Revision: metadata.AtRevision}, - }, computeBothHashes), []string{ + }), []string{ resourceRelation.Relation, resourceRelation.Namespace, resourceIds[0], @@ -620,7 +620,7 @@ var generatorFuncs = map[string]generatorFunc{ SubjectRelation: subjectRelation, ResourceIds: resourceIds, Metadata: metadata, - }, computeBothHashes), append([]string{ + }), append([]string{ resourceRelation.Namespace, resourceRelation.Relation, subjectRelation.Namespace, @@ -661,7 +661,6 @@ func TestCacheKeyNoOverlap(t *testing.T) { dataCombinationSeen := mapz.NewSet[string]() stableCacheKeysSeen := mapz.NewSet[string]() - unstableCacheKeysSeen := mapz.NewSet[uint64]() // Ensure all key functions are generated. require.Len(t, cachePrefixes, len(generatorFuncs)) @@ -687,7 +686,6 @@ func TestCacheKeyNoOverlap(t *testing.T) { usedDataString := fmt.Sprintf("%s:%s", prefix, strings.Join(usedData, ",")) if dataCombinationSeen.Add(usedDataString) { require.True(t, stableCacheKeysSeen.Add(hex.EncodeToString((generated.StableSumAsBytes())))) - require.True(t, unstableCacheKeysSeen.Add(generated.processSpecificSum)) } }) } @@ -703,20 +701,6 @@ func TestCacheKeyNoOverlap(t *testing.T) { } } -func TestComputeOnlyStableHash(t *testing.T) { - result := checkRequestToKey(&v1.DispatchCheckRequest{ - ResourceRelation: RR("document", "view"), - ResourceIds: []string{"foo", "bar"}, - Subject: ONR("user", "tom", "..."), - Metadata: &v1.ResolverMeta{ - AtRevision: "1234", - SchemaHash: []byte(datalayer.NoSchemaHashForTesting), - }, - }, computeOnlyStableHash) - - require.Equal(t, uint64(0), result.processSpecificSum) -} - func TestComputeContextHash(t *testing.T) { result, err := lookupResourcesRequest2ToKey(&v1.DispatchLookupResources2Request{ ResourceRelation: RR("document", "view"), @@ -741,7 +725,7 @@ func TestComputeContextHash(t *testing.T) { }) return v }(), - }, computeBothHashes) + }) require.NoError(t, err) require.Equal(t, "e49efdc8e1d99daca601", hex.EncodeToString(result.StableSumAsBytes())) diff --git a/internal/dispatch/keys/dispatchcheckkey.go b/internal/dispatch/keys/dispatchcheckkey.go index 9bd5783c02..ee815567b3 100644 --- a/internal/dispatch/keys/dispatchcheckkey.go +++ b/internal/dispatch/keys/dispatchcheckkey.go @@ -6,29 +6,18 @@ import ( // DispatchCacheKey is a struct which holds the key representing a dispatch operation being // dispatched or cached. -type DispatchCacheKey struct { - stableSum uint64 - processSpecificSum uint64 -} +type DispatchCacheKey uint64 // StableSumAsBytes returns the stable portion of the dispatch cache key as bytes. Note that since // this is only returning the result of one of the two sums, the returned bytes may not be fully // unique for the input data. func (dck DispatchCacheKey) StableSumAsBytes() []byte { - return binary.AppendUvarint(make([]byte, 0, 8), dck.stableSum) -} - -// AsUInt64s returns the cache key in the form of two uint64's. This method returns uint64s created -// from two distinct hashing algorithms, which should make the risk of key overlap incredibly -// unlikely. -func (dck DispatchCacheKey) AsUInt64s() (uint64, uint64) { - return dck.processSpecificSum, dck.stableSum + return binary.AppendUvarint(make([]byte, 0, 8), uint64(dck)) } func (dck DispatchCacheKey) KeyString() string { - firstBytes := binary.AppendUvarint(make([]byte, 0, 8), dck.stableSum) - secondBytes := binary.AppendUvarint(make([]byte, 0, 8), dck.processSpecificSum) - return string(firstBytes) + string(secondBytes) + firstBytes := binary.AppendUvarint(make([]byte, 0, 8), uint64(dck)) + return string(firstBytes) } -var emptyDispatchCacheKey = DispatchCacheKey{0, 0} +var emptyDispatchCacheKey = DispatchCacheKey(0) diff --git a/internal/dispatch/keys/hasher_common.go b/internal/dispatch/keys/hasher.go similarity index 65% rename from internal/dispatch/keys/hasher_common.go rename to internal/dispatch/keys/hasher.go index 9f632b3162..71c56a560e 100644 --- a/internal/dispatch/keys/hasher_common.go +++ b/internal/dispatch/keys/hasher.go @@ -1,9 +1,11 @@ package keys import ( + "fmt" "slices" "strconv" + "github.com/cespare/xxhash/v2" "google.golang.org/protobuf/types/known/structpb" "github.com/authzed/spicedb/pkg/caveats" @@ -117,3 +119,50 @@ func (hc hashableContext) AppendToHash(hasher hasherInterface) { } hasher.WriteString(stable) } + +// dispatchCacheKeyHash computes a DispatchCheckKey for the given prefix and any hashable values. +func dispatchCacheKeyHash(prefix cachePrefix, atRevision string, args ...hashableValue) DispatchCacheKey { + hasher := newDispatchCacheKeyHasher(prefix) + + for _, arg := range args { + arg.AppendToHash(hasher) + hasher.WriteString("@") + } + + hasher.WriteString(atRevision) + return hasher.BuildKey() +} + +type dispatchCacheKeyHasher struct { + stableHasher *xxhash.Digest +} + +func newDispatchCacheKeyHasher(prefix cachePrefix) *dispatchCacheKeyHasher { + h := &dispatchCacheKeyHasher{ + stableHasher: xxhash.New(), + } + + prefixString := string(prefix) + h.WriteString(prefixString) + h.WriteString("/") + return h +} + +// WriteString writes a single string to the hasher. +func (h *dispatchCacheKeyHasher) WriteString(value string) { + h.mustWriteString(value) +} + +func (h *dispatchCacheKeyHasher) mustWriteString(value string) { + // NOTE: xxhash doesn't seem to ever return an error for WriteString, but we check it just + // to be on the safe side. + _, err := h.stableHasher.WriteString(value) + if err != nil { + panic(fmt.Errorf("got an error from writing to the stable hasher: %w", err)) + } +} + +// BuildKey returns the constructed DispatchCheckKey. +func (h *dispatchCacheKeyHasher) BuildKey() DispatchCacheKey { + return DispatchCacheKey(h.stableHasher.Sum64()) +} diff --git a/internal/dispatch/keys/hasher_ristretto.go b/internal/dispatch/keys/hasher_ristretto.go deleted file mode 100644 index b40ff375f8..0000000000 --- a/internal/dispatch/keys/hasher_ristretto.go +++ /dev/null @@ -1,82 +0,0 @@ -//go:build !wasm - -package keys - -import ( - "fmt" - "unsafe" - - "github.com/cespare/xxhash/v2" -) - -// dispatchCacheKeyHash computes a DispatchCheckKey for the given prefix and any hashable values. -func dispatchCacheKeyHash(prefix cachePrefix, atRevision string, computeOption dispatchCacheKeyHashComputeOption, args ...hashableValue) DispatchCacheKey { - hasher := newDispatchCacheKeyHasher(prefix, computeOption) - - for _, arg := range args { - arg.AppendToHash(hasher) - hasher.WriteString("@") - } - - hasher.WriteString(atRevision) - return hasher.BuildKey() -} - -type dispatchCacheKeyHasher struct { - stableHasher *xxhash.Digest - computeOption dispatchCacheKeyHashComputeOption - processSpecificSum uint64 -} - -func newDispatchCacheKeyHasher(prefix cachePrefix, computeOption dispatchCacheKeyHashComputeOption) *dispatchCacheKeyHasher { - h := &dispatchCacheKeyHasher{ - stableHasher: xxhash.New(), - computeOption: computeOption, - } - - prefixString := string(prefix) - h.WriteString(prefixString) - h.WriteString("/") - return h -} - -// WriteString writes a single string to the hasher. -func (h *dispatchCacheKeyHasher) WriteString(value string) { - h.mustWriteString(value) -} - -func (h *dispatchCacheKeyHasher) mustWriteString(value string) { - // NOTE: xxhash doesn't seem to ever return an error for WriteString, but we check it just - // to be on the safe side. - _, err := h.stableHasher.WriteString(value) - if err != nil { - panic(fmt.Errorf("got an error from writing to the stable hasher: %w", err)) - } - - if h.computeOption == computeBothHashes { - h.processSpecificSum = runMemHash(h.processSpecificSum, []byte(value)) - } -} - -// From: https://github.com/outcaste-io/ristretto/blob/master/z/rtutil.go -type stringStruct struct { - str unsafe.Pointer - len int -} - -//go:noescape -//go:linkname memhash runtime.memhash -func memhash(p unsafe.Pointer, h, s uintptr) uintptr - -func runMemHash(seed uint64, data []byte) uint64 { - ss := (*stringStruct)(unsafe.Pointer(&data)) - return uint64(memhash(ss.str, uintptr(seed), uintptr(ss.len))) //nolint:gosec -} - -// BuildKey returns the constructed DispatchCheckKey. -func (h *dispatchCacheKeyHasher) BuildKey() DispatchCacheKey { - return DispatchCacheKey{ - stableSum: h.stableHasher.Sum64(), - processSpecificSum: h.processSpecificSum, - } -} diff --git a/internal/dispatch/keys/hasher_wasm.go b/internal/dispatch/keys/hasher_wasm.go deleted file mode 100644 index c89aa015da..0000000000 --- a/internal/dispatch/keys/hasher_wasm.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build wasm - -package keys - -func dispatchCacheKeyHash(prefix cachePrefix, atRevision string, computeOption dispatchCacheKeyHashComputeOption, args ...hashableValue) DispatchCacheKey { - panic("Caching is not implemented under WASM") -} diff --git a/internal/dispatch/keys/keys.go b/internal/dispatch/keys/keys.go index da32711c97..8bdea65f31 100644 --- a/internal/dispatch/keys/keys.go +++ b/internal/dispatch/keys/keys.go @@ -62,27 +62,27 @@ type Handler interface { type baseKeyHandler struct{} func (b baseKeyHandler) LookupResources2CacheKey(_ context.Context, req *v1.DispatchLookupResources2Request) (DispatchCacheKey, error) { - return lookupResourcesRequest2ToKey(req, computeBothHashes) + return lookupResourcesRequest2ToKey(req) } func (b baseKeyHandler) LookupResources3CacheKey(_ context.Context, req *v1.DispatchLookupResources3Request) (DispatchCacheKey, error) { - return lookupResourcesRequest3ToKey(req, computeBothHashes) + return lookupResourcesRequest3ToKey(req) } func (b baseKeyHandler) LookupSubjectsCacheKey(_ context.Context, req *v1.DispatchLookupSubjectsRequest) (DispatchCacheKey, error) { - return lookupSubjectsRequestToKey(req, computeBothHashes), nil + return lookupSubjectsRequestToKey(req), nil } func (b baseKeyHandler) ExpandCacheKey(_ context.Context, req *v1.DispatchExpandRequest) (DispatchCacheKey, error) { - return expandRequestToKey(req, computeBothHashes), nil + return expandRequestToKey(req), nil } func (b baseKeyHandler) CheckDispatchKey(_ context.Context, req *v1.DispatchCheckRequest) ([]byte, error) { - return checkRequestToKey(req, computeOnlyStableHash).StableSumAsBytes(), nil + return checkRequestToKey(req).StableSumAsBytes(), nil } func (b baseKeyHandler) LookupResources2DispatchKey(_ context.Context, req *v1.DispatchLookupResources2Request) ([]byte, error) { - hash, err := lookupResourcesRequest2ToKey(req, computeOnlyStableHash) + hash, err := lookupResourcesRequest2ToKey(req) if err != nil { return nil, err } @@ -90,7 +90,7 @@ func (b baseKeyHandler) LookupResources2DispatchKey(_ context.Context, req *v1.D } func (b baseKeyHandler) LookupResources3DispatchKey(_ context.Context, req *v1.DispatchLookupResources3Request) ([]byte, error) { - hash, err := lookupResourcesRequest3ToKey(req, computeOnlyStableHash) + hash, err := lookupResourcesRequest3ToKey(req) if err != nil { return nil, err } @@ -98,35 +98,35 @@ func (b baseKeyHandler) LookupResources3DispatchKey(_ context.Context, req *v1.D } func (b baseKeyHandler) LookupSubjectsDispatchKey(_ context.Context, req *v1.DispatchLookupSubjectsRequest) ([]byte, error) { - return lookupSubjectsRequestToKey(req, computeOnlyStableHash).StableSumAsBytes(), nil + return lookupSubjectsRequestToKey(req).StableSumAsBytes(), nil } func (b baseKeyHandler) ExpandDispatchKey(_ context.Context, req *v1.DispatchExpandRequest) ([]byte, error) { - return expandRequestToKey(req, computeOnlyStableHash).StableSumAsBytes(), nil + return expandRequestToKey(req).StableSumAsBytes(), nil } func (b baseKeyHandler) PlanCheckCacheKey(_ context.Context, req *v1.DispatchQueryPlanRequest) (DispatchCacheKey, error) { - return planCheckRequestToKey(req, computeBothHashes), nil + return planCheckRequestToKey(req), nil } func (b baseKeyHandler) PlanCheckDispatchKey(_ context.Context, req *v1.DispatchQueryPlanRequest) ([]byte, error) { - return planCheckRequestToKey(req, computeOnlyStableHash).StableSumAsBytes(), nil + return planCheckRequestToKey(req).StableSumAsBytes(), nil } func (b baseKeyHandler) PlanLookupResourcesCacheKey(_ context.Context, req *v1.DispatchQueryPlanRequest) (DispatchCacheKey, error) { - return planLookupResourcesRequestToKey(req, computeBothHashes), nil + return planLookupResourcesRequestToKey(req), nil } func (b baseKeyHandler) PlanLookupResourcesDispatchKey(_ context.Context, req *v1.DispatchQueryPlanRequest) ([]byte, error) { - return planLookupResourcesRequestToKey(req, computeOnlyStableHash).StableSumAsBytes(), nil + return planLookupResourcesRequestToKey(req).StableSumAsBytes(), nil } func (b baseKeyHandler) PlanLookupSubjectsCacheKey(_ context.Context, req *v1.DispatchQueryPlanRequest) (DispatchCacheKey, error) { - return planLookupSubjectsRequestToKey(req, computeBothHashes), nil + return planLookupSubjectsRequestToKey(req), nil } func (b baseKeyHandler) PlanLookupSubjectsDispatchKey(_ context.Context, req *v1.DispatchQueryPlanRequest) ([]byte, error) { - return planLookupSubjectsRequestToKey(req, computeOnlyStableHash).StableSumAsBytes(), nil + return planLookupSubjectsRequestToKey(req).StableSumAsBytes(), nil } // DirectKeyHandler is a key handler that uses the relation name itself as the key. @@ -135,7 +135,7 @@ type DirectKeyHandler struct { } func (d *DirectKeyHandler) CheckCacheKey(_ context.Context, req *v1.DispatchCheckRequest) (DispatchCacheKey, error) { - return checkRequestToKey(req, computeBothHashes), nil + return checkRequestToKey(req), nil } // CanonicalKeyHandler is a key handler which makes use of the canonical key for relations for @@ -178,5 +178,5 @@ func (c *CanonicalKeyHandler) CheckCacheKey(ctx context.Context, req *v1.Dispatc } } - return checkRequestToKey(req, computeBothHashes), nil + return checkRequestToKey(req), nil } diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 23195be060..e356073a05 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -23,19 +23,10 @@ func (sk StringKey) KeyString() string { } // Config for caching. -// See: https://github.com/outcaste-io/ristretto#Config type Config struct { - // NumCounters determines the number of counters (keys) to keep that hold - // access frequency information. It's generally a good idea to have more - // counters than the max cache capacity, as this will improve eviction - // accuracy and subsequent hit ratios. - // - // For example, if you expect your cache to hold 1,000,000 items when full, - // NumCounters should be 10,000,000 (10x). Each counter takes up roughly - // 3 bytes (4 bits for each counter * 4 copies plus about a byte per - // counter for the bloom filter). Note that the number of counters is - // internally rounded up to the nearest power of 2, so the space usage - // may be a little larger than 3 bytes * NumCounters. + // Deprecated: NumCounters was used to control behavior of the cache + // when the underlying cache exposed it, but the current cache implementation + // does not use it. NumCounters int64 // MaxCost can be considered as the cache capacity, in whatever units you @@ -57,7 +48,6 @@ func (c *Config) MarshalZerologObject(e *zerolog.Event) { maxCost := spiceerrors.MustSafecast[uint64](c.MaxCost) e. Str("maxCost", humanize.IBytes(maxCost)). - Int64("numCounters", c.NumCounters). Dur("defaultTTL", c.DefaultTTL) } diff --git a/pkg/cache/cache_otter.go b/pkg/cache/cache_otter.go index dd1c37dd17..4c64e3852f 100644 --- a/pkg/cache/cache_otter.go +++ b/pkg/cache/cache_otter.go @@ -18,6 +18,9 @@ func NewOtterCacheWithMetrics[K KeyString, V any](name string, config *Config) ( if err != nil { return nil, err } + // NOTE: this is the difference between `WithMetrics` and not - + // the counters are instantiated either way, but they're only registered + // in this variant. mustRegisterCache(name, cache) return cache, nil } @@ -96,6 +99,10 @@ func (wtc *otterCache[K, V]) Set(key K, value V, cost int64) bool { func (wtc *otterCache[K, V]) Wait() {} func (wtc *otterCache[K, V]) Close() { + // NOTE: CleanUp is *not* the same as Close - otter/v2 doesn't expose a Close + // method. It should help reduce resource usage e.g. in tests, but there will + // still be a periodicCleanup goroutine hanging around. + wtc.cache.CleanUp() unregisterCache(wtc.name) } diff --git a/pkg/cache/cache_ristretto.go b/pkg/cache/cache_ristretto.go deleted file mode 100644 index dd9b97982f..0000000000 --- a/pkg/cache/cache_ristretto.go +++ /dev/null @@ -1,99 +0,0 @@ -//go:build !wasm - -package cache - -import ( - "time" - - "github.com/outcaste-io/ristretto" - "github.com/outcaste-io/ristretto/z" - "github.com/rs/zerolog" - - "github.com/authzed/spicedb/internal/dispatch/keys" -) - -type keyStringer interface { - KeyString() string -} - -func ristrettoConfig(config *Config) *ristretto.Config { - return &ristretto.Config{ - NumCounters: config.NumCounters, - MaxCost: config.MaxCost, - BufferItems: 64, // Recommended constant by Ristretto authors. - KeyToHash: func(key any) (uint64, uint64) { - dispatchCacheKey, ok := key.(keys.DispatchCacheKey) - if !ok { - if ks, ok := key.(keyStringer); ok { - return z.KeyToHash(ks.KeyString()) - } - - return z.KeyToHash(key) - } - return dispatchCacheKey.AsUInt64s() - }, - } -} - -// NewRistrettoCacheWithMetrics creates a new ristretto cache from the given config -// that also reports metrics to the default Prometheus registry. -func NewRistrettoCacheWithMetrics[K KeyString, V any](name string, config *Config) (Cache[K, V], error) { - cfg := ristrettoConfig(config) - cfg.Metrics = true - - rcache, err := ristretto.NewCache(cfg) - if err != nil { - return nil, err - } - - cache := ristretoCache[K, V]{name, config, config.DefaultTTL, rcache} - mustRegisterCache(name, cache) - return &cache, nil -} - -// NewRistrettoCache creates a new ristretto cache from the given config. -func NewRistrettoCache[K KeyString, V any](config *Config) (Cache[K, V], error) { - rcache, err := ristretto.NewCache(ristrettoConfig(config)) - return &ristretoCache[K, V]{"", config, config.DefaultTTL, rcache}, err -} - -type ristretoCache[K any, V any] struct { - name string - config *Config - defaultTTL time.Duration - ristretto *ristretto.Cache -} - -func (w ristretoCache[K, V]) Set(key K, entry V, cost int64) bool { - if w.defaultTTL <= 0 { - return w.ristretto.Set(key, entry, cost) - } - return w.ristretto.SetWithTTL(key, entry, cost, w.defaultTTL) -} - -func (w ristretoCache[K, V]) Get(key K) (V, bool) { - found, ok := w.ristretto.Get(key) - if !ok { - return *new(V), false - } - - return found.(V), true -} - -func (w ristretoCache[K, V]) Wait() { - w.ristretto.Wait() -} - -func (w ristretoCache[K, V]) GetTTL() time.Duration { - return w.defaultTTL -} - -var _ Cache[StringKey, any] = (*ristretoCache[StringKey, any])(nil) - -func (w ristretoCache[K, V]) GetMetrics() Metrics { return w.ristretto.Metrics } -func (w ristretoCache[K, V]) MarshalZerologObject(e *zerolog.Event) { e.EmbedObject(w.config) } - -func (w ristretoCache[K, V]) Close() { - w.ristretto.Close() - unregisterCache(w.name) -} diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go index 8f831c96d2..b069d13117 100644 --- a/pkg/cache/cache_test.go +++ b/pkg/cache/cache_test.go @@ -1,5 +1,3 @@ -//go:build !wasm - package cache import ( @@ -11,34 +9,12 @@ import ( func TestCacheWithMetrics(t *testing.T) { config := &Config{ - NumCounters: 10000, - MaxCost: 1000, - DefaultTTL: 10 * time.Hour, + MaxCost: 1000, + DefaultTTL: 10 * time.Hour, } - t.Run("otter", func(t *testing.T) { - testCacheImplementation(t, func() (Cache[StringKey, string], error) { - return NewOtterCacheWithMetrics[StringKey, string]("test-otter", config) - }) - }) - - t.Run("ristretto", func(t *testing.T) { - testCacheImplementation(t, func() (Cache[StringKey, string], error) { - // Use the metrics version for proper metrics tracking - return NewRistrettoCacheWithMetrics[StringKey, string]("test-ristretto", config) - }) - }) - - t.Run("theine", func(t *testing.T) { - testCacheImplementation(t, func() (Cache[StringKey, string], error) { - return NewTheineCacheWithMetrics[StringKey, string]("test-theine", config) - }) - }) -} - -func testCacheImplementation(t *testing.T, factory func() (Cache[StringKey, string], error)) { t.Run("Set and Get", func(t *testing.T) { - cache, err := factory() + cache, err := NewOtterCacheWithMetrics[StringKey, string]("test-otter", config) require.NoError(t, err) defer cache.Close() @@ -69,7 +45,7 @@ func testCacheImplementation(t *testing.T, factory func() (Cache[StringKey, stri }) t.Run("Set same key with diff values", func(t *testing.T) { - cache, err := factory() + cache, err := NewOtterCacheWithMetrics[StringKey, string]("test-otter", config) require.NoError(t, err) defer cache.Close() @@ -90,7 +66,7 @@ func testCacheImplementation(t *testing.T, factory func() (Cache[StringKey, stri }) t.Run("Close multiple times", func(t *testing.T) { - cache, err := factory() + cache, err := NewOtterCacheWithMetrics[StringKey, string]("test-otter", config) require.NoError(t, err) for range 10 { @@ -99,7 +75,7 @@ func testCacheImplementation(t *testing.T, factory func() (Cache[StringKey, stri }) t.Run("GetTTL", func(t *testing.T) { - cache, err := factory() + cache, err := NewOtterCacheWithMetrics[StringKey, string]("test-otter", config) require.NoError(t, err) defer cache.Close() @@ -107,7 +83,7 @@ func testCacheImplementation(t *testing.T, factory func() (Cache[StringKey, stri }) t.Run("GetMetrics", func(t *testing.T) { - cache, err := factory() + cache, err := NewOtterCacheWithMetrics[StringKey, string]("test-otter", config) require.NoError(t, err) defer cache.Close() diff --git a/pkg/cache/cache_theine.go b/pkg/cache/cache_theine.go deleted file mode 100644 index a022442a00..0000000000 --- a/pkg/cache/cache_theine.go +++ /dev/null @@ -1,81 +0,0 @@ -//go:build !wasm - -package cache - -import ( - "math" - "sync" - "sync/atomic" - "time" - - "github.com/Yiling-J/theine-go" - "github.com/ccoveille/go-safecast/v2" - "github.com/rs/zerolog" -) - -func NewTheineCacheWithMetrics[K KeyString, V any](name string, config *Config) (Cache[K, V], error) { - builder := theine.NewBuilder[K, V](config.MaxCost) - built, err := builder.Build() - if err != nil { - return nil, err - } - cc := theineCache[K, V]{name, built, config.DefaultTTL, sync.Once{}, theineMetrics[K, V]{atomic.Uint64{}, built}} - mustRegisterCache(name, &cc) - return &cc, nil -} - -type theineCache[K KeyString, V any] struct { - name string - cache *theine.Cache[K, V] - defaultTTL time.Duration - closed sync.Once - metrics theineMetrics[K, V] -} - -func (wtc *theineCache[K, V]) GetTTL() time.Duration { - return wtc.defaultTTL -} - -func (wtc *theineCache[K, V]) Get(key K) (V, bool) { - return wtc.cache.Get(key) -} - -func (wtc *theineCache[K, V]) Set(key K, value V, cost int64) bool { - uintCost, err := safecast.Convert[uint64](cost) - if err != nil { - // We make an assumption that if the cast fails, it's because the value - // was too big, so we set to maxint in that case. - uintCost = math.MaxUint32 - } - wtc.metrics.costAdded.Add(uintCost) - if wtc.defaultTTL <= 0 { - return wtc.cache.Set(key, value, cost) - } - return wtc.cache.SetWithTTL(key, value, cost, wtc.defaultTTL) -} - -func (wtc *theineCache[K, V]) Wait() { - // No-op because theine doesn't have a wait function. -} - -func (wtc *theineCache[K, V]) Close() { - wtc.closed.Do(func() { - wtc.cache.Close() - }) - unregisterCache(wtc.name) -} - -func (wtc *theineCache[K, V]) GetMetrics() Metrics { return &wtc.metrics } -func (wtc *theineCache[K, V]) MarshalZerologObject(e *zerolog.Event) { - e.Bool("theine", true) -} - -type theineMetrics[K KeyString, V any] struct { - costAdded atomic.Uint64 - cache *theine.Cache[K, V] -} - -func (tm *theineMetrics[K, V]) CostAdded() uint64 { return tm.costAdded.Load() } -func (tm *theineMetrics[K, V]) CostEvicted() uint64 { return 0 } -func (tm *theineMetrics[K, V]) Hits() uint64 { return tm.cache.Stats().Hits() } -func (tm *theineMetrics[K, V]) Misses() uint64 { return tm.cache.Stats().Misses() } diff --git a/pkg/cache/cache_wasm.go b/pkg/cache/cache_wasm.go index f524892ede..4b3e175402 100644 --- a/pkg/cache/cache_wasm.go +++ b/pkg/cache/cache_wasm.go @@ -1,3 +1,5 @@ +//go:build wasm + package cache import ( @@ -14,14 +16,6 @@ func NewStandardCacheWithMetrics[K KeyString, V any](name string, config *Config return nil, fmt.Errorf("caching is currently unsupported in WASM") } -func NewRistrettoCache[K KeyString, V any](config *Config) (Cache[K, V], error) { - return nil, fmt.Errorf("caching is currently unsupported in WASM") -} - -func NewTheineCache[K KeyString, V any](config *Config) (Cache[K, V], error) { - return nil, fmt.Errorf("caching is currently unsupported in WASM") -} - func NewOtterCache[K KeyString, V any](name string, config *Config) (Cache[K, V], error) { return nil, fmt.Errorf("caching is currently unsupported in WASM") } diff --git a/pkg/cache/standard.go b/pkg/cache/standard.go index 8bde17aa09..8faa5103e2 100644 --- a/pkg/cache/standard.go +++ b/pkg/cache/standard.go @@ -1,12 +1,20 @@ //go:build !wasm +/* +NOTE: This is the non-wasm path because the otter package contains +references to runtime code that works differently in the wasm environment +than in a "standard" golang environment and therefore cannot be built for +wasm. +*/ + package cache // NewStandardCache creates a new cache with the given configuration. func NewStandardCache[K KeyString, V any](config *Config) (Cache[K, V], error) { - return NewRistrettoCache[K, V](config) + // TODO: check name here + return NewOtterCache[K, V]("", config) } func NewStandardCacheWithMetrics[K KeyString, V any](name string, config *Config) (Cache[K, V], error) { - return NewRistrettoCacheWithMetrics[K, V](name, config) + return NewOtterCacheWithMetrics[K, V](name, config) } diff --git a/pkg/cmd/serve.go b/pkg/cmd/serve.go index 5048097ce9..846c920dd3 100644 --- a/pkg/cmd/serve.go +++ b/pkg/cmd/serve.go @@ -24,48 +24,43 @@ const PresharedKeyFlag = "grpc-preshared-key" var ( namespaceCacheDefaults = &server.CacheConfig{ - Name: "namespace", - Enabled: true, - Metrics: true, - NumCounters: 1_000, - MaxCost: "32MiB", - CacheKindForTesting: "", + Name: "namespace", + Enabled: true, + Metrics: true, + NumCounters: 1_000, + MaxCost: "32MiB", } dispatchCacheDefaults = &server.CacheConfig{ - Name: "dispatch", - Enabled: true, - Metrics: true, - NumCounters: 10_000, - MaxCost: "30%", - CacheKindForTesting: "", + Name: "dispatch", + Enabled: true, + Metrics: true, + NumCounters: 10_000, + MaxCost: "30%", } dispatchClusterCacheDefaults = &server.CacheConfig{ - Name: "cluster_dispatch", - Enabled: true, - Metrics: true, - NumCounters: 100_000, - MaxCost: "70%", - CacheKindForTesting: "", + Name: "cluster_dispatch", + Enabled: true, + Metrics: true, + NumCounters: 100_000, + MaxCost: "70%", } lr3ChunkCacheDefaults = &server.CacheConfig{ - Name: "lr3_chunk", - Enabled: true, - Metrics: false, - NumCounters: 10_000, - MaxCost: "50MiB", - CacheKindForTesting: "", + Name: "lr3_chunk", + Enabled: true, + Metrics: false, + NumCounters: 10_000, + MaxCost: "50MiB", } storedSchemaCacheDefaults = &server.CacheConfig{ - Name: "stored_schema", - Enabled: true, - Metrics: true, - NumCounters: 1_000, - MaxCost: "32MiB", - CacheKindForTesting: "", + Name: "stored_schema", + Enabled: true, + Metrics: true, + NumCounters: 1_000, + MaxCost: "32MiB", } ) diff --git a/pkg/cmd/server/cacheconfig.go b/pkg/cmd/server/cacheconfig.go index 8d201ac60e..aa982abeeb 100644 --- a/pkg/cmd/server/cacheconfig.go +++ b/pkg/cmd/server/cacheconfig.go @@ -25,13 +25,13 @@ var errOverHundredPercent = errors.New("percentage greater than 100") // //go:generate go run github.com/ecordell/optgen -output zz_generated.cacheconfig.options.go . CacheConfig type CacheConfig struct { - Name string `debugmap:"visible"` - MaxCost string `debugmap:"visible"` - NumCounters int64 `debugmap:"visible"` - Metrics bool `debugmap:"visible"` - Enabled bool `debugmap:"visible"` - defaultTTL time.Duration `debugmap:"visible"` - CacheKindForTesting string `debugmap:"visible"` + Name string `debugmap:"visible"` + MaxCost string `debugmap:"visible"` + // Deprecated: NumCounters is no longer used with the current cache implementation. + NumCounters int64 `debugmap:"visible"` + Metrics bool `debugmap:"visible"` + Enabled bool `debugmap:"visible"` + defaultTTL time.Duration `debugmap:"visible"` } // WithRevisionParameters configures a cache such that all entries are given a TTL @@ -48,7 +48,7 @@ func (cc *CacheConfig) WithRevisionParameters( // CompleteCache translates the CLI cache config into a cache config. func CompleteCache[K cache.KeyString, V any](cc *CacheConfig) (cache.Cache[K, V], error) { - if !cc.Enabled || cc.MaxCost == "" || cc.MaxCost == "0%" || cc.NumCounters == 0 { + if !cc.Enabled || cc.MaxCost == "" || cc.MaxCost == "0%" { return cache.NoopCache[K, V](), nil } @@ -71,46 +71,16 @@ func CompleteCache[K cache.KeyString, V any](cc *CacheConfig) (cache.Cache[K, V] return nil, errors.New("could not cast max cost to int64") } - if cc.CacheKindForTesting != "" { - switch cc.CacheKindForTesting { - case "theine": - return cache.NewTheineCacheWithMetrics[K, V](cc.Name, &cache.Config{ - MaxCost: intMaxCost, - NumCounters: cc.NumCounters, - DefaultTTL: cc.defaultTTL, - }) - - case "otter": - if cc.Metrics { - return cache.NewOtterCacheWithMetrics[K, V](cc.Name, &cache.Config{ - MaxCost: intMaxCost, - NumCounters: cc.NumCounters, - DefaultTTL: cc.defaultTTL, - }) - } - return cache.NewOtterCache[K, V](cc.Name, &cache.Config{ - MaxCost: intMaxCost, - NumCounters: cc.NumCounters, - DefaultTTL: cc.defaultTTL, - }) - - default: - return nil, fmt.Errorf("unknown cache kind: %s", cc.CacheKindForTesting) - } - } - if cc.Metrics { return cache.NewStandardCacheWithMetrics[K, V](cc.Name, &cache.Config{ - MaxCost: intMaxCost, - NumCounters: cc.NumCounters, - DefaultTTL: cc.defaultTTL, + MaxCost: intMaxCost, + DefaultTTL: cc.defaultTTL, }) } return cache.NewStandardCache[K, V](&cache.Config{ - MaxCost: intMaxCost, - NumCounters: cc.NumCounters, - DefaultTTL: cc.defaultTTL, + MaxCost: intMaxCost, + DefaultTTL: cc.defaultTTL, }) } @@ -133,14 +103,13 @@ func RegisterCacheFlags(flags *pflag.FlagSet, flagPrefix, flagDescription string config.Name = defaults.Name flagPrefix = cmp.Or(flagPrefix, "cache") flags.StringVar(&config.MaxCost, flagPrefix+"-max-cost", defaults.MaxCost, "upper bound (in bytes or as a percent of available memory) of the cache for "+flagDescription) - flags.Int64Var(&config.NumCounters, flagPrefix+"-num-counters", defaults.NumCounters, "number of counters for tracking access frequency in the cache for "+flagDescription+". A higher number means more accurate eviction decisions but more memory usage") + flags.Int64Var(&config.NumCounters, flagPrefix+"-num-counters", 0, "number of counters for tracking access frequency in the cache for "+flagDescription+". A higher number means more accurate eviction decisions but more memory usage") + err := flags.MarkDeprecated(flagPrefix+"-num-counters", "this flag is now unused") + if err != nil { + return err + } flags.BoolVar(&config.Metrics, flagPrefix+"-metrics", defaults.Metrics, "enable metrics for the cache for "+flagDescription) flags.BoolVar(&config.Enabled, flagPrefix+"-enabled", defaults.Enabled, "enable caching of "+flagDescription) - // Hidden flags. - flags.StringVar(&config.CacheKindForTesting, flagPrefix+"-kind-for-testing", defaults.CacheKindForTesting, "choose a different kind of cache, for testing") - if err := flags.MarkHidden(flagPrefix + "-kind-for-testing"); err != nil { - return err - } return nil } diff --git a/pkg/cmd/server/zz_generated.cacheconfig.options.go b/pkg/cmd/server/zz_generated.cacheconfig.options.go index f37638a66f..c58035d3a3 100644 --- a/pkg/cmd/server/zz_generated.cacheconfig.options.go +++ b/pkg/cmd/server/zz_generated.cacheconfig.options.go @@ -32,7 +32,6 @@ func (c *CacheConfig) ToOption() CacheConfigOption { to.NumCounters = c.NumCounters to.Metrics = c.Metrics to.Enabled = c.Enabled - to.CacheKindForTesting = c.CacheKindForTesting } } @@ -52,11 +51,6 @@ func (c *CacheConfig) DebugMap() map[string]any { debugMap["NumCounters"] = c.NumCounters debugMap["Metrics"] = c.Metrics debugMap["Enabled"] = c.Enabled - if c.CacheKindForTesting == "" { - debugMap["CacheKindForTesting"] = "(empty)" - } else { - debugMap["CacheKindForTesting"] = c.CacheKindForTesting - } return debugMap } @@ -131,10 +125,3 @@ func WithEnabled(enabled bool) CacheConfigOption { c.Enabled = enabled } } - -// WithCacheKindForTesting returns an option that can set CacheKindForTesting on a CacheConfig -func WithCacheKindForTesting(cacheKindForTesting string) CacheConfigOption { - return func(c *CacheConfig) { - c.CacheKindForTesting = cacheKindForTesting - } -} diff --git a/pkg/testutil/leakdetection.go b/pkg/testutil/leakdetection.go index afe50213a4..86f0516d7f 100644 --- a/pkg/testutil/leakdetection.go +++ b/pkg/testutil/leakdetection.go @@ -9,5 +9,8 @@ func GoLeakIgnores() []goleak.Option { // TODO: https://github.com/googleapis/google-cloud-go/issues/14228 // when that is complete, remove this line goleak.IgnoreAnyFunction("go.opencensus.io/stats/view.(*worker).start"), + // Otter doesn't expose a `close` function, so we can't close down its periodicCleanup + // function. See https://github.com/maypok86/otter/issues/181 + goleak.IgnoreAnyFunction("github.com/maypok86/otter/v2.(*cache[...]).periodicCleanUp"), } }