Self hosted Decision API
Early Adoption Phase
This feature is in early adoption, if you want to try it feel free to contact us at [email protected]
Introduction
Although we provide a cloud hosted Decision API that is fast and scalable, you can still choose to host the Decision API on your premises instead of using the cloud one.
The data collection part of the Flagship platform is still hosted on our side, and the Decision API will just batch send the hit tracking calls to our own cloud-hosted data collection on a regular basis.
The Decision API that you can install on-premise is almost the same as the cloud-based Decision API. Because the cloud-based Decision API is multi-tenant, and the on-premise version is single-tenant, there are some added connectors in the cloud-based version such as storage interface for visitor assignments. But the decision business logic is completely identical.
How does it work?
The Decision API is a Go based binary or docker image that synchronizes with the use cases you created on the Flagship platform. The decision logic (context targeting & visitor assignments) is done locally, which makes it really fast.
Here is a high level overview of the On premise Decision API architecture:
Why self-host the Decision API
There are multiple reasons why you would want to host the Decision API yourself, such as:
- You need a < 30ms response time from the API
- You have a volume of API calls that goes beyond the cloud based Decision API hard limits
- You have network restrictions setup that prevent your apps from calling an external service
- You do not want to use the SDK with the bucketing mode for any reason (like not wanting to add a third-party dependancy to your stack)
Caveats
Visitor assignments caching
Thanks to our hashing algorithm, a unique visitor (with a unique ID) will always see the same variation of a use case, if the traffic allocation of the variations stays the same.
If you're using a high level Flagship feature such as Experience Continuity, 1 visitor 1 experiment or Dynamic Allocation, or if you manually change the traffic allocation of your variations, the hashing algorithm is not enough. You need some sort of assignment cache to store the visitor assignment to the variation.
Flagship cloud-hosted Decision API provides such a caching mechanism for you by default using our own high-velocity key-value store.
If you want to use those features when hosting the DecisionAPI on premise, you need to configure your own cache to store visitor assignments. See the Configuration below to set it up.
Installation
The Flagship Decision API can be installed and deployed in your infrastructure either by downloading and running the binary, or pulling and running the docker image in your orchestration system.
Using a binary
You can download the latest binary here: https://github.com/flagship-io/decision-api/releases
Using a Docker image
You can pull the latest docker image from docker hub:
docker pull flagshipio/decision-api
Running
Using a binary
Download the latest release on github and then simply run:
ENV_ID={your_environment_id} API_KEY={your_api_key} ./decision-api
The server will run on the port 8080 by default. You can override this configuration (see Configuration)
Running with Docker
Run the following command to start the server with Docker
docker run -p 8080:8080 -e ENV_ID={your_env_id} -e API_KEY={your_api_key} flagshipio/decision-api
Configuration
You can configure the Decision API using 2 ways:
- YAML configuration file
- Environment Variables
Using a configuration file
Create a config.yaml
along your app file, or mount it in docker in location /config.yaml:
docker run -p 8080:8080 -v ./config.yaml:/config.yaml flagshipio/decision-api
The configuration file should look like this:
env_id: "env_id" # Your Flagship Environment ID
api_key: "api_key" # Your Flagship API Key
address: ":8080" # the listening address of the server
log:
level: "warn" # minimal log level to output
format: "text" # log format output
polling_internal: "1m" # the polling interval to synchronize with Flagship platform
# Cache
cache:
type: local # or 'redis' or 'none' (if you do not want to using visitor cache)
options:
dbPath: ./data
#redisHost: 'localhost:6379' # for redis storage
#redisUsername: username # (Optional) for redis storage
#redisPassword: password # (Optional) for redis storage
#redisDb: 0 # (Optional) for redis storage
Using environment variables
You can override each configuration variables from the configuration file using environment variables.
Just name your env variables the same as the config file, but with the following rules:
- Env variable name should be UPPERCASE
Example: ENV_ID - Sub configuration level are defined using a
_
sign
Example: CACHE_TYPE
Example: CACHE_OPTIONS_DBPATH
Here is a Docker example using environment variables to setup local caching:
docker run -p 8080:8080 -e ENV_ID={your_env_id} -e API_KEY={your_api_key} -e CACHE_TYPE=local -e CACHE_OPTIONS_DBPATH=./data -v ./config.yaml:/config.yaml flagshipio/decision-api
Here is a Docker Compose example of using Redis as a visitor cache engine:
version: "3"
services:
decision:
image: flagshipio/decision-api
ports:
- 8080:8080
environment:
ENV_ID: "env_id"
API_KEY: "api_key"
CACHE_TYPE: redis
CACHE_OPTIONS_REDISHOST: "redis:6379"
depends_on:
- redis
redis:
image: redis
That you can run using docker-compose up -d
Configuration parameters
You can use the following parameters to customize the Decision API.
Each parameter is named as in the config.yaml file, and the matching environment variable is parenthesis.
Parameter | Type | Required | |
---|---|---|---|
env_id (ENV_ID) | string | yes | The Flagship environment ID. You can get it from the Flagship platform. Default to empty string |
api_key (API_KEY) | string | yes | The Flagship API Key for this environment ID. You can get it from the Flagship platform. Default to empty string |
address (ADDRESS) | string | no | The server address to listen for requests. Default to ":8080" |
cors.enabled (CORS_ENABLED) | bool | no | If true, the server will return the cors response headers necessary for cross origins API calls. Default to true |
cors.allowed_origins (CORS_ALLOWED_ORIGINS) | string | no | If the cors are enabled, this option will set the Access-Control-Allow-Origin response headers. Default to "*" |
log.level (LOG_LEVEL) | string | no | Set the minimum log level that will be send to output. Can be trace, debug, info, warn, error, fatal, panic. Default to "warning" |
log.format (LOG_FORMAT) | string | no | Set the output log format. Can be either "text" or "json". Default to "text" |
polling_interval (POLLING_INTERVAL) | string | no | The polling frequency (as parsable by the ParseDuration method) to synchronize with your Flagship configuration. Default to 60s |
cache.type (CACHE_TYPE) | string | no | If you want to enable caching for the visitor assignment. Can be "memory", "redis", "dynamo" or "local". Default to empty string. |
cache.options.dbPath (CACHE_OPTIONS_DBPATH) | string | no | If you chose local cache type, this is the path of the file where the cache will be stored. Default to empty string |
cache.options.redisHost (CACHE_OPTIONS_REDISHOST) | string | no | If you chose redis cache type, this is the host for your redis server |
cache.options.redisUsername (CACHE_OPTIONS_REDISUSERNAME) | string | no | If you chose redis cache type, this is the username for your redis server |
cache.options.redisUsername (CACHE_OPTIONS_REDISPASSWORD) | string | no | If you chose redis cache type, this is the password for your redis server |
cache.options.redisDb (CACHE_OPTIONS_REDISDB) | int | no | If you chose redis cache type, this is the db number for your redis server. Default to 0 (default DB) |
cache.options.redisTls (CACHE_OPTIONS_REDISTLS) | bool | no | If true, redis client will be set to connect using TLS to the redis server. Default to false |
cache.options.dynamoTableName (CACHE_OPTIONS_DYNAMOTABLENAME) | string | no | The table name to store cache assignments when using DynamoDB. Default to "visitor-assignments" |
cache.options.dynamoPKSeparator (CACHE_OPTIONS_DYNAMOPKSEPARATOR) | string | no | The primary key separator between env ID & visitor ID to store cache assignments when using DynamoDB. Default to "." |
cache.options.dynamoPKField (CACHE_OPTIONS_DYNAMOPKFIELD) | string | no | The primary key field name to store cache assignments when using DynamoDB. Default to "id" |
cache.options.dynamoGetTimeout (CACHE_OPTIONS_DYNAMOGETTIMEOUT) | string | no | The timeout for getting previously stored visitor cache assignment when using DynamoDB. Default to 1s |
Custom visitor cache assignment connector
The Decision API provides 3 cache systems to store and retrieve visitor assignments to allow traffic allocation changes for live use cases:
- in memory: the assignments are stored in memory
- local: the assignments are stored in a local file using a key/value database
- redis: the assignments are stored in a redis server
If you want to use another cache management system, you still can, but you will need to create a Go application that runs the Decision API package, and implement your own visitor assignment interface.
Here is an example of how you can do that:
package main
import (
"log"
"net/http"
"os"
"time"
"github.com/flagship-io/decision-api/pkg/connectors"
"github.com/flagship-io/decision-api/pkg/server"
common "github.com/flagship-io/flagship-common"
)
type CustomAssignmentManager struct {
}
func (m *CustomAssignmentManager) LoadAssignments(envID string, visitorID string) (*common.VisitorAssignments, error) {
// TODO implement this method
return nil, nil
}
func (m *CustomAssignmentManager) ShouldSaveAssignments(context connectors.SaveAssignmentsContext) bool {
// TODO implement this method
return true
}
func (m *CustomAssignmentManager) SaveAssignments(envID string, visitorID string, vgIDAssignments map[string]*common.VisitorCache, date time.Time) error {
// TODO implement this method
return nil
}
func main() {
srv, err := server.CreateServer(
os.Getenv("ENV_ID"),
os.Getenv("API_KEY"),
":8080",
server.WithAssignmentsManager(&CustomAssignmentManager{}),
)
if err != nil {
log.Fatalf("error when creating server: %v", err)
}
log.Printf("server listening on :8080")
if err := srv.Listen(); err != http.ErrServerClosed {
log.Fatalf("error when starting server: %v", err)
}
}
API Reference
The self-hosted Decision API has the same relative endpoints than the cloud-base Decision API V2 (latest) . The only difference is that you do not need to put your environment ID inside the URL path (because the environment ID is already configured when starting the Decision API).
So instead of calling
https://decision.flagship.io/v2/{{YOUR_ENVIRONMENT_ID}}/campaigns
You would call
https://your.decision.api.instance/v2/campaigns
You can find the Swagger API doc at the /v2/swagger/index.html
URL when running the application.
Metrics endpoint
On top of the usual endpoints, the open source Decision API also exposes a /v2/metrics endpoint, which can be used to monitor the API performance, errors and response time.
The metrics ouput format looks like:
{
"cmdline": [
"./bin/server"
],
"handlers.activate.errors": 0,
"handlers.activate.response_time.p50": 0,
"handlers.activate.response_time.p90": 0,
"handlers.activate.response_time.p95": 0,
"handlers.activate.response_time.p99": 0,
"handlers.campaign.errors": 0,
"handlers.campaign.response_time.p50": 0,
"handlers.campaign.response_time.p90": 0,
"handlers.campaign.response_time.p95": 0,
"handlers.campaign.response_time.p99": 0,
"handlers.campaigns.errors": 0,
"handlers.campaigns.response_time.p50": 1,
"handlers.campaigns.response_time.p90": 4,
"handlers.campaigns.response_time.p95": 10,
"handlers.campaigns.response_time.p99": 11,
"handlers.flags.errors": 0,
"handlers.flags.response_time.p50": 1,
"handlers.flags.response_time.p90": 1,
"handlers.flags.response_time.p95": 1,
"handlers.flags.response_time.p99": 1,
"memstats": {
// typical https://pkg.go.dev/runtime#MemStats struct
}
}
Updated 11 months ago