11
Quick Review of the Most Popular Ways to Implement Flags
There are many ways to store flags and use them for communication between client <-> backend service
or service <-> service
. In the article, we are going to review the most popular options and we will try to help to choose the correct way for the next project.
The most popular ways to work with flags are:
- Store in separate column in DB or separate property in a class/struct
- Store as an array or in a set
- Store as binary data in a variable
It means we would create a separate column in DB for each flag.
In an example, is_new
will be a flag.
| id | username | is_new |
|----|----------|--------|
| 1 | test1 | true |
| 2 | test2 | false |
In a code or a NoSQL database representation would be:
[
{
"id": 1,
"username": "test1",
"is_new": true
},
{
"id": 2,
"username": "test2",
"is_new": false
}
]
- Simple to read
- Simple to understand
- Self-documented
- In a code it can represent as typed properties
- Simple to convert between different representations, like
struct/class -> JSON -> protobuf -> MsgPack -> DB row -> struct/class
- Simple to use in search and aggregation requests in DB (for example by SQL)
- Can be updated in DB in parallel, by UPDATE query
- Takes up memory/storage space/traffic
- Could be expensive in development or risky to remove in case of dynamically typed languages
- Could be expensive in development to add or remove or update in case of microservice architecture
- Could be expensive in development to remove from DB as a column
In my opinion, before using this approach, we should answer "yes" for all listed options in the checklist:
- I agree to extend add and remove columns in your database (or add fields to the NoSQL database)?
- I understand it could take up resources in DB will be used
- I do not have memory and traffic sensitive clients or services
In the current implementation, we will store all flags as strings
in an array.
In a DB, we are going to store values in a separate table as one to many relationships.
Table user
:
| id | username |
|----|----------|
| 1 | test1 |
| 2 | test2 |
Table user_flag
| id | user_id | flag |
|----|---------|---------|
| 1 | 1 | is_new |
| 2 | 1 | is_test |
Every user_flag
row is linked to a row in user
table by user_id
field.
It can be optimized to not store the flag as a string, but in our current topic, it is not so important.
In a code or a NoSQL database representation would be:
[
{
"id": 1,
"username": "test1",
"flags": [
"is_new",
"is_test"
]
},
{
"id": 2,
"username": "test2",
"flags": []
}
]
- Simple to read
- Simple to understand
- Self-documented
- Could be dynamically extended in a code (does not need to update code to add new flag)
- Simple to convert between different representations, like
struct/class -> JSON -> protobuf -> MsgPack -> DB row -> struct/class
- Pretty simple to use in search and aggregation requests in DB (for example by SQL)
- Can be updated in DB in parallel, by INSERT query and DELETE query
- Takes up memory/storage space/traffic
- Need to write additional code for
set
andunset
andfind
functions - Could affect different limitation in case of many flags
- Could affect performance, especially on a client-side
In my opinion, before using this approach, we should answer "yes" for all listed options in the checklist:
- I do not have performance and memory and traffic sensitive clients or services
- I understand it could take up resources on all sides it would be used
To see code in action, simply read README.md from binflags library.
In DB we can store bitmask in various types, like: TINYINT
, SMALLINT
, MEDIUMINT
, INT
, BIGINT
, BLOB
types.
| id | username | flags |
|----|----------|--------|
| 1 | test1 | 1 |
| 2 | test2 | 0 |
In the current example, flags
field has the type INT
and the first bit is is_new
flag.
In a code or a NoSQL database representation would be:
[
{
"id": 1,
"username": "test1",
"flags": 1
},
{
"id": 2,
"username": "test2",
"flags": 0
}
]
- Simple to understand
- The fastest implementation for all operations with flags
- Do not take up additional memory and traffic resources (always only 1 bit per flag)
- Can be used in a search by DB (but cannot be efficiently used indexes)
- In some cases flags cannot be updated in parallel
- Cannot search flags by index
- Have to be documented (name to bit mapping)
- Should be explained for some people
In my opinion, before using this approach, we should answer "yes" for all listed options in the checklist:
- I want to save memory/traffic/processor resources
- I understand all limitations
As we can see, as usual, we do not have silver bullet for all use cases and systems. But the provided list of implementations can help to find the best solution for a specific project.
If you want to use the most efficient option in Go, I would suggest checking Go implementation of binary flags for various types.
11