Summary
vespa/flags is a standalone library that defines runtime feature flags as id + typed value + targeting rules, with JSON as the storage format. On the config server, authoritative values live in ZooKeeper under /flags/v1/
Key mechanisms
- On-disk db: FlagDbFile implements both FlagRepository and FlagSource; the no-arg constructor resolves the path to Defaults.getDefaults().underVespaHome(“var/vespa/flag.db”) (FlagDbFile.java:44-50). read() deserializes the whole file into a Map of FlagData (FlagDbFile.java:65-69); sync(Map) diffs against current contents by comparing serializeToJson() strings, logs new/updated/removed flags, and rewrites the entire file via FlagData.serializeListToUtf8Json when anything changed (FlagDbFile.java:71-103).
- JSON format: the wire format is Jackson-mapped WireFlagData with fields id (string), rules (list of WireRule), and attributes (map of dimension name to value, the default fetch vector) (WireFlagData.java:22-25); each WireRule has conditions (AND-ed WireCondition list) and value (raw JSON) (WireRule.java:16-18). FlagData.toWire fills id, rules, and defaultFetchVector (FlagData.java:119-130), and FlagData.deserializeUtf8Json is the reverse (FlagData.java:172). Resolution picks the first rule whose conditions all match defaultFetchVector.with(fetchVector) (FlagData.java:95-100); condition types live in the json package (RelationalCondition, WhitelistCondition, BlacklistCondition, ListCondition, per directory listing).
- ZK store: FlagsDbImpl creates /flags/v1 at startup and keeps one UTF-8 JSON FlagData node per flag id, read through a Curator DirectoryCache; setValue writes data.serializeToUtf8Json() to /flags/v1/
(FlagsDbImpl.java:25-49, 65-67) - same nodes GDN-NOTE-0037 recorded. - Precedence chain: ConfigServerFlagSource chains OrderedFlagSource(BootstrapFlagSource, ZooKeeperFlagSource) (ConfigServerFlagSource.java:24-27). BootstrapFlagSource reads var/vespa/flag.db exactly once at construction, documented as the emergency escape hatch, and should take precedence over ZK whenever it defines a value (BootstrapFlagSource.java:15-35).
- Operator API: FlagsHandler handles GET/PUT/DELETE on /flags/v1 (FlagsHandler.java:24-43), including a listing at /flags/v1 with “data” and “defined” resources (FlagsHandler.java:56-58); DefinedFlags renders flag definitions sorted by id, using FlagDefinition.getUnboundFlag().id() (DefinedFlags.java:31-38).
- Delivery to nodes: there is no container-side FlagSource implementation outside the flags and configserver-flags modules (verified by repo-wide search). Instead ModelContextImpl.FeatureFlag binds flags server-side with dimensions application id and vespa version, then narrows per cluster type, cluster id, and hostname (ModelContextImpl.java:170-199), and ModelContextImpl.FeatureFlags exposes typed values such as heapSizePercentage, queryDispatchWarmup, feedConcurrency, and responseSequencerType (lines 200-222). PermanentFlags (config-server-only flags from ConfigserverConfig, PermanentFlags.java:30) are bound the same way, e.g. PermanentFlags.INCOMPATIBLE_VERSIONS in TenantApplications (TenantApplications.java:102). container-jdisc components therefore see flags only as ordinary config.
Open questions
- Propagation latency: a FlagsDbImpl write changes ZK state, but the trigger that causes model rebuilding (and hence new flag-derived config for nodes) was not traced from FlagsDbImpl to SessionRepository/deploy.
- Which component wires ConfigServerFlagSource and FlagsHandler into the config server’s component graph: only pom.xml/CMakeLists dependencies of configserver on configserver-flags were confirmed.
- Whether anything besides BootstrapFlagSource ever writes var/vespa/flag.db (no writer found in-tree).