diff --git a/db/revision_cache_interface.go b/db/revision_cache_interface.go index 891c8e1b01..3441108a88 100644 --- a/db/revision_cache_interface.go +++ b/db/revision_cache_interface.go @@ -68,7 +68,7 @@ func NewRevisionCache(cacheOptions *RevisionCacheOptions, backingStores map[uint cacheOptions = DefaultRevisionCacheOptions() } - if cacheOptions.Size == 0 { + if cacheOptions.MaxItemCount == 0 { bypassStat := cacheStats.RevisionCacheBypass return NewBypassRevisionCache(backingStores, bypassStat) } @@ -78,21 +78,22 @@ func NewRevisionCache(cacheOptions *RevisionCacheOptions, backingStores map[uint cacheNumItemsStat := cacheStats.RevisionCacheNumItems if cacheOptions.ShardCount > 1 { - return NewShardedLRURevisionCache(cacheOptions.ShardCount, cacheOptions.Size, backingStores, cacheHitStat, cacheMissStat, cacheNumItemsStat) + return NewShardedLRURevisionCache(cacheOptions.ShardCount, cacheOptions.MaxItemCount, backingStores, cacheHitStat, cacheMissStat, cacheNumItemsStat) } - return NewLRURevisionCache(cacheOptions.Size, backingStores, cacheHitStat, cacheMissStat, cacheNumItemsStat) + return NewLRURevisionCache(cacheOptions.MaxItemCount, backingStores, cacheHitStat, cacheMissStat, cacheNumItemsStat) } type RevisionCacheOptions struct { - Size uint32 - ShardCount uint16 + MaxItemCount uint32 + MaxBytes int64 + ShardCount uint16 } func DefaultRevisionCacheOptions() *RevisionCacheOptions { return &RevisionCacheOptions{ - Size: DefaultRevisionCacheSize, - ShardCount: DefaultRevisionCacheShardCount, + MaxItemCount: DefaultRevisionCacheSize, + ShardCount: DefaultRevisionCacheShardCount, } } diff --git a/db/revision_cache_test.go b/db/revision_cache_test.go index 2b41e2d005..d6a42eb5a8 100644 --- a/db/revision_cache_test.go +++ b/db/revision_cache_test.go @@ -562,7 +562,7 @@ func TestRevCacheHitMultiCollectionLoadFromBucket(t *testing.T) { // create database context with rev cache size 1 dbOptions := DatabaseContextOptions{ RevisionCacheOptions: &RevisionCacheOptions{ - Size: 1, + MaxItemCount: 1, }, } dbOptions.Scopes = GetScopesOptions(t, tb, 2) diff --git a/rest/access_test.go b/rest/access_test.go index 116399e229..86b870fe93 100644 --- a/rest/access_test.go +++ b/rest/access_test.go @@ -285,7 +285,7 @@ func TestUserHasDocAccessDocNotFound(t *testing.T) { QueryPaginationLimit: base.IntPtr(2), CacheConfig: &CacheConfig{ RevCacheConfig: &RevCacheConfig{ - Size: base.Uint32Ptr(0), + MaxItemCount: base.Uint32Ptr(0), }, ChannelCacheConfig: &ChannelCacheConfig{ MaxNumber: base.IntPtr(0), diff --git a/rest/adminapitest/admin_api_test.go b/rest/adminapitest/admin_api_test.go index 8be26ea55c..42edfef91f 100644 --- a/rest/adminapitest/admin_api_test.go +++ b/rest/adminapitest/admin_api_test.go @@ -2547,7 +2547,7 @@ func TestHandleDBConfig(t *testing.T) { dbConfig := rt.NewDbConfig() dbConfig.CacheConfig = &rest.CacheConfig{ RevCacheConfig: &rest.RevCacheConfig{ - Size: base.Uint32Ptr(1337), ShardCount: base.Uint16Ptr(7), + MaxItemCount: base.Uint32Ptr(1337), ShardCount: base.Uint16Ptr(7), }, } diff --git a/rest/api_benchmark_test.go b/rest/api_benchmark_test.go index f8e61afa73..db49501e39 100644 --- a/rest/api_benchmark_test.go +++ b/rest/api_benchmark_test.go @@ -127,7 +127,7 @@ func BenchmarkReadOps_GetRevCacheMisses(b *testing.B) { // Get database handle rtDatabase := rt.GetDatabase() - revCacheSize := rtDatabase.Options.RevisionCacheOptions.Size + revCacheSize := rtDatabase.Options.RevisionCacheOptions.MaxItemCount doc1k_putDoc := fmt.Sprintf(doc_1k_format, "") numDocs := int(revCacheSize + 1) diff --git a/rest/bootstrap_test.go b/rest/bootstrap_test.go index 1ec82b6034..a712905f0d 100644 --- a/rest/bootstrap_test.go +++ b/rest/bootstrap_test.go @@ -69,7 +69,7 @@ func TestBootstrapRESTAPISetup(t *testing.T) { assert.Empty(t, dbConfigResp.Username) assert.Empty(t, dbConfigResp.Password) require.Nil(t, dbConfigResp.Sync) - require.Equal(t, uint32(1234), *dbConfigResp.CacheConfig.RevCacheConfig.Size) + require.Equal(t, uint32(1234), *dbConfigResp.CacheConfig.RevCacheConfig.MaxItemCount) // Sanity check to use the database resp = BootstrapAdminRequest(t, sc, http.MethodPut, "/db1/doc1", `{"foo":"bar"}`) @@ -103,7 +103,7 @@ func TestBootstrapRESTAPISetup(t *testing.T) { assert.Empty(t, dbConfigResp.Username) assert.Empty(t, dbConfigResp.Password) require.Nil(t, dbConfigResp.Sync) - require.Equal(t, uint32(1234), *dbConfigResp.CacheConfig.RevCacheConfig.Size) + require.Equal(t, uint32(1234), *dbConfigResp.CacheConfig.RevCacheConfig.MaxItemCount) // Ensure it's _actually_ the same bucket resp = BootstrapAdminRequest(t, sc, http.MethodGet, "/db1/doc1", ``) diff --git a/rest/config.go b/rest/config.go index 984f3d211f..6e088840a3 100644 --- a/rest/config.go +++ b/rest/config.go @@ -246,8 +246,9 @@ type DeprecatedCacheConfig struct { } type RevCacheConfig struct { - Size *uint32 `json:"size,omitempty"` // Maximum number of revisions to store in the revision cache - ShardCount *uint16 `json:"shard_count,omitempty"` // Number of shards the rev cache should be split into + MaxItemCount *uint32 `json:"max_item_count,omitempty"` // Maximum number of revisions to store in the revision cache + MaxMemoryCountMB *uint32 `json:"max_memory_count_mb"` // Maximum amount of memory the rev cache should consume in MB + ShardCount *uint16 `json:"shard_count,omitempty"` // Number of shards the rev cache should be split into } type ChannelCacheConfig struct { @@ -814,10 +815,15 @@ func (dbConfig *DbConfig) validateVersion(ctx context.Context, isEnterpriseEditi if dbConfig.CacheConfig.RevCacheConfig != nil { // EE: disable revcache - revCacheSize := dbConfig.CacheConfig.RevCacheConfig.Size + revCacheSize := dbConfig.CacheConfig.RevCacheConfig.MaxItemCount if !isEnterpriseEdition && revCacheSize != nil && *revCacheSize == 0 { - base.WarnfCtx(ctx, eeOnlyWarningMsg, "cache.rev_cache.size", *revCacheSize, db.DefaultRevisionCacheSize) - dbConfig.CacheConfig.RevCacheConfig.Size = nil + base.WarnfCtx(ctx, eeOnlyWarningMsg, "cache.rev_cache.max_item_count", *revCacheSize, db.DefaultRevisionCacheSize) + dbConfig.CacheConfig.RevCacheConfig.MaxItemCount = nil + } + revCacheMemoryLimit := dbConfig.CacheConfig.RevCacheConfig.MaxMemoryCountMB + if !isEnterpriseEdition && revCacheMemoryLimit != nil && *revCacheMemoryLimit != 0 { + base.WarnfCtx(ctx, eeOnlyWarningMsg, "cache.rev_cache.max_memory_count_mb", *revCacheMemoryLimit, "no memory limit") + dbConfig.CacheConfig.RevCacheConfig.MaxMemoryCountMB = nil } if dbConfig.CacheConfig.RevCacheConfig.ShardCount != nil { @@ -1116,8 +1122,8 @@ func (dbConfig *DbConfig) deprecatedConfigCacheFallback() (warnings []string) { } if dbConfig.DeprecatedRevCacheSize != nil { - if dbConfig.CacheConfig.RevCacheConfig.Size == nil { - dbConfig.CacheConfig.RevCacheConfig.Size = dbConfig.DeprecatedRevCacheSize + if dbConfig.CacheConfig.RevCacheConfig.MaxItemCount == nil { + dbConfig.CacheConfig.RevCacheConfig.MaxItemCount = dbConfig.DeprecatedRevCacheSize } warnings = append(warnings, fmt.Sprintf(warningMsgFmt, "rev_cache_size", "cache.rev_cache.size")) } diff --git a/rest/config_database.go b/rest/config_database.go index 9fc2d20f13..6d4f97afbd 100644 --- a/rest/config_database.go +++ b/rest/config_database.go @@ -137,8 +137,8 @@ func DefaultDbConfig(sc *StartupConfig, useXattrs bool) *DbConfig { AllowEmptyPassword: base.BoolPtr(false), CacheConfig: &CacheConfig{ RevCacheConfig: &RevCacheConfig{ - Size: base.Uint32Ptr(db.DefaultRevisionCacheSize), - ShardCount: base.Uint16Ptr(db.DefaultRevisionCacheShardCount), + MaxItemCount: base.Uint32Ptr(db.DefaultRevisionCacheSize), + ShardCount: base.Uint16Ptr(db.DefaultRevisionCacheShardCount), }, ChannelCacheConfig: &ChannelCacheConfig{ MaxNumber: base.IntPtr(db.DefaultChannelCacheMaxNumber), diff --git a/rest/config_test.go b/rest/config_test.go index 9f1aa44345..3b8fdba31a 100644 --- a/rest/config_test.go +++ b/rest/config_test.go @@ -18,6 +18,7 @@ import ( "crypto/rsa" "crypto/tls" "crypto/x509" + "encoding/json" "errors" "flag" "fmt" @@ -190,11 +191,11 @@ func TestConfigValidationCache(t *testing.T) { require.NotNil(t, config.Databases["db"].CacheConfig.RevCacheConfig) if base.IsEnterpriseEdition() { - require.NotNil(t, config.Databases["db"].CacheConfig.RevCacheConfig.Size) - assert.Equal(t, 0, int(*config.Databases["db"].CacheConfig.RevCacheConfig.Size)) + require.NotNil(t, config.Databases["db"].CacheConfig.RevCacheConfig.MaxItemCount) + assert.Equal(t, 0, int(*config.Databases["db"].CacheConfig.RevCacheConfig.MaxItemCount)) } else { // CE disallowed - should be nil - assert.Nil(t, config.Databases["db"].CacheConfig.RevCacheConfig.Size) + assert.Nil(t, config.Databases["db"].CacheConfig.RevCacheConfig.MaxItemCount) } require.NotNil(t, config.Databases["db"].CacheConfig.ChannelCacheConfig) @@ -488,7 +489,7 @@ func TestDeprecatedCacheConfig(t *testing.T) { require.Len(t, warnings, 8) // Check that the deprecated values have correctly been propagated upto the new config values - assert.Equal(t, *dbConfig.CacheConfig.RevCacheConfig.Size, uint32(10)) + assert.Equal(t, *dbConfig.CacheConfig.RevCacheConfig.MaxItemCount, uint32(10)) assert.Equal(t, *dbConfig.CacheConfig.ChannelCacheConfig.ExpirySeconds, 10) assert.Equal(t, *dbConfig.CacheConfig.ChannelCacheConfig.MinLength, 10) assert.Equal(t, *dbConfig.CacheConfig.ChannelCacheConfig.MaxLength, 10) @@ -507,7 +508,7 @@ func TestDeprecatedCacheConfig(t *testing.T) { // Set A Couple Deprecated Values AND Their New Counterparts dbConfig.DeprecatedRevCacheSize = base.Uint32Ptr(10) - dbConfig.CacheConfig.RevCacheConfig.Size = base.Uint32Ptr(20) + dbConfig.CacheConfig.RevCacheConfig.MaxItemCount = base.Uint32Ptr(20) dbConfig.CacheConfig.DeprecatedEnableStarChannel = base.BoolPtr(false) dbConfig.CacheConfig.ChannelCacheConfig.EnableStarChannel = base.BoolPtr(true) @@ -518,7 +519,7 @@ func TestDeprecatedCacheConfig(t *testing.T) { require.Len(t, warnings, 2) // Check that the deprecated value has been ignored as the new value is the priority - assert.Equal(t, *dbConfig.CacheConfig.RevCacheConfig.Size, uint32(20)) + assert.Equal(t, *dbConfig.CacheConfig.RevCacheConfig.MaxItemCount, uint32(20)) assert.Equal(t, *dbConfig.CacheConfig.ChannelCacheConfig.EnableStarChannel, true) } @@ -3067,3 +3068,40 @@ func TestNotFoundOnInvalidDatabase(t *testing.T) { assert.Equal(c, 1, len(rt.ServerContext().dbConfigs)) }, time.Second*10, time.Millisecond*100) } + +func TestRevCacheMemoryLimitConfig(t *testing.T) { + rt := NewRestTester(t, &RestTesterConfig{ + CustomTestBucket: base.GetTestBucket(t), + PersistentConfig: true, + }) + defer rt.Close() + + dbConfig := rt.NewDbConfig() + RequireStatus(t, rt.CreateDatabase("db1", dbConfig), http.StatusCreated) + + resp := rt.SendAdminRequest(http.MethodGet, "/db1/_config", "") + RequireStatus(t, resp, http.StatusOK) + + require.NoError(t, json.Unmarshal(resp.BodyBytes(), &dbConfig)) + assert.Nil(t, dbConfig.CacheConfig) + + dbConfig.CacheConfig = &CacheConfig{} + dbConfig.CacheConfig.RevCacheConfig = &RevCacheConfig{ + MaxItemCount: base.Uint32Ptr(100), + MaxMemoryCountMB: base.Uint32Ptr(4), + } + RequireStatus(t, rt.UpsertDbConfig("db1", dbConfig), http.StatusCreated) + + resp = rt.SendAdminRequest(http.MethodGet, "/db1/_config", "") + RequireStatus(t, resp, http.StatusOK) + + require.NoError(t, json.Unmarshal(resp.BodyBytes(), &dbConfig)) + assert.NotNil(t, dbConfig.CacheConfig) + + assert.Equal(t, uint32(100), *dbConfig.CacheConfig.RevCacheConfig.MaxItemCount) + if base.IsEnterpriseEdition() { + assert.Equal(t, uint32(4), *dbConfig.CacheConfig.RevCacheConfig.MaxMemoryCountMB) + } else { + assert.Nil(t, dbConfig.CacheConfig.RevCacheConfig.MaxMemoryCountMB) + } +} diff --git a/rest/revocation_test.go b/rest/revocation_test.go index 563024680a..d0bab3e9fa 100644 --- a/rest/revocation_test.go +++ b/rest/revocation_test.go @@ -1087,7 +1087,7 @@ func TestRevocationsWithQueryLimit(t *testing.T) { QueryPaginationLimit: base.IntPtr(2), CacheConfig: &CacheConfig{ RevCacheConfig: &RevCacheConfig{ - Size: base.Uint32Ptr(0), + MaxItemCount: base.Uint32Ptr(0), }, ChannelCacheConfig: &ChannelCacheConfig{ MaxNumber: base.IntPtr(0), @@ -1176,7 +1176,7 @@ func TestRevocationsWithQueryLimitChangesLimit(t *testing.T) { QueryPaginationLimit: base.IntPtr(2), CacheConfig: &CacheConfig{ RevCacheConfig: &RevCacheConfig{ - Size: base.Uint32Ptr(0), + MaxItemCount: base.Uint32Ptr(0), }, ChannelCacheConfig: &ChannelCacheConfig{ MaxNumber: base.IntPtr(0), @@ -1227,7 +1227,7 @@ func TestRevocationUserHasDocAccessDocNotFound(t *testing.T) { QueryPaginationLimit: base.IntPtr(2), CacheConfig: &CacheConfig{ RevCacheConfig: &RevCacheConfig{ - Size: base.Uint32Ptr(0), + MaxItemCount: base.Uint32Ptr(0), }, ChannelCacheConfig: &ChannelCacheConfig{ MaxNumber: base.IntPtr(0), diff --git a/rest/server_context.go b/rest/server_context.go index d7f2d1fe8a..2c68ea90c1 100644 --- a/rest/server_context.go +++ b/rest/server_context.go @@ -1164,8 +1164,11 @@ func dbcOptionsFromConfig(ctx context.Context, sc *ServerContext, config *DbConf } if config.CacheConfig.RevCacheConfig != nil { - if config.CacheConfig.RevCacheConfig.Size != nil { - revCacheOptions.Size = *config.CacheConfig.RevCacheConfig.Size + if config.CacheConfig.RevCacheConfig.MaxItemCount != nil { + revCacheOptions.MaxItemCount = *config.CacheConfig.RevCacheConfig.MaxItemCount + } + if config.CacheConfig.RevCacheConfig.MaxMemoryCountMB != nil { + revCacheOptions.MaxBytes = int64(*config.CacheConfig.RevCacheConfig.MaxMemoryCountMB * 1024 * 1024) // Convert MB input to bytes } if config.CacheConfig.RevCacheConfig.ShardCount != nil { revCacheOptions.ShardCount = *config.CacheConfig.RevCacheConfig.ShardCount