{"record":{"id":"d1b715b2863f5b6f","repo":"dgraph-io/badger","slug":"index-cache-must-be-set-for-encrypted-workloads","errorCode":null,"errorMessage":"Index Cache must be set for encrypted workloads","messagePattern":"Index Cache must be set for encrypted workloads","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"table/table.go","lineNumber":533,"sourceCode":"\tfor i := 0; i < oLen; i += jump {\n\t\tif i >= oLen {\n\t\t\ti = oLen - 1\n\t\t}\n\t\ty.AssertTrue(t.offsets(&bo, i))\n\t\tif bytes.HasPrefix(bo.KeyBytes(), prefix) {\n\t\t\tres = append(res, string(bo.KeyBytes()))\n\t\t}\n\t}\n\treturn res\n}\n\nfunc (t *Table) fetchIndex() *fb.TableIndex {\n\tif !t.shouldDecrypt() {\n\t\treturn t._index\n\t}\n\n\tif t.opt.IndexCache == nil {\n\t\tpanic(\"Index Cache must be set for encrypted workloads\")\n\t}\n\tif val, ok := t.opt.IndexCache.Get(t.indexKey()); ok && val != nil {\n\t\treturn val\n\t}\n\n\tindex, err := t.readTableIndex()\n\ty.Check(err)\n\tt.opt.IndexCache.Set(t.indexKey(), index, int64(t.indexLen))\n\treturn index\n}\n\nfunc (t *Table) offsets(ko *fb.BlockOffset, i int) bool {\n\treturn t.fetchIndex().Offsets(ko, i)\n}\n\n// block function return a new block. Each block holds a ref and the byte\n// slice stored in the block will be reused when the ref becomes zero. The\n// caller should release the block by calling block.decrRef() on it.","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/dgraph-io/badger/blob/2a001d466f6b71a917319a1db41f99860e16e269/table/table.go#L515-L551","documentation":"For encrypted workloads the table index cannot be kept as plain bytes in memory; it must be cached decrypted. fetchIndex panics when the table requires decryption but opt.IndexCache is nil, because there would be nowhere to store the decrypted index.","triggerScenarios":"Opening a DB with WithEncryptionKey set but without WithIndexCache, then calling any API that reads the index: t.offsets(), StaleDataSize(), DoesNotHave(), VerifyChecksum(), or normal iteration on an encrypted table.","commonSituations":"Users enabling Badger's encryption feature but forgetting the mandatory IndexCache option; copying options from a non-encrypted setup to an encrypted one.","solutions":["Set WithIndexCache (e.g. ristretto.NewCache) in the options when encryption is enabled","Re-open the DB with both WithEncryptionKey and a non-nil IndexCache","If encryption is unintended, remove WithEncryptionKey so fetchIndex returns the plain index"],"exampleFix":"// before\nopt := badger.DefaultOptions(dir).\n    WithEncryptionKey(key)\n\n// after\nic, _ := ristretto.NewCache(&ristretto.Config{NumCounters: 1e6, MaxCost: 1 << 30})\nopt := badger.DefaultOptions(dir).\n    WithEncryptionKey(key).\n    WithIndexCache(ic)","handlingStrategy":"validation","validationCode":"if opt.EncryptionKey != nil && opt.IndexCache == nil {\n    ic, err := ristretto.NewCache(&ristretto.Config{\n        NumCounters: 1e6, MaxCost: 1 << 30, BufferItems: 64,\n    })\n    if err != nil { return err }\n    opt.IndexCache = ic\n}","typeGuard":null,"tryCatchPattern":"func safeOpen(opt badger.Options) (*badger.DB, error) {\n    if opt.EncryptionKey != nil && opt.IndexCache == nil {\n        return nil, errors.New(\"encryption requires WithIndexCache\")\n    }\n    return badger.Open(opt)\n}","preventionTips":["Always pair WithEncryptionKey with WithIndexCache in your options builder","Centralize option construction in one factory function","Add a startup assertion/test for encrypted deployments"],"tags":["encryption","configuration","panic","index-cache"],"backgroundTag":"missing-index-cache-option","analyzedSha":"2a001d466f6b71a917319a1db41f99860e16e269","analyzedAt":"2026-09-05T13:00:02.264Z","contentChangedAt":"2026-09-05T13:00:02.264Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}