Skip to main content
darkship docs
Open console

Concepts · Evaluation

Hybrid evaluation

Server code evaluates flags locally from an in-memory ruleset. Browsers and phones ask us to evaluate and get answers, never rules. Same logic, different trust boundaries.

Updated Oct 28, 20263 min read Edit this page on GitHub (opens in a new tab)

Two paths, one evaluator#

A flag evaluation answers one question: for this flag, in this environment, and this context (a user key plus attributes), which variation do we serve? Where that question is answered matters for latency, availability and what data is exposed.

Darkship answers it in two places. Pick a path below to see which parts of the system it touches.

Evaluation path explorer#

Evaluation pathsThe control plane writes a snapshot to Redis. The eval service holds it in memory. Server SDKs download the whole ruleset and evaluate locally. Client SDKs send a context and receive resolved values only. Console + APIflag changes Redisversioned snapshot eval serviceL1 in-memory copyETag = content hash 1 write-through 2 pub/subinvalidate Your backendserver SDK · ds_srv_ full ruleset in memory 3 GET /v1/sdk/snapshotIf-None-Match → 304 boolVariation() = 0 network calls Browser / appclient SDK · ds_cli_ values for this context 3 POST /v1/sdk/evaluatecontext in, values out Rules, segments and salts never leavethe left half of this diagram unlessyou hold a server key.
SERVER PATH Ruleset downloaded once, revalidated with ETag; every evaluation after that is a function call in your process.

Server vs client at a glance#

Behaviour of the two evaluation modes.
PropertyServer SDKClient SDK
Keyds_srv_ (secret)ds_cli_ (public by design)
Where it evaluatesIn your process, from a local rulesetOn the eval service, via POST /v1/sdk/evaluate
Network on each callNoneNone after the first fetch; results cached per context for the session
ReceivesRules, segments, salts, schemasResolved values for one context
Context leaves the processNeverSent for evaluation, used in memory, discarded
Counts as a billable requestEach ruleset fetch, including 304 revalidationsEach remote evaluation
Survives an eval outageYes, keeps the last rulesetYes, from the SDK's session cache; defaults for new contexts

Choosing a key#

  • Anything you run: API servers, workers, cron jobs, serverless functions. Use a server key and the Node or Python SDK.
  • Anything you ship: web pages, mobile apps, desktop apps. Use a client key. Treat what it reveals (the values of your flags for a context you supply) as public.
  • Other languages: call the REST API; there is no local evaluation without an SDK. See the data plane endpoints.
Client keys are not secrets

A client key can only evaluate; it cannot read rules or change anything. You can restrict it to an allow-list of origins, but that is defence in depth, not authentication. Never put anything in a flag value that must stay confidential from your own users.

One implementation, the same answer#

Rule matching, percentage bucketing and type handling are specified once and verified in every SDK and in the eval service against a shared set of language-neutral conformance fixtures (ruleset + context → expected variation). If the Python SDK, the JavaScript SDK and the remote endpoint disagree for the same ruleset and context, that is a bug in us, not a feature.

The fixed algorithm is described in Targeting and rollouts.

What we promise#

Service objectives for the evaluation plane.
ObjectiveTarget
eval availability99.9%
eval latency, server-side, warm cachep95 < 50 ms
Console click to SDK seeing the changep95 < 5 s

These apply to the service. A server SDK evaluating locally has no network round trip to measure.

Before the first ruleset arrives#

Evaluation never blocks and never throws. If you call it before the ruleset has loaded, you receive the default you passed, with reason NOT_READY. To avoid serving defaults on a cold start, either await waitForReady() at boot, or pass a bootstrap snapshot (see init options).

Was this helpful?