Implement Unleash Ruby Client in GitLab CE/EE
Description
This is preliminary work for https://gitlab.com/gitlab-org/release/framework/issues/32.
We implement https://github.com/Unleash/unleash-client-ruby in GitLab CE/EE.
We should consider how we can transit from Flipper to Unleash.
Probably we cannot leave from Flipper as on-prem customers wouldn't have another GitLab instance.
This is the first step needed in order to dogfood Feature flags. Once all concerns are taken care of we will add new features behind the Gitlab feature flags (and not Flipper) and slowly migrate existing features from flipper to Gitlab FF.
Steps
- Add unleash into the codebase
- Select 1 trivial feature to use
- Use a wrapper class for our feature flags. If it's pointed at a GitLab instance, it will fetch feature flags using the Unleash client. If not, then it will use the feature flags in the local database with Flipper.
- We could configure .com to use another GitLab instance (I saw mention of ops.gitlab.net). Local development and self managed instances could still use the local database.
- Measure performance after this is enabled (flipper has a cache mechanism in place to deal with multiple simultaneous calls to the class)
- Flipper works locally - need to check if unleash can handle this method
Concerns
- Efficient cache mechanism that reduces some performance bottle-neck (especially, network bandwidth/throughput between server and client sides and Redis (as cache server) I/O). In Flipper FF, it currently uses multi-layered cache that 1. Cache in thread 2. Cache in Redis. If the cache is not found, it falls back to database.
- Fallback policy. If FF server is not responsible and cache is unavailable, how should the flags behave?
- Reasonable latency. Since ruby client lib uses polling mechanism, flag-update is not reflected immediately. Is it a reasonable timeframe when operators want to disable a feature flag immediately in order to mitigate an on-going incident.
Testing
- Performance tests must be conducted due to the different caching mechanism implemented in Unleash vs. Flipper
- Also, we have to take a look at the error handling when unleash server is not found.
Basic concept/behavior of Unleash FF:
- Fetching flags from unleash server periodically. (Unleash::ToggleFetcher)
- Flag evaluation with strategies. (Unleash::FeatureToggle)
-
enabled == true
ANDif any_strategies_satisfied?
, thentrue
. Elsefalse
.
-
Failover with Unleash FF:
- When Unleash Server does not respond when Unleash client lib has been initialized
- Read flags from a cached file (
/tmp/unleash-production-repo.json
) - This file cache wouldn't be available on CNG instances as the temporary file could be wiped per POD recreation.
- If both cached file and server is not found,
default_value
is used.
- Read flags from a cached file (
- Server fetches flags per
refresh_interval
(15sec), if server failed to fetch thentoggle_cache
(memoized hash) is used.- For the 15sec, the changed values on Unleash server won't be reflected on client (i.e. Maximum Latecy)
- This repeated process uses ruby's
Thread
, not Sidekiq.- If the thread is terminated, how can users recover it?
Common usage of FF in GitLab:
- Disable a feature flag but enable it on a particular id (in GitLab, project_id or group_id are typically used)
- Enable a feature flag but disable it on a particular id
- Today, Flipper-FF cannot accomplish 2. and this is something Unleash-FF can accomplish with custom strategy.
- This would require custom strategy for gitlab (named
GitLab
strategy)
On-premises instances (Omnibus) with Unleash FF:
- Create a project in the instance that used for Feature Flag (client side and server side are living in the same application, but it works)
- This effectively provides users to leverage FF on UI, which is also a missing point in Flipper-FF.
- Automatically configure the on-prem instance (gitlab.yml/rb) to refer the FF-project as Unleash server.
Lack of optimization in Unleash FF:
- Flipper has a dedicated cache mechanism https://github.com/jnunemaker/flipper/blob/master/docs/Optimization.md
- In Unleash client libs,
is_enabled?
is called per execution and this would be bottle neck. - How can we optimize unleash-ruby-client library? (likely we need to contribute)
Sample GitLab
strategy
# NOTE: Implement in GitLab, not unleash-ruby-client.
require 'unleash/strategy/util'
# Custom strategy for GitLab
module Unleash
module Strategy
class GitLab < Base
def name
'gitlab'
end
###
# 1. Enable a feature flag but disable it on a particular id
# default: true,
# group: { 1: false },
# project: { 3: false },
# user: { 1: true }
# ---
# 2. Disable a feature flag but enable it on a particular id (in GitLab, project_id or group_id are typically used)
# default: false,
# group: { 1: true, 2: true },
# project: { 1: true, 2: true },
# user: { 1: true }
def is_enabled?(params = {}, context)
return false unless params.is_a?(Hash)
return false unless context.class.name == 'Unleash::Context'
group_id = context.properties['group_id']
project_id = context.properties['project_id']
user_id = context.user_id
params.dig(:group).try do |pattern|
return !!pattern[group_id] if pattern.key?(group_id)
end
params.dig(:project).try do |pattern|
return !!pattern[project_id] if pattern.key?(project_id)
end
params.dig(:user).try do |pattern|
return !!pattern[user_id] if pattern.key?(user_id)
end
return params.fetch(:default, false)
end
end
end
end