Repository files navigation

feature build statusGoDoc

Feature gate database designed for simplicity and efficiency.

Motivation

Feature gates are an important part of controlling the risk associated with software releases, they bring safe guards and granular knobs over the exposure of data to new code paths.

However, these promises can only be kept if programs can reliably access the feature gate data, and query the data set with high efficiency. Most feature gate systems rely on performing network calls to a foreign system, creating opportunities for cascading failures in distributed systems where feature gate checks are often performed on critical data paths.

The feature package was designed to offer high availbility of the feature gates, and high query performance, allowing its use in large scale systems with many nines of uptime like those run by Segment.

Reliability

The feature database is represented by an immutable set of directories and files on a file system. The level of reliability offered by a set of files on disk exceeds by a wide margin what can be achieved with a daemon process serving the data over a network interface. Would the program updating the feature database be restarted or crashed, the files would remain available for consumers to read and query. The system is known to fail static: in the worst case scenario, nothing changes.

Efficiency

The feature database being immutable, it enables very efficient access to the data. Programs can implement simple and high performance caching layers because they do not need to manage cache expirations or transactional updates. The database files are mapped to read-only memory areas and therefore can be shared by all collocated processes, ensuring that a single copy of the data ever exists in memory.

Data Models

Collections

Collections are lists of unique identifiers that programs can query the state of gates for; gates are either open or closed. The collections are arranged in groups and tiers. Each group may have multiple tiers, within each tier the collection files contain the list of identifiers, one by line.

Here is an example of the on-disk representation of collections:

$ tree
.
└── standard
├── 1
│ ├── collections
│ │ ├── source
│ │ ├── workspace
│ │ └── write_key
...

For the standard group, tier 1, there are three collections of source, workspace and write keys.

$ cat ./standard/1/collections/source
ACAtsprztv
B458ru47n7
CQRxBaQSt8
EJw9i04Lsv
IbQor7hHBU
LZK0HYwDTH
MKOxgJsedB
OmNMfU6RbP
Q5lmdTzq1Y
SqNT0bDYl7
...

On-disk file structures with large number of directories and small files cause space usage amplification, leading to large amounts of wasted space. By analyzing the volume of data used to represent feature flags, we observed that most of the space was used by the collections of identifiers. Text files provide a compact representation of the identifiers, minimizing storage space waste caused by block size alignment, and offering a scalable model to grow the number of collections and identifiers in the system.

Gates

The second core data type are feature gates, which are grouped by family, name, and collections that they apply to. The gate family and name are represented as directories, and the gate data per collection are stored in text files of key/value pairs.

Continuing on our previous example, here is a view of the way gates are laid out in the file system:

$ tree
.
└── standard
├── 1
...
│ └── gates
│ ├── access-management
│ │ └── invite-flow-enabled
│ │ └── workspace
...

For the standard group, tier 1, gate invite-flow-enabled of the access-management family is enabled for workspaces.

$ cat ./standard/1/gates/access-management/invite-flow-enabled/workspace
open	true
salt	3653824901
volume	1

The gate files contain key value pairs for the few properties of a gate, which determine which of the identifiers will see the gate open or closed.

KeyValue
opentrue/false, indicates the default behavior for identifiers that are not in the collection file
saltrandom value injected in the hash function used to determine the gate open state
volumefloating point number between 0 and 1 defining the volume of identifiers that the gate is open for

Using the CLI

The cmd/feature program can be used to explore the state of a feature database. The CLI has multiple subcommands, we present a few useful ones in this section.

All subcommand understand the following options:

OptionEnvironment VariableDescription
-p, --pathFEATURE_PATHPath to the feature database to run commands on

The FEATURE_PATH environment variable provides a simple mechanism to configure configure the default database used by the command:

$ export FEATURE_PATH=/path/to/features

By default, the $HOME/feature directory is used.

feature get gates [collection] [id]

This command prints the list of gates enabled for an identifier, it is useful to determine whether a gate is open for a given id, for example:

# empty output if the gate is not open
$ feature get gates source B458ru47n7 | grep gate-family | grep gate-name

feature get tiers

This command prints a summary of the tiers that exist in the feature database, here is an example:

$ feature get tiers
GROUP TIER COLLECTIONS FAMILIES GATES
standard 7 0 17 39
standard 6 0 18 40
standard 1 3 20 109
standard 8 0 17 39
standard 4 3 18 41
standard 3 0 18 41
standard 2 3 19 107
standard 5 3 18 40

feature describe collection [-g group] [-t tier] [collection]

This command prints the list of identifiers in a collection, with the option to filter on a group and tier; by default all groups and tiers are shown.

$ feature describe collection workspace
96x782dXhZmn6RpPJVDXgG
4o74gqFGmTgq7GS6EN3ZQJ
mcYdYvfZQcUaid1CVdC9F3
nRRroPD8pV3giaetjpDmu7
96x782dXhZmn6RpPJVDXgG
1232rt203
9a2aceada5
cus_HbXktPfAbH3weZ
opzvxHK692ZJJicNxz1AfL
pkpdcdSLNX14Za6qpD7wtv
...

Note: the identifiers are not displayed in any particular order, this command iterates over the directories and scans the collection files.

feature describe tier [group] [tier]

This command shows a verbose description of a tier, including the list of collections, and the state of each gate in the tier:

$ feature describe tier standard 1
Group:	standard
Tier:	1
Collections:
- write_key
- workspace
- source
Gates:
integrations-consumer/observability-discards-gate
- workspace	(100%, default: open)
destinations-59ceac7c2828a60001d22936/centrifuge_qa
- workspace	(100%, default: open)
destinations-54521fdc25e721e32a72ef04/webhook-flagon-centrifuge
- write_key	(100%, default: close)
...

Using the Go API

The feature package provides APIs to consume the feature gate data set, this section presents on the most common use cases that programs have and how they are solved by the package.

import (
"github.com/segmentio/feature"
)

feature.MountPoint

The feature.MountPoint type represents a path on the file system where a feature database is mounted. This type is the entry point to all other APIs, a common pattern is for programs to construct a mount point from a configuration option or environment variable:

mountPoint:=feature.MountPoint("/path/to/features")

Note: prefer using an absolute path for the mount point, so operations are not dependent on the working directory.

feature.Store

From a mount point, a program can open a feature database, which is materialized by a feature.Store object.

features, err:=mountPoint.Open()
iferr!=nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
...
}

The feature.Store type will watch for changes to the mount point, and automatically reload the content of the feature database when a change is detected. This mechanism assumes that the feature database is immutable, programs that intend to apply updates to the database must recreate it and replace the entire directory structure (which should be done in an atomic fashion via the use of the rename(2) syscall for example).

feature.(*Store).GateOpen

This is the most common use case for programs, the GateOpen method tests whether a gate is open for a given identifier.

The gate is defined by the pair of gate family and name, while the identifier is expressed as a pair of the collection and its value.

iffeatures.GateOpen("gate-family", "gate-name", "collection", "1234") {
...
}

feature.(*Store).LookupGates

Another common use case is for programs to lookup the list of gates that are enabled on an identifier. The LookupGates method solves for this use case.

for_, gate:=rangefeatures.LookupGates("gate-family", "collection", "1234") {
...
}

Note: the feature.Store type uses an internal cache to optimize gate lookups, programs must treat the returned slice as an immutable value to avoid race conditions. If the slice needs to be modified, a copy must be made first.

About

Feature gate database designed for simplicity and efficiency.

Topics

Resources

Code of conduct

Contributing

Stars

39 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

feature build statusGoDoc

Feature gate database designed for simplicity and efficiency.

Motivation

Feature gates are an important part of controlling the risk associated with software releases, they bring safe guards and granular knobs over the exposure of data to new code paths.

However, these promises can only be kept if programs can reliably access the feature gate data, and query the data set with high efficiency. Most feature gate systems rely on performing network calls to a foreign system, creating opportunities for cascading failures in distributed systems where feature gate checks are often performed on critical data paths.

The feature package was designed to offer high availbility of the feature gates, and high query performance, allowing its use in large scale systems with many nines of uptime like those run by Segment.

Reliability

The feature database is represented by an immutable set of directories and files on a file system. The level of reliability offered by a set of files on disk exceeds by a wide margin what can be achieved with a daemon process serving the data over a network interface. Would the program updating the feature database be restarted or crashed, the files would remain available for consumers to read and query. The system is known to fail static: in the worst case scenario, nothing changes.

Efficiency

The feature database being immutable, it enables very efficient access to the data. Programs can implement simple and high performance caching layers because they do not need to manage cache expirations or transactional updates. The database files are mapped to read-only memory areas and therefore can be shared by all collocated processes, ensuring that a single copy of the data ever exists in memory.

Data Models

Collections

Collections are lists of unique identifiers that programs can query the state of gates for; gates are either open or closed. The collections are arranged in groups and tiers. Each group may have multiple tiers, within each tier the collection files contain the list of identifiers, one by line.

Here is an example of the on-disk representation of collections:

$ tree
.
└── standard
├── 1
│ ├── collections
│ │ ├── source
│ │ ├── workspace
│ │ └── write_key
...

For the standard group, tier 1, there are three collections of source, workspace and write keys.

$ cat ./standard/1/collections/source
ACAtsprztv
B458ru47n7
CQRxBaQSt8
EJw9i04Lsv
IbQor7hHBU
LZK0HYwDTH
MKOxgJsedB
OmNMfU6RbP
Q5lmdTzq1Y
SqNT0bDYl7
...

On-disk file structures with large number of directories and small files cause space usage amplification, leading to large amounts of wasted space. By analyzing the volume of data used to represent feature flags, we observed that most of the space was used by the collections of identifiers. Text files provide a compact representation of the identifiers, minimizing storage space waste caused by block size alignment, and offering a scalable model to grow the number of collections and identifiers in the system.

Gates

The second core data type are feature gates, which are grouped by family, name, and collections that they apply to. The gate family and name are represented as directories, and the gate data per collection are stored in text files of key/value pairs.

Continuing on our previous example, here is a view of the way gates are laid out in the file system:

$ tree
.
└── standard
├── 1
...
│ └── gates
│ ├── access-management
│ │ └── invite-flow-enabled
│ │ └── workspace
...

For the standard group, tier 1, gate invite-flow-enabled of the access-management family is enabled for workspaces.

$ cat ./standard/1/gates/access-management/invite-flow-enabled/workspace
open	true
salt	3653824901
volume	1

The gate files contain key value pairs for the few properties of a gate, which determine which of the identifiers will see the gate open or closed.

KeyValue
opentrue/false, indicates the default behavior for identifiers that are not in the collection file
saltrandom value injected in the hash function used to determine the gate open state
volumefloating point number between 0 and 1 defining the volume of identifiers that the gate is open for

Using the CLI

The cmd/feature program can be used to explore the state of a feature database. The CLI has multiple subcommands, we present a few useful ones in this section.

All subcommand understand the following options:

OptionEnvironment VariableDescription
-p, --pathFEATURE_PATHPath to the feature database to run commands on

The FEATURE_PATH environment variable provides a simple mechanism to configure configure the default database used by the command:

$ export FEATURE_PATH=/path/to/features

By default, the $HOME/feature directory is used.

feature get gates [collection] [id]

This command prints the list of gates enabled for an identifier, it is useful to determine whether a gate is open for a given id, for example:

# empty output if the gate is not open
$ feature get gates source B458ru47n7 | grep gate-family | grep gate-name

feature get tiers

This command prints a summary of the tiers that exist in the feature database, here is an example:

$ feature get tiers
GROUP TIER COLLECTIONS FAMILIES GATES
standard 7 0 17 39
standard 6 0 18 40
standard 1 3 20 109
standard 8 0 17 39
standard 4 3 18 41
standard 3 0 18 41
standard 2 3 19 107
standard 5 3 18 40

feature describe collection [-g group] [-t tier] [collection]

This command prints the list of identifiers in a collection, with the option to filter on a group and tier; by default all groups and tiers are shown.

$ feature describe collection workspace
96x782dXhZmn6RpPJVDXgG
4o74gqFGmTgq7GS6EN3ZQJ
mcYdYvfZQcUaid1CVdC9F3
nRRroPD8pV3giaetjpDmu7
96x782dXhZmn6RpPJVDXgG
1232rt203
9a2aceada5
cus_HbXktPfAbH3weZ
opzvxHK692ZJJicNxz1AfL
pkpdcdSLNX14Za6qpD7wtv
...

Note: the identifiers are not displayed in any particular order, this command iterates over the directories and scans the collection files.

feature describe tier [group] [tier]

This command shows a verbose description of a tier, including the list of collections, and the state of each gate in the tier:

$ feature describe tier standard 1
Group:	standard
Tier:	1
Collections:
- write_key
- workspace
- source
Gates:
integrations-consumer/observability-discards-gate
- workspace	(100%, default: open)
destinations-59ceac7c2828a60001d22936/centrifuge_qa
- workspace	(100%, default: open)
destinations-54521fdc25e721e32a72ef04/webhook-flagon-centrifuge
- write_key	(100%, default: close)
...

Using the Go API

The feature package provides APIs to consume the feature gate data set, this section presents on the most common use cases that programs have and how they are solved by the package.

import (
"github.com/segmentio/feature"
)

feature.MountPoint

The feature.MountPoint type represents a path on the file system where a feature database is mounted. This type is the entry point to all other APIs, a common pattern is for programs to construct a mount point from a configuration option or environment variable:

mountPoint:=feature.MountPoint("/path/to/features")

Note: prefer using an absolute path for the mount point, so operations are not dependent on the working directory.

feature.Store

From a mount point, a program can open a feature database, which is materialized by a feature.Store object.

features, err:=mountPoint.Open()
iferr!=nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
...
}

The feature.Store type will watch for changes to the mount point, and automatically reload the content of the feature database when a change is detected. This mechanism assumes that the feature database is immutable, programs that intend to apply updates to the database must recreate it and replace the entire directory structure (which should be done in an atomic fashion via the use of the rename(2) syscall for example).

feature.(*Store).GateOpen

This is the most common use case for programs, the GateOpen method tests whether a gate is open for a given identifier.

The gate is defined by the pair of gate family and name, while the identifier is expressed as a pair of the collection and its value.

iffeatures.GateOpen("gate-family", "gate-name", "collection", "1234") {
...
}

feature.(*Store).LookupGates

Another common use case is for programs to lookup the list of gates that are enabled on an identifier. The LookupGates method solves for this use case.

for_, gate:=rangefeatures.LookupGates("gate-family", "collection", "1234") {
...
}

Note: the feature.Store type uses an internal cache to optimize gate lookups, programs must treat the returned slice as an immutable value to avoid race conditions. If the slice needs to be modified, a copy must be made first.

About

Feature gate database designed for simplicity and efficiency.

Topics

Resources

Code of conduct

Contributing

Stars

39 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

feature build statusGoDoc

Feature gate database designed for simplicity and efficiency.

Motivation

Feature gates are an important part of controlling the risk associated with software releases, they bring safe guards and granular knobs over the exposure of data to new code paths.

However, these promises can only be kept if programs can reliably access the feature gate data, and query the data set with high efficiency. Most feature gate systems rely on performing network calls to a foreign system, creating opportunities for cascading failures in distributed systems where feature gate checks are often performed on critical data paths.

The feature package was designed to offer high availbility of the feature gates, and high query performance, allowing its use in large scale systems with many nines of uptime like those run by Segment.

Reliability

The feature database is represented by an immutable set of directories and files on a file system. The level of reliability offered by a set of files on disk exceeds by a wide margin what can be achieved with a daemon process serving the data over a network interface. Would the program updating the feature database be restarted or crashed, the files would remain available for consumers to read and query. The system is known to fail static: in the worst case scenario, nothing changes.

Efficiency

The feature database being immutable, it enables very efficient access to the data. Programs can implement simple and high performance caching layers because they do not need to manage cache expirations or transactional updates. The database files are mapped to read-only memory areas and therefore can be shared by all collocated processes, ensuring that a single copy of the data ever exists in memory.

Data Models

Collections

Collections are lists of unique identifiers that programs can query the state of gates for; gates are either open or closed. The collections are arranged in groups and tiers. Each group may have multiple tiers, within each tier the collection files contain the list of identifiers, one by line.

Here is an example of the on-disk representation of collections:

$ tree
.
└── standard
├── 1
│ ├── collections
│ │ ├── source
│ │ ├── workspace
│ │ └── write_key
...

For the standard group, tier 1, there are three collections of source, workspace and write keys.

$ cat ./standard/1/collections/source
ACAtsprztv
B458ru47n7
CQRxBaQSt8
EJw9i04Lsv
IbQor7hHBU
LZK0HYwDTH
MKOxgJsedB
OmNMfU6RbP
Q5lmdTzq1Y
SqNT0bDYl7
...

On-disk file structures with large number of directories and small files cause space usage amplification, leading to large amounts of wasted space. By analyzing the volume of data used to represent feature flags, we observed that most of the space was used by the collections of identifiers. Text files provide a compact representation of the identifiers, minimizing storage space waste caused by block size alignment, and offering a scalable model to grow the number of collections and identifiers in the system.

Gates

The second core data type are feature gates, which are grouped by family, name, and collections that they apply to. The gate family and name are represented as directories, and the gate data per collection are stored in text files of key/value pairs.

Continuing on our previous example, here is a view of the way gates are laid out in the file system:

$ tree
.
└── standard
├── 1
...
│ └── gates
│ ├── access-management
│ │ └── invite-flow-enabled
│ │ └── workspace
...

For the standard group, tier 1, gate invite-flow-enabled of the access-management family is enabled for workspaces.

$ cat ./standard/1/gates/access-management/invite-flow-enabled/workspace
open	true
salt	3653824901
volume	1

The gate files contain key value pairs for the few properties of a gate, which determine which of the identifiers will see the gate open or closed.

KeyValue
opentrue/false, indicates the default behavior for identifiers that are not in the collection file
saltrandom value injected in the hash function used to determine the gate open state
volumefloating point number between 0 and 1 defining the volume of identifiers that the gate is open for

Using the CLI

The cmd/feature program can be used to explore the state of a feature database. The CLI has multiple subcommands, we present a few useful ones in this section.

All subcommand understand the following options:

OptionEnvironment VariableDescription
-p, --pathFEATURE_PATHPath to the feature database to run commands on

The FEATURE_PATH environment variable provides a simple mechanism to configure configure the default database used by the command:

$ export FEATURE_PATH=/path/to/features

By default, the $HOME/feature directory is used.

feature get gates [collection] [id]

This command prints the list of gates enabled for an identifier, it is useful to determine whether a gate is open for a given id, for example:

# empty output if the gate is not open
$ feature get gates source B458ru47n7 | grep gate-family | grep gate-name

feature get tiers

This command prints a summary of the tiers that exist in the feature database, here is an example:

$ feature get tiers
GROUP TIER COLLECTIONS FAMILIES GATES
standard 7 0 17 39
standard 6 0 18 40
standard 1 3 20 109
standard 8 0 17 39
standard 4 3 18 41
standard 3 0 18 41
standard 2 3 19 107
standard 5 3 18 40

feature describe collection [-g group] [-t tier] [collection]

This command prints the list of identifiers in a collection, with the option to filter on a group and tier; by default all groups and tiers are shown.

$ feature describe collection workspace
96x782dXhZmn6RpPJVDXgG
4o74gqFGmTgq7GS6EN3ZQJ
mcYdYvfZQcUaid1CVdC9F3
nRRroPD8pV3giaetjpDmu7
96x782dXhZmn6RpPJVDXgG
1232rt203
9a2aceada5
cus_HbXktPfAbH3weZ
opzvxHK692ZJJicNxz1AfL
pkpdcdSLNX14Za6qpD7wtv
...

Note: the identifiers are not displayed in any particular order, this command iterates over the directories and scans the collection files.

feature describe tier [group] [tier]

This command shows a verbose description of a tier, including the list of collections, and the state of each gate in the tier:

$ feature describe tier standard 1
Group:	standard
Tier:	1
Collections:
- write_key
- workspace
- source
Gates:
integrations-consumer/observability-discards-gate
- workspace	(100%, default: open)
destinations-59ceac7c2828a60001d22936/centrifuge_qa
- workspace	(100%, default: open)
destinations-54521fdc25e721e32a72ef04/webhook-flagon-centrifuge
- write_key	(100%, default: close)
...

Using the Go API

The feature package provides APIs to consume the feature gate data set, this section presents on the most common use cases that programs have and how they are solved by the package.

import (
"github.com/segmentio/feature"
)

feature.MountPoint

The feature.MountPoint type represents a path on the file system where a feature database is mounted. This type is the entry point to all other APIs, a common pattern is for programs to construct a mount point from a configuration option or environment variable:

mountPoint:=feature.MountPoint("/path/to/features")

Note: prefer using an absolute path for the mount point, so operations are not dependent on the working directory.

feature.Store

From a mount point, a program can open a feature database, which is materialized by a feature.Store object.

features, err:=mountPoint.Open()
iferr!=nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
...
}

The feature.Store type will watch for changes to the mount point, and automatically reload the content of the feature database when a change is detected. This mechanism assumes that the feature database is immutable, programs that intend to apply updates to the database must recreate it and replace the entire directory structure (which should be done in an atomic fashion via the use of the rename(2) syscall for example).

feature.(*Store).GateOpen

This is the most common use case for programs, the GateOpen method tests whether a gate is open for a given identifier.

The gate is defined by the pair of gate family and name, while the identifier is expressed as a pair of the collection and its value.

iffeatures.GateOpen("gate-family", "gate-name", "collection", "1234") {
...
}

feature.(*Store).LookupGates

Another common use case is for programs to lookup the list of gates that are enabled on an identifier. The LookupGates method solves for this use case.

for_, gate:=rangefeatures.LookupGates("gate-family", "collection", "1234") {
...
}

Note: the feature.Store type uses an internal cache to optimize gate lookups, programs must treat the returned slice as an immutable value to avoid race conditions. If the slice needs to be modified, a copy must be made first.

About

Feature gate database designed for simplicity and efficiency.

Topics

Resources

Code of conduct

Contributing

Stars

39 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

feature build statusGoDoc

Feature gate database designed for simplicity and efficiency.

Motivation

Feature gates are an important part of controlling the risk associated with software releases, they bring safe guards and granular knobs over the exposure of data to new code paths.

However, these promises can only be kept if programs can reliably access the feature gate data, and query the data set with high efficiency. Most feature gate systems rely on performing network calls to a foreign system, creating opportunities for cascading failures in distributed systems where feature gate checks are often performed on critical data paths.

The feature package was designed to offer high availbility of the feature gates, and high query performance, allowing its use in large scale systems with many nines of uptime like those run by Segment.

Reliability

The feature database is represented by an immutable set of directories and files on a file system. The level of reliability offered by a set of files on disk exceeds by a wide margin what can be achieved with a daemon process serving the data over a network interface. Would the program updating the feature database be restarted or crashed, the files would remain available for consumers to read and query. The system is known to fail static: in the worst case scenario, nothing changes.

Efficiency

The feature database being immutable, it enables very efficient access to the data. Programs can implement simple and high performance caching layers because they do not need to manage cache expirations or transactional updates. The database files are mapped to read-only memory areas and therefore can be shared by all collocated processes, ensuring that a single copy of the data ever exists in memory.

Data Models

Collections

Collections are lists of unique identifiers that programs can query the state of gates for; gates are either open or closed. The collections are arranged in groups and tiers. Each group may have multiple tiers, within each tier the collection files contain the list of identifiers, one by line.

Here is an example of the on-disk representation of collections:

$ tree
.
└── standard
├── 1
│ ├── collections
│ │ ├── source
│ │ ├── workspace
│ │ └── write_key
...

For the standard group, tier 1, there are three collections of source, workspace and write keys.

$ cat ./standard/1/collections/source
ACAtsprztv
B458ru47n7
CQRxBaQSt8
EJw9i04Lsv
IbQor7hHBU
LZK0HYwDTH
MKOxgJsedB
OmNMfU6RbP
Q5lmdTzq1Y
SqNT0bDYl7
...

On-disk file structures with large number of directories and small files cause space usage amplification, leading to large amounts of wasted space. By analyzing the volume of data used to represent feature flags, we observed that most of the space was used by the collections of identifiers. Text files provide a compact representation of the identifiers, minimizing storage space waste caused by block size alignment, and offering a scalable model to grow the number of collections and identifiers in the system.

Gates

The second core data type are feature gates, which are grouped by family, name, and collections that they apply to. The gate family and name are represented as directories, and the gate data per collection are stored in text files of key/value pairs.

Continuing on our previous example, here is a view of the way gates are laid out in the file system:

$ tree
.
└── standard
├── 1
...
│ └── gates
│ ├── access-management
│ │ └── invite-flow-enabled
│ │ └── workspace
...

For the standard group, tier 1, gate invite-flow-enabled of the access-management family is enabled for workspaces.

$ cat ./standard/1/gates/access-management/invite-flow-enabled/workspace
open	true
salt	3653824901
volume	1

The gate files contain key value pairs for the few properties of a gate, which determine which of the identifiers will see the gate open or closed.

KeyValue
opentrue/false, indicates the default behavior for identifiers that are not in the collection file
saltrandom value injected in the hash function used to determine the gate open state
volumefloating point number between 0 and 1 defining the volume of identifiers that the gate is open for

Using the CLI

The cmd/feature program can be used to explore the state of a feature database. The CLI has multiple subcommands, we present a few useful ones in this section.

All subcommand understand the following options:

OptionEnvironment VariableDescription
-p, --pathFEATURE_PATHPath to the feature database to run commands on

The FEATURE_PATH environment variable provides a simple mechanism to configure configure the default database used by the command:

$ export FEATURE_PATH=/path/to/features

By default, the $HOME/feature directory is used.

feature get gates [collection] [id]

This command prints the list of gates enabled for an identifier, it is useful to determine whether a gate is open for a given id, for example:

# empty output if the gate is not open
$ feature get gates source B458ru47n7 | grep gate-family | grep gate-name

feature get tiers

This command prints a summary of the tiers that exist in the feature database, here is an example:

$ feature get tiers
GROUP TIER COLLECTIONS FAMILIES GATES
standard 7 0 17 39
standard 6 0 18 40
standard 1 3 20 109
standard 8 0 17 39
standard 4 3 18 41
standard 3 0 18 41
standard 2 3 19 107
standard 5 3 18 40

feature describe collection [-g group] [-t tier] [collection]

This command prints the list of identifiers in a collection, with the option to filter on a group and tier; by default all groups and tiers are shown.

$ feature describe collection workspace
96x782dXhZmn6RpPJVDXgG
4o74gqFGmTgq7GS6EN3ZQJ
mcYdYvfZQcUaid1CVdC9F3
nRRroPD8pV3giaetjpDmu7
96x782dXhZmn6RpPJVDXgG
1232rt203
9a2aceada5
cus_HbXktPfAbH3weZ
opzvxHK692ZJJicNxz1AfL
pkpdcdSLNX14Za6qpD7wtv
...

Note: the identifiers are not displayed in any particular order, this command iterates over the directories and scans the collection files.

feature describe tier [group] [tier]

This command shows a verbose description of a tier, including the list of collections, and the state of each gate in the tier:

$ feature describe tier standard 1
Group:	standard
Tier:	1
Collections:
- write_key
- workspace
- source
Gates:
integrations-consumer/observability-discards-gate
- workspace	(100%, default: open)
destinations-59ceac7c2828a60001d22936/centrifuge_qa
- workspace	(100%, default: open)
destinations-54521fdc25e721e32a72ef04/webhook-flagon-centrifuge
- write_key	(100%, default: close)
...

Using the Go API

The feature package provides APIs to consume the feature gate data set, this section presents on the most common use cases that programs have and how they are solved by the package.

import (
"github.com/segmentio/feature"
)

feature.MountPoint

The feature.MountPoint type represents a path on the file system where a feature database is mounted. This type is the entry point to all other APIs, a common pattern is for programs to construct a mount point from a configuration option or environment variable:

mountPoint:=feature.MountPoint("/path/to/features")

Note: prefer using an absolute path for the mount point, so operations are not dependent on the working directory.

feature.Store

From a mount point, a program can open a feature database, which is materialized by a feature.Store object.

features, err:=mountPoint.Open()
iferr!=nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
...
}

The feature.Store type will watch for changes to the mount point, and automatically reload the content of the feature database when a change is detected. This mechanism assumes that the feature database is immutable, programs that intend to apply updates to the database must recreate it and replace the entire directory structure (which should be done in an atomic fashion via the use of the rename(2) syscall for example).

feature.(*Store).GateOpen

This is the most common use case for programs, the GateOpen method tests whether a gate is open for a given identifier.

The gate is defined by the pair of gate family and name, while the identifier is expressed as a pair of the collection and its value.

iffeatures.GateOpen("gate-family", "gate-name", "collection", "1234") {
...
}

feature.(*Store).LookupGates

Another common use case is for programs to lookup the list of gates that are enabled on an identifier. The LookupGates method solves for this use case.

for_, gate:=rangefeatures.LookupGates("gate-family", "collection", "1234") {
...
}

Note: the feature.Store type uses an internal cache to optimize gate lookups, programs must treat the returned slice as an immutable value to avoid race conditions. If the slice needs to be modified, a copy must be made first.

About

Feature gate database designed for simplicity and efficiency.

Topics

Resources

Code of conduct

Contributing

Stars

39 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

feature build statusGoDoc

Feature gate database designed for simplicity and efficiency.

Motivation

Feature gates are an important part of controlling the risk associated with software releases, they bring safe guards and granular knobs over the exposure of data to new code paths.

However, these promises can only be kept if programs can reliably access the feature gate data, and query the data set with high efficiency. Most feature gate systems rely on performing network calls to a foreign system, creating opportunities for cascading failures in distributed systems where feature gate checks are often performed on critical data paths.

The feature package was designed to offer high availbility of the feature gates, and high query performance, allowing its use in large scale systems with many nines of uptime like those run by Segment.

Reliability

The feature database is represented by an immutable set of directories and files on a file system. The level of reliability offered by a set of files on disk exceeds by a wide margin what can be achieved with a daemon process serving the data over a network interface. Would the program updating the feature database be restarted or crashed, the files would remain available for consumers to read and query. The system is known to fail static: in the worst case scenario, nothing changes.

Efficiency

The feature database being immutable, it enables very efficient access to the data. Programs can implement simple and high performance caching layers because they do not need to manage cache expirations or transactional updates. The database files are mapped to read-only memory areas and therefore can be shared by all collocated processes, ensuring that a single copy of the data ever exists in memory.

Data Models

Collections

Collections are lists of unique identifiers that programs can query the state of gates for; gates are either open or closed. The collections are arranged in groups and tiers. Each group may have multiple tiers, within each tier the collection files contain the list of identifiers, one by line.

Here is an example of the on-disk representation of collections:

$ tree
.
└── standard
├── 1
│ ├── collections
│ │ ├── source
│ │ ├── workspace
│ │ └── write_key
...

For the standard group, tier 1, there are three collections of source, workspace and write keys.

$ cat ./standard/1/collections/source
ACAtsprztv
B458ru47n7
CQRxBaQSt8
EJw9i04Lsv
IbQor7hHBU
LZK0HYwDTH
MKOxgJsedB
OmNMfU6RbP
Q5lmdTzq1Y
SqNT0bDYl7
...

On-disk file structures with large number of directories and small files cause space usage amplification, leading to large amounts of wasted space. By analyzing the volume of data used to represent feature flags, we observed that most of the space was used by the collections of identifiers. Text files provide a compact representation of the identifiers, minimizing storage space waste caused by block size alignment, and offering a scalable model to grow the number of collections and identifiers in the system.

Gates

The second core data type are feature gates, which are grouped by family, name, and collections that they apply to. The gate family and name are represented as directories, and the gate data per collection are stored in text files of key/value pairs.

Continuing on our previous example, here is a view of the way gates are laid out in the file system:

$ tree
.
└── standard
├── 1
...
│ └── gates
│ ├── access-management
│ │ └── invite-flow-enabled
│ │ └── workspace
...

For the standard group, tier 1, gate invite-flow-enabled of the access-management family is enabled for workspaces.

$ cat ./standard/1/gates/access-management/invite-flow-enabled/workspace
open	true
salt	3653824901
volume	1

The gate files contain key value pairs for the few properties of a gate, which determine which of the identifiers will see the gate open or closed.

KeyValue
opentrue/false, indicates the default behavior for identifiers that are not in the collection file
saltrandom value injected in the hash function used to determine the gate open state
volumefloating point number between 0 and 1 defining the volume of identifiers that the gate is open for

Using the CLI

The cmd/feature program can be used to explore the state of a feature database. The CLI has multiple subcommands, we present a few useful ones in this section.

All subcommand understand the following options:

OptionEnvironment VariableDescription
-p, --pathFEATURE_PATHPath to the feature database to run commands on

The FEATURE_PATH environment variable provides a simple mechanism to configure configure the default database used by the command:

$ export FEATURE_PATH=/path/to/features

By default, the $HOME/feature directory is used.

feature get gates [collection] [id]

This command prints the list of gates enabled for an identifier, it is useful to determine whether a gate is open for a given id, for example:

# empty output if the gate is not open
$ feature get gates source B458ru47n7 | grep gate-family | grep gate-name

feature get tiers

This command prints a summary of the tiers that exist in the feature database, here is an example:

$ feature get tiers
GROUP TIER COLLECTIONS FAMILIES GATES
standard 7 0 17 39
standard 6 0 18 40
standard 1 3 20 109
standard 8 0 17 39
standard 4 3 18 41
standard 3 0 18 41
standard 2 3 19 107
standard 5 3 18 40

feature describe collection [-g group] [-t tier] [collection]

This command prints the list of identifiers in a collection, with the option to filter on a group and tier; by default all groups and tiers are shown.

$ feature describe collection workspace
96x782dXhZmn6RpPJVDXgG
4o74gqFGmTgq7GS6EN3ZQJ
mcYdYvfZQcUaid1CVdC9F3
nRRroPD8pV3giaetjpDmu7
96x782dXhZmn6RpPJVDXgG
1232rt203
9a2aceada5
cus_HbXktPfAbH3weZ
opzvxHK692ZJJicNxz1AfL
pkpdcdSLNX14Za6qpD7wtv
...

Note: the identifiers are not displayed in any particular order, this command iterates over the directories and scans the collection files.

feature describe tier [group] [tier]

This command shows a verbose description of a tier, including the list of collections, and the state of each gate in the tier:

$ feature describe tier standard 1
Group:	standard
Tier:	1
Collections:
- write_key
- workspace
- source
Gates:
integrations-consumer/observability-discards-gate
- workspace	(100%, default: open)
destinations-59ceac7c2828a60001d22936/centrifuge_qa
- workspace	(100%, default: open)
destinations-54521fdc25e721e32a72ef04/webhook-flagon-centrifuge
- write_key	(100%, default: close)
...

Using the Go API

The feature package provides APIs to consume the feature gate data set, this section presents on the most common use cases that programs have and how they are solved by the package.

import (
"github.com/segmentio/feature"
)

feature.MountPoint

The feature.MountPoint type represents a path on the file system where a feature database is mounted. This type is the entry point to all other APIs, a common pattern is for programs to construct a mount point from a configuration option or environment variable:

mountPoint:=feature.MountPoint("/path/to/features")

Note: prefer using an absolute path for the mount point, so operations are not dependent on the working directory.

feature.Store

From a mount point, a program can open a feature database, which is materialized by a feature.Store object.

features, err:=mountPoint.Open()
iferr!=nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
...
}

The feature.Store type will watch for changes to the mount point, and automatically reload the content of the feature database when a change is detected. This mechanism assumes that the feature database is immutable, programs that intend to apply updates to the database must recreate it and replace the entire directory structure (which should be done in an atomic fashion via the use of the rename(2) syscall for example).

feature.(*Store).GateOpen

This is the most common use case for programs, the GateOpen method tests whether a gate is open for a given identifier.

The gate is defined by the pair of gate family and name, while the identifier is expressed as a pair of the collection and its value.

iffeatures.GateOpen("gate-family", "gate-name", "collection", "1234") {
...
}

feature.(*Store).LookupGates

Another common use case is for programs to lookup the list of gates that are enabled on an identifier. The LookupGates method solves for this use case.

for_, gate:=rangefeatures.LookupGates("gate-family", "collection", "1234") {
...
}

Note: the feature.Store type uses an internal cache to optimize gate lookups, programs must treat the returned slice as an immutable value to avoid race conditions. If the slice needs to be modified, a copy must be made first.

About

Feature gate database designed for simplicity and efficiency.

Topics

Resources

Code of conduct

Contributing

Stars

39 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

feature build statusGoDoc

Feature gate database designed for simplicity and efficiency.

Motivation

Feature gates are an important part of controlling the risk associated with software releases, they bring safe guards and granular knobs over the exposure of data to new code paths.

However, these promises can only be kept if programs can reliably access the feature gate data, and query the data set with high efficiency. Most feature gate systems rely on performing network calls to a foreign system, creating opportunities for cascading failures in distributed systems where feature gate checks are often performed on critical data paths.

The feature package was designed to offer high availbility of the feature gates, and high query performance, allowing its use in large scale systems with many nines of uptime like those run by Segment.

Reliability

The feature database is represented by an immutable set of directories and files on a file system. The level of reliability offered by a set of files on disk exceeds by a wide margin what can be achieved with a daemon process serving the data over a network interface. Would the program updating the feature database be restarted or crashed, the files would remain available for consumers to read and query. The system is known to fail static: in the worst case scenario, nothing changes.

Efficiency

The feature database being immutable, it enables very efficient access to the data. Programs can implement simple and high performance caching layers because they do not need to manage cache expirations or transactional updates. The database files are mapped to read-only memory areas and therefore can be shared by all collocated processes, ensuring that a single copy of the data ever exists in memory.

Data Models

Collections

Collections are lists of unique identifiers that programs can query the state of gates for; gates are either open or closed. The collections are arranged in groups and tiers. Each group may have multiple tiers, within each tier the collection files contain the list of identifiers, one by line.

Here is an example of the on-disk representation of collections:

$ tree
.
└── standard
├── 1
│ ├── collections
│ │ ├── source
│ │ ├── workspace
│ │ └── write_key
...

For the standard group, tier 1, there are three collections of source, workspace and write keys.

$ cat ./standard/1/collections/source
ACAtsprztv
B458ru47n7
CQRxBaQSt8
EJw9i04Lsv
IbQor7hHBU
LZK0HYwDTH
MKOxgJsedB
OmNMfU6RbP
Q5lmdTzq1Y
SqNT0bDYl7
...

On-disk file structures with large number of directories and small files cause space usage amplification, leading to large amounts of wasted space. By analyzing the volume of data used to represent feature flags, we observed that most of the space was used by the collections of identifiers. Text files provide a compact representation of the identifiers, minimizing storage space waste caused by block size alignment, and offering a scalable model to grow the number of collections and identifiers in the system.

Gates

The second core data type are feature gates, which are grouped by family, name, and collections that they apply to. The gate family and name are represented as directories, and the gate data per collection are stored in text files of key/value pairs.

Continuing on our previous example, here is a view of the way gates are laid out in the file system:

$ tree
.
└── standard
├── 1
...
│ └── gates
│ ├── access-management
│ │ └── invite-flow-enabled
│ │ └── workspace
...

For the standard group, tier 1, gate invite-flow-enabled of the access-management family is enabled for workspaces.

$ cat ./standard/1/gates/access-management/invite-flow-enabled/workspace
open	true
salt	3653824901
volume	1

The gate files contain key value pairs for the few properties of a gate, which determine which of the identifiers will see the gate open or closed.

KeyValue
opentrue/false, indicates the default behavior for identifiers that are not in the collection file
saltrandom value injected in the hash function used to determine the gate open state
volumefloating point number between 0 and 1 defining the volume of identifiers that the gate is open for

Using the CLI

The cmd/feature program can be used to explore the state of a feature database. The CLI has multiple subcommands, we present a few useful ones in this section.

All subcommand understand the following options:

OptionEnvironment VariableDescription
-p, --pathFEATURE_PATHPath to the feature database to run commands on

The FEATURE_PATH environment variable provides a simple mechanism to configure configure the default database used by the command:

$ export FEATURE_PATH=/path/to/features

By default, the $HOME/feature directory is used.

feature get gates [collection] [id]

This command prints the list of gates enabled for an identifier, it is useful to determine whether a gate is open for a given id, for example:

# empty output if the gate is not open
$ feature get gates source B458ru47n7 | grep gate-family | grep gate-name

feature get tiers

This command prints a summary of the tiers that exist in the feature database, here is an example:

$ feature get tiers
GROUP TIER COLLECTIONS FAMILIES GATES
standard 7 0 17 39
standard 6 0 18 40
standard 1 3 20 109
standard 8 0 17 39
standard 4 3 18 41
standard 3 0 18 41
standard 2 3 19 107
standard 5 3 18 40

feature describe collection [-g group] [-t tier] [collection]

This command prints the list of identifiers in a collection, with the option to filter on a group and tier; by default all groups and tiers are shown.

$ feature describe collection workspace
96x782dXhZmn6RpPJVDXgG
4o74gqFGmTgq7GS6EN3ZQJ
mcYdYvfZQcUaid1CVdC9F3
nRRroPD8pV3giaetjpDmu7
96x782dXhZmn6RpPJVDXgG
1232rt203
9a2aceada5
cus_HbXktPfAbH3weZ
opzvxHK692ZJJicNxz1AfL
pkpdcdSLNX14Za6qpD7wtv
...

Note: the identifiers are not displayed in any particular order, this command iterates over the directories and scans the collection files.

feature describe tier [group] [tier]

This command shows a verbose description of a tier, including the list of collections, and the state of each gate in the tier:

$ feature describe tier standard 1
Group:	standard
Tier:	1
Collections:
- write_key
- workspace
- source
Gates:
integrations-consumer/observability-discards-gate
- workspace	(100%, default: open)
destinations-59ceac7c2828a60001d22936/centrifuge_qa
- workspace	(100%, default: open)
destinations-54521fdc25e721e32a72ef04/webhook-flagon-centrifuge
- write_key	(100%, default: close)
...

Using the Go API

The feature package provides APIs to consume the feature gate data set, this section presents on the most common use cases that programs have and how they are solved by the package.

import (
"github.com/segmentio/feature"
)

feature.MountPoint

The feature.MountPoint type represents a path on the file system where a feature database is mounted. This type is the entry point to all other APIs, a common pattern is for programs to construct a mount point from a configuration option or environment variable:

mountPoint:=feature.MountPoint("/path/to/features")

Note: prefer using an absolute path for the mount point, so operations are not dependent on the working directory.

feature.Store

From a mount point, a program can open a feature database, which is materialized by a feature.Store object.

features, err:=mountPoint.Open()
iferr!=nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
...
}

The feature.Store type will watch for changes to the mount point, and automatically reload the content of the feature database when a change is detected. This mechanism assumes that the feature database is immutable, programs that intend to apply updates to the database must recreate it and replace the entire directory structure (which should be done in an atomic fashion via the use of the rename(2) syscall for example).

feature.(*Store).GateOpen

This is the most common use case for programs, the GateOpen method tests whether a gate is open for a given identifier.

The gate is defined by the pair of gate family and name, while the identifier is expressed as a pair of the collection and its value.

iffeatures.GateOpen("gate-family", "gate-name", "collection", "1234") {
...
}

feature.(*Store).LookupGates

Another common use case is for programs to lookup the list of gates that are enabled on an identifier. The LookupGates method solves for this use case.

for_, gate:=rangefeatures.LookupGates("gate-family", "collection", "1234") {
...
}

Note: the feature.Store type uses an internal cache to optimize gate lookups, programs must treat the returned slice as an immutable value to avoid race conditions. If the slice needs to be modified, a copy must be made first.

About

Feature gate database designed for simplicity and efficiency.

Topics

Resources

Code of conduct

Contributing

Stars

39 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

feature build statusGoDoc

Feature gate database designed for simplicity and efficiency.

Motivation

Feature gates are an important part of controlling the risk associated with software releases, they bring safe guards and granular knobs over the exposure of data to new code paths.

However, these promises can only be kept if programs can reliably access the feature gate data, and query the data set with high efficiency. Most feature gate systems rely on performing network calls to a foreign system, creating opportunities for cascading failures in distributed systems where feature gate checks are often performed on critical data paths.

The feature package was designed to offer high availbility of the feature gates, and high query performance, allowing its use in large scale systems with many nines of uptime like those run by Segment.

Reliability

The feature database is represented by an immutable set of directories and files on a file system. The level of reliability offered by a set of files on disk exceeds by a wide margin what can be achieved with a daemon process serving the data over a network interface. Would the program updating the feature database be restarted or crashed, the files would remain available for consumers to read and query. The system is known to fail static: in the worst case scenario, nothing changes.

Efficiency

The feature database being immutable, it enables very efficient access to the data. Programs can implement simple and high performance caching layers because they do not need to manage cache expirations or transactional updates. The database files are mapped to read-only memory areas and therefore can be shared by all collocated processes, ensuring that a single copy of the data ever exists in memory.

Data Models

Collections

Collections are lists of unique identifiers that programs can query the state of gates for; gates are either open or closed. The collections are arranged in groups and tiers. Each group may have multiple tiers, within each tier the collection files contain the list of identifiers, one by line.

Here is an example of the on-disk representation of collections:

$ tree
.
└── standard
├── 1
│ ├── collections
│ │ ├── source
│ │ ├── workspace
│ │ └── write_key
...

For the standard group, tier 1, there are three collections of source, workspace and write keys.

$ cat ./standard/1/collections/source
ACAtsprztv
B458ru47n7
CQRxBaQSt8
EJw9i04Lsv
IbQor7hHBU
LZK0HYwDTH
MKOxgJsedB
OmNMfU6RbP
Q5lmdTzq1Y
SqNT0bDYl7
...

On-disk file structures with large number of directories and small files cause space usage amplification, leading to large amounts of wasted space. By analyzing the volume of data used to represent feature flags, we observed that most of the space was used by the collections of identifiers. Text files provide a compact representation of the identifiers, minimizing storage space waste caused by block size alignment, and offering a scalable model to grow the number of collections and identifiers in the system.

Gates

The second core data type are feature gates, which are grouped by family, name, and collections that they apply to. The gate family and name are represented as directories, and the gate data per collection are stored in text files of key/value pairs.

Continuing on our previous example, here is a view of the way gates are laid out in the file system:

$ tree
.
└── standard
├── 1
...
│ └── gates
│ ├── access-management
│ │ └── invite-flow-enabled
│ │ └── workspace
...

For the standard group, tier 1, gate invite-flow-enabled of the access-management family is enabled for workspaces.

$ cat ./standard/1/gates/access-management/invite-flow-enabled/workspace
open	true
salt	3653824901
volume	1

The gate files contain key value pairs for the few properties of a gate, which determine which of the identifiers will see the gate open or closed.

KeyValue
opentrue/false, indicates the default behavior for identifiers that are not in the collection file
saltrandom value injected in the hash function used to determine the gate open state
volumefloating point number between 0 and 1 defining the volume of identifiers that the gate is open for

Using the CLI

The cmd/feature program can be used to explore the state of a feature database. The CLI has multiple subcommands, we present a few useful ones in this section.

All subcommand understand the following options:

OptionEnvironment VariableDescription
-p, --pathFEATURE_PATHPath to the feature database to run commands on

The FEATURE_PATH environment variable provides a simple mechanism to configure configure the default database used by the command:

$ export FEATURE_PATH=/path/to/features

By default, the $HOME/feature directory is used.

feature get gates [collection] [id]

This command prints the list of gates enabled for an identifier, it is useful to determine whether a gate is open for a given id, for example:

# empty output if the gate is not open
$ feature get gates source B458ru47n7 | grep gate-family | grep gate-name

feature get tiers

This command prints a summary of the tiers that exist in the feature database, here is an example:

$ feature get tiers
GROUP TIER COLLECTIONS FAMILIES GATES
standard 7 0 17 39
standard 6 0 18 40
standard 1 3 20 109
standard 8 0 17 39
standard 4 3 18 41
standard 3 0 18 41
standard 2 3 19 107
standard 5 3 18 40

feature describe collection [-g group] [-t tier] [collection]

This command prints the list of identifiers in a collection, with the option to filter on a group and tier; by default all groups and tiers are shown.

$ feature describe collection workspace
96x782dXhZmn6RpPJVDXgG
4o74gqFGmTgq7GS6EN3ZQJ
mcYdYvfZQcUaid1CVdC9F3
nRRroPD8pV3giaetjpDmu7
96x782dXhZmn6RpPJVDXgG
1232rt203
9a2aceada5
cus_HbXktPfAbH3weZ
opzvxHK692ZJJicNxz1AfL
pkpdcdSLNX14Za6qpD7wtv
...

Note: the identifiers are not displayed in any particular order, this command iterates over the directories and scans the collection files.

feature describe tier [group] [tier]

This command shows a verbose description of a tier, including the list of collections, and the state of each gate in the tier:

$ feature describe tier standard 1
Group:	standard
Tier:	1
Collections:
- write_key
- workspace
- source
Gates:
integrations-consumer/observability-discards-gate
- workspace	(100%, default: open)
destinations-59ceac7c2828a60001d22936/centrifuge_qa
- workspace	(100%, default: open)
destinations-54521fdc25e721e32a72ef04/webhook-flagon-centrifuge
- write_key	(100%, default: close)
...

Using the Go API

The feature package provides APIs to consume the feature gate data set, this section presents on the most common use cases that programs have and how they are solved by the package.

import (
"github.com/segmentio/feature"
)

feature.MountPoint

The feature.MountPoint type represents a path on the file system where a feature database is mounted. This type is the entry point to all other APIs, a common pattern is for programs to construct a mount point from a configuration option or environment variable:

mountPoint:=feature.MountPoint("/path/to/features")

Note: prefer using an absolute path for the mount point, so operations are not dependent on the working directory.

feature.Store

From a mount point, a program can open a feature database, which is materialized by a feature.Store object.

features, err:=mountPoint.Open()
iferr!=nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
...
}

The feature.Store type will watch for changes to the mount point, and automatically reload the content of the feature database when a change is detected. This mechanism assumes that the feature database is immutable, programs that intend to apply updates to the database must recreate it and replace the entire directory structure (which should be done in an atomic fashion via the use of the rename(2) syscall for example).

feature.(*Store).GateOpen

This is the most common use case for programs, the GateOpen method tests whether a gate is open for a given identifier.

The gate is defined by the pair of gate family and name, while the identifier is expressed as a pair of the collection and its value.

iffeatures.GateOpen("gate-family", "gate-name", "collection", "1234") {
...
}

feature.(*Store).LookupGates

Another common use case is for programs to lookup the list of gates that are enabled on an identifier. The LookupGates method solves for this use case.

for_, gate:=rangefeatures.LookupGates("gate-family", "collection", "1234") {
...
}

Note: the feature.Store type uses an internal cache to optimize gate lookups, programs must treat the returned slice as an immutable value to avoid race conditions. If the slice needs to be modified, a copy must be made first.

About

Feature gate database designed for simplicity and efficiency.

Topics

Resources

Code of conduct

Contributing

Stars

39 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

feature build statusGoDoc

Feature gate database designed for simplicity and efficiency.

Motivation

Feature gates are an important part of controlling the risk associated with software releases, they bring safe guards and granular knobs over the exposure of data to new code paths.

However, these promises can only be kept if programs can reliably access the feature gate data, and query the data set with high efficiency. Most feature gate systems rely on performing network calls to a foreign system, creating opportunities for cascading failures in distributed systems where feature gate checks are often performed on critical data paths.

The feature package was designed to offer high availbility of the feature gates, and high query performance, allowing its use in large scale systems with many nines of uptime like those run by Segment.

Reliability

The feature database is represented by an immutable set of directories and files on a file system. The level of reliability offered by a set of files on disk exceeds by a wide margin what can be achieved with a daemon process serving the data over a network interface. Would the program updating the feature database be restarted or crashed, the files would remain available for consumers to read and query. The system is known to fail static: in the worst case scenario, nothing changes.

Efficiency

The feature database being immutable, it enables very efficient access to the data. Programs can implement simple and high performance caching layers because they do not need to manage cache expirations or transactional updates. The database files are mapped to read-only memory areas and therefore can be shared by all collocated processes, ensuring that a single copy of the data ever exists in memory.

Data Models

Collections

Collections are lists of unique identifiers that programs can query the state of gates for; gates are either open or closed. The collections are arranged in groups and tiers. Each group may have multiple tiers, within each tier the collection files contain the list of identifiers, one by line.

Here is an example of the on-disk representation of collections:

$ tree
.
└── standard
├── 1
│ ├── collections
│ │ ├── source
│ │ ├── workspace
│ │ └── write_key
...

For the standard group, tier 1, there are three collections of source, workspace and write keys.

$ cat ./standard/1/collections/source
ACAtsprztv
B458ru47n7
CQRxBaQSt8
EJw9i04Lsv
IbQor7hHBU
LZK0HYwDTH
MKOxgJsedB
OmNMfU6RbP
Q5lmdTzq1Y
SqNT0bDYl7
...

On-disk file structures with large number of directories and small files cause space usage amplification, leading to large amounts of wasted space. By analyzing the volume of data used to represent feature flags, we observed that most of the space was used by the collections of identifiers. Text files provide a compact representation of the identifiers, minimizing storage space waste caused by block size alignment, and offering a scalable model to grow the number of collections and identifiers in the system.

Gates

The second core data type are feature gates, which are grouped by family, name, and collections that they apply to. The gate family and name are represented as directories, and the gate data per collection are stored in text files of key/value pairs.

Continuing on our previous example, here is a view of the way gates are laid out in the file system:

$ tree
.
└── standard
├── 1
...
│ └── gates
│ ├── access-management
│ │ └── invite-flow-enabled
│ │ └── workspace
...

For the standard group, tier 1, gate invite-flow-enabled of the access-management family is enabled for workspaces.

$ cat ./standard/1/gates/access-management/invite-flow-enabled/workspace
open	true
salt	3653824901
volume	1

The gate files contain key value pairs for the few properties of a gate, which determine which of the identifiers will see the gate open or closed.

KeyValue
opentrue/false, indicates the default behavior for identifiers that are not in the collection file
saltrandom value injected in the hash function used to determine the gate open state
volumefloating point number between 0 and 1 defining the volume of identifiers that the gate is open for

Using the CLI

The cmd/feature program can be used to explore the state of a feature database. The CLI has multiple subcommands, we present a few useful ones in this section.

All subcommand understand the following options:

OptionEnvironment VariableDescription
-p, --pathFEATURE_PATHPath to the feature database to run commands on

The FEATURE_PATH environment variable provides a simple mechanism to configure configure the default database used by the command:

$ export FEATURE_PATH=/path/to/features

By default, the $HOME/feature directory is used.

feature get gates [collection] [id]

This command prints the list of gates enabled for an identifier, it is useful to determine whether a gate is open for a given id, for example:

# empty output if the gate is not open
$ feature get gates source B458ru47n7 | grep gate-family | grep gate-name

feature get tiers

This command prints a summary of the tiers that exist in the feature database, here is an example:

$ feature get tiers
GROUP TIER COLLECTIONS FAMILIES GATES
standard 7 0 17 39
standard 6 0 18 40
standard 1 3 20 109
standard 8 0 17 39
standard 4 3 18 41
standard 3 0 18 41
standard 2 3 19 107
standard 5 3 18 40

feature describe collection [-g group] [-t tier] [collection]

This command prints the list of identifiers in a collection, with the option to filter on a group and tier; by default all groups and tiers are shown.

$ feature describe collection workspace
96x782dXhZmn6RpPJVDXgG
4o74gqFGmTgq7GS6EN3ZQJ
mcYdYvfZQcUaid1CVdC9F3
nRRroPD8pV3giaetjpDmu7
96x782dXhZmn6RpPJVDXgG
1232rt203
9a2aceada5
cus_HbXktPfAbH3weZ
opzvxHK692ZJJicNxz1AfL
pkpdcdSLNX14Za6qpD7wtv
...

Note: the identifiers are not displayed in any particular order, this command iterates over the directories and scans the collection files.

feature describe tier [group] [tier]

This command shows a verbose description of a tier, including the list of collections, and the state of each gate in the tier:

$ feature describe tier standard 1
Group:	standard
Tier:	1
Collections:
- write_key
- workspace
- source
Gates:
integrations-consumer/observability-discards-gate
- workspace	(100%, default: open)
destinations-59ceac7c2828a60001d22936/centrifuge_qa
- workspace	(100%, default: open)
destinations-54521fdc25e721e32a72ef04/webhook-flagon-centrifuge
- write_key	(100%, default: close)
...

Using the Go API

The feature package provides APIs to consume the feature gate data set, this section presents on the most common use cases that programs have and how they are solved by the package.

import (
"github.com/segmentio/feature"
)

feature.MountPoint

The feature.MountPoint type represents a path on the file system where a feature database is mounted. This type is the entry point to all other APIs, a common pattern is for programs to construct a mount point from a configuration option or environment variable:

mountPoint:=feature.MountPoint("/path/to/features")

Note: prefer using an absolute path for the mount point, so operations are not dependent on the working directory.

feature.Store

From a mount point, a program can open a feature database, which is materialized by a feature.Store object.

features, err:=mountPoint.Open()
iferr!=nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
...
}

The feature.Store type will watch for changes to the mount point, and automatically reload the content of the feature database when a change is detected. This mechanism assumes that the feature database is immutable, programs that intend to apply updates to the database must recreate it and replace the entire directory structure (which should be done in an atomic fashion via the use of the rename(2) syscall for example).

feature.(*Store).GateOpen

This is the most common use case for programs, the GateOpen method tests whether a gate is open for a given identifier.

The gate is defined by the pair of gate family and name, while the identifier is expressed as a pair of the collection and its value.

iffeatures.GateOpen("gate-family", "gate-name", "collection", "1234") {
...
}

feature.(*Store).LookupGates

Another common use case is for programs to lookup the list of gates that are enabled on an identifier. The LookupGates method solves for this use case.

for_, gate:=rangefeatures.LookupGates("gate-family", "collection", "1234") {
...
}

Note: the feature.Store type uses an internal cache to optimize gate lookups, programs must treat the returned slice as an immutable value to avoid race conditions. If the slice needs to be modified, a copy must be made first.

About

Feature gate database designed for simplicity and efficiency.

Topics

Resources

Code of conduct

Contributing

Stars

39 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages