Skip to content

Configuration Management

CoSky's Configuration Management subsystem provides a centralized, versioned, and auditable way to store and distribute microservice configuration data backed by Redis. In a distributed microservice environment, dozens or hundreds of services need consistent, real-time access to shared configuration -- connection strings, feature flags, feature toggles, and operational parameters. CoSky solves this by leveraging Redis's native hash structures and Lua scripting to deliver atomic, high-throughput configuration operations with automatic version history and rollback capabilities, all without requiring any additional infrastructure beyond the Redis instance you already run.

At a Glance

ComponentResponsibilityKey FileSource
ConfigServiceCore CRUD interface for configuration operationsConfigService.ktConfigService.kt:24
Config / ConfigDataData model holding config payload, hash, version, timestampsConfig.ktConfig.kt:20
ConfigCodecSerialization/deserialization between Redis hash maps and domain objectsConfigCodec.ktConfigCodec.kt:20
ConfigHistoryExtended config model with operation type and operation timestampConfigHistory.ktConfigHistory.kt:20
ConfigVersionLightweight interface pairing a configId with its version numberConfigVersion.ktConfigVersion.kt:20
ConfigRollbackInterface for rolling back to a prior config versionConfigRollback.ktConfigRollback.kt:24
ConfigKeyGeneratorGenerates Redis key patterns for config indices, history, and entriesConfigKeyGenerator.ktConfigKeyGenerator.kt:22
RedisConfigServiceRedis-backed implementation of ConfigService using Lua scriptsRedisConfigService.ktRedisConfigService.kt:41
ConfigRedisScriptsLoads and caches the Lua scripts for atomic config operationsConfigRedisScripts.ktConfigRedisScripts.kt:24
ConfigChangedEventEvent emitted on config changes (set, remove, rollback) via Redis PubSubConfigChangedEvent.ktConfigChangedEvent.kt:20

ConfigService Interface

The ConfigService interface defines the complete contract for configuration CRUD operations. It extends ConfigRollback to inherit version history and rollback capabilities. All methods return Project Reactor types (Mono / Flux) for non-blocking, reactive execution.

MethodReturn TypeDescriptionSource
getConfigs(namespace)Flux<String>Lists all config IDs in a namespace by reading the config index setConfigService.kt:25
getConfig(namespace, configId)Mono<Config>Retrieves a single configuration entry with its data, hash, version, and timestampsConfigService.kt:26
setConfig(namespace, configId, data)Mono<Boolean>Creates or updates a configuration; atomically stores data, increments version, archives prior version as history, and publishes a change eventConfigService.kt:27
removeConfig(namespace, configId)Mono<Boolean>Removes a configuration and archives it to history; publishes a remove eventConfigService.kt:28
containsConfig(namespace, configId)Mono<Boolean>Checks whether a configuration entry exists via Redis EXISTSConfigService.kt:29

In addition, the inherited ConfigRollback interface adds:

MethodReturn TypeDescriptionSource
rollback(namespace, configId, targetVersion)Mono<Boolean>Restores configuration to a prior version; archives current version and publishes a rollback eventConfigRollback.kt:30
getConfigVersions(namespace, configId)Flux<ConfigVersion>Lists the version history (up to 10 entries) in reverse chronological orderConfigRollback.kt:32
getConfigHistory(namespace, configId, version)Mono<ConfigHistory>Retrieves the archived configuration for a specific versionConfigRollback.kt:34

Data Model

The configuration data model is built on a clean interface hierarchy that separates versioning from full configuration data.

mermaid
classDiagram
    class ConfigVersion {
        <<interface>>
        +configId: String
        +version: Int
    }

    class Config {
        <<interface>>
        +data: String
        +hash: String
        +createTime: Long
    }

    class ConfigData {
        +configId: String
        +data: String
        +hash: String
        +createTime: Long
        +version: Int
    }

    class ConfigHistory {
        +configId: String
        +data: String
        +hash: String
        +createTime: Long
        +version: Int
        +op: String
        +opTime: Long
    }

    class ConfigVersionData {
        +configId: String
        +version: Int
    }

    class NamespacedConfigId {
        +namespace: String
        +configId: String
    }

    ConfigVersion <|.. Config
    ConfigVersion <|.. ConfigVersionData
    Config <|.. ConfigData
    Config <|.. ConfigHistory

Config

The Config interface extends ConfigVersion and adds the payload (data), content hash (hash), and creation timestamp. The ConfigData data class is the concrete implementation used for current configuration entries.

  • configId -- Unique identifier for the configuration within a namespace
  • data -- The raw configuration payload (typically YAML, JSON, or properties text)
  • hash -- SHA-256 hash of the data, used for change detection and deduplication
  • version -- Monotonically increasing version number
  • createTime -- Unix timestamp when the version was created

Source: Config.kt:20

ConfigHistory

ConfigHistory extends Config with two additional fields that record audit information for archived versions:

  • op -- The operation that caused this version to be archived: set, remove, or rollback
  • opTime -- Unix timestamp when the archive operation occurred

Source: ConfigHistory.kt:20

ConfigCodec

ConfigCodec is a singleton object providing Kotlin extension functions to decode Redis hash maps (Map<String, String>) into domain objects:

  • decodeAsConfig() -- Converts a hash map into a ConfigData instance
  • decodeAsHistory() -- Converts a hash map into a ConfigHistory instance (includes op and opTime fields)

Source: ConfigCodec.kt:20

Redis Implementation

The RedisConfigService implements all ConfigService and ConfigRollback operations using Redis hash structures and atomic Lua scripts. This ensures that multi-step operations (version increment + history archival + event publishing) execute as a single atomic unit.

mermaid
classDiagram
    class ConfigService {
        <<interface>>
        +getConfigs(namespace) Flux~String~
        +getConfig(namespace, configId) Mono~Config~
        +setConfig(namespace, configId, data) Mono~Boolean~
        +removeConfig(namespace, configId) Mono~Boolean~
        +containsConfig(namespace, configId) Mono~Boolean~
    }

    class ConfigRollback {
        <<interface>>
        +rollback(namespace, configId, targetVersion) Mono~Boolean~
        +getConfigVersions(namespace, configId) Flux~ConfigVersion~
        +getConfigHistory(namespace, configId, version) Mono~ConfigHistory~
    }

    class RedisConfigService {
        -redisTemplate: ReactiveStringRedisTemplate
        +getConfigs(namespace) Flux~String~
        +getConfig(namespace, configId) Mono~Config~
        +setConfig(namespace, configId, data) Mono~Boolean~
        +removeConfig(namespace, configId) Mono~Boolean~
        +containsConfig(namespace, configId) Mono~Boolean~
        +rollback(namespace, configId, targetVersion) Mono~Boolean~
        +getConfigVersions(namespace, configId) Flux~ConfigVersion~
        +getConfigHistory(namespace, configId, version) Mono~ConfigHistory~
        -getAndDecodeConfig(key, decode) Mono~T~
        -ensureNamespacedConfigId(namespace, configId)
    }

    class ConfigRedisScripts {
        <<object>>
        +SCRIPT_CONFIG_SET: RedisScript~Boolean~
        +SCRIPT_CONFIG_REMOVE: RedisScript~Boolean~
        +SCRIPT_CONFIG_ROLLBACK: RedisScript~Boolean~
    }

    ConfigService <|.. RedisConfigService
    ConfigRollback <|.. ConfigService
    RedisConfigService ..> ConfigRedisScripts : uses Lua scripts

ConfigRedisScripts

The ConfigRedisScripts object pre-loads three Lua scripts from the classpath at startup. Each script corresponds to a write operation and is evaluated atomically by Redis:

ScriptResource FilePurpose
SCRIPT_CONFIG_SETconfig_set.luaAtomically sets config data, increments version, archives previous version to history, and publishes a set change event via Redis PubSub
SCRIPT_CONFIG_REMOVEconfig_remove.luaAtomically removes a config from the index, archives it to history, and publishes a remove change event
SCRIPT_CONFIG_ROLLBACKconfig_rollback.luaAtomically restores config to a target version from history, creates a new version entry, and publishes a rollback change event

Source: ConfigRedisScripts.kt:24

getConfig Flow

When an application reads configuration, the request flows through the reactive pipeline to retrieve the Redis hash and decode it.

mermaid
sequenceDiagram
    autonumber
    participant App as Application
    participant RCS as RedisConfigService
    participant RST as ReactiveStringRedisTemplate
    participant Redis as Redis

    App->>RCS: getConfig(namespace, configId)
    RCS->>RCS: ensureNamespacedConfigId()
    RCS->>RCS: build configKey = "{namespace}:cfg:{configId}"
    RCS->>RST: opsForHash().entries(configKey)
    RST->>Redis: HGETALL {namespace}:cfg:{configId}
    Redis-->>RST: Map~String,String~
    RST-->>RCS: Mono~Map~
    RCS->>RCS: ConfigCodec.decodeAsConfig()
    RCS-->>App: Mono~Config~

setConfig Flow

Writing configuration is an atomic operation performed by a Lua script that handles version management, history archival, change detection, and event publishing in a single Redis call.

mermaid
sequenceDiagram
    autonumber
    participant App as Application
    participant RCS as RedisConfigService
    participant RST as ReactiveStringRedisTemplate
    participant Redis as Redis (Lua)
    participant PS as Redis PubSub

    App->>RCS: setConfig(namespace, configId, data)
    RCS->>RCS: compute SHA-256 hash of data
    RCS->>RST: execute(SCRIPT_CONFIG_SET, keys=[namespace], args=[configId, data, hash])
    RST->>Redis: EVAL config_set.lua

    Note over Redis: 1. HGET current hash<br>2. If hash unchanged, return 0<br>3. SADD to config index<br>4. Archive prior version to history<br>5. HMSET new config entry<br>6. PUBLISH change event

    Redis->>PS: PUBLISH {namespace}:cfg:{configId} "set"
    Redis-->>RST: Boolean
    RST-->>RCS: Mono~Boolean~
    RCS-->>App: Mono~Boolean~

Redis Key Structure

The ConfigKeyGenerator object generates all Redis keys following a consistent namespaced pattern. This ensures key isolation across namespaces and provides human-readable key names.

mermaid
flowchart LR
    subgraph keys["Redis Key Patterns"]
        style keys fill:#161b22,stroke:#30363d,color:#e6edf3
        A["{ns}:cfg_idx<br><i>SET - config index</i>"]
        B["{ns}:cfg:{configId}<br><i>HASH - current config</i>"]
        C["{ns}:cfg_htr_idx:{configId}<br><i>ZSET - history index</i>"]
        D["{ns}:cfg_htr:{configId}:{version}<br><i>HASH - history entry</i>"]
    end

    A -->|contains| B
    B -->|archives to| D
    C -->|tracks| D
Key PatternRedis TypePurpose
{namespace}:cfg_idxSETIndex of all current config keys in a namespace
{namespace}:cfg:{configId}HASHCurrent config entry with fields: configId, data, hash, version, createTime
{namespace}:cfg_htr_idx:{configId}ZSETSorted set of history entries for a config, scored by version number (up to 10 entries)
{namespace}:cfg_htr:{configId}:{version}HASHArchived config snapshot with additional op and opTime fields

Source: ConfigKeyGenerator.kt:22

API Usage Examples

When integrated with Spring Cloud, CoSky serves as a PropertySource that loads configuration from Redis. Here is a typical bootstrap.yaml setup:

yaml
spring:
  application:
    name: ${service.name:cosky}
  data:
    redis:
      url: redis://localhost:6379
  cloud:
    cosky:
      namespace: ${cosky.namespace:cosky-production}
      config:
        config-id: ${spring.application.name}.yaml
Config PropertyDescriptionExample
spring.data.redis.urlRedis connection URLredis://localhost:6379
spring.cloud.cosky.namespaceNamespace for config isolationcosky-production
spring.cloud.cosky.config.config-idThe configId to load (typically {app}.yaml)order-service.yaml

Source: README.md:89

Rollback Mechanism

CoSky maintains up to 10 historical versions per configuration entry (defined by ConfigRollback.HISTORY_SIZE). Each time a configuration is set or removed, the prior version is atomically archived to a history entry via the Lua script. The rollback operation restores a target version by reading its archived data and creating a new version.

mermaid
stateDiagram-v2
    [*] --> Created: setConfig (first write)
    Created --> Updated: setConfig (new data)
    Updated --> Updated: setConfig (new data)
    Updated --> Removed: removeConfig
    Removed --> Restored: rollback
    Updated --> Restored: rollback (to older version)
    Restored --> Updated: setConfig (new data)
    Restored --> Removed: removeConfig

    state Created {
        [*] --> Version1: version=1
    }

    state Updated {
        [*] --> VersionN: version=N
    }

    state Restored {
        [*] --> VersionNPlus1: version=N+1 (from history)
    }

    state Removed {
        [*] --> Archived: moved to history
    }

The config_rollback.lua script performs the following steps atomically:

  1. Reads the target version's history entry
  2. Compares hashes to avoid no-op rollbacks
  3. Archives the current version to history
  4. Writes the historical data as a new version with an incremented version number
  5. Publishes a rollback event via Redis PubSub

Source: config_rollback.lua:1

Configuration Lifecycle

The following diagram illustrates the complete lifecycle of a configuration entry from creation through versioned updates, rollbacks, and removal.

mermaid
flowchart TD
    subgraph lifecycle["Config Lifecycle"]
        style lifecycle fill:#161b22,stroke:#30363d,color:#e6edf3
        A["Create Config<br>setConfig(namespace, id, data)"]
        B{"Hash changed?"}
        C["Write to Redis<br>HMSET cfg:{id}"]
        D["Add to Index<br>SADD cfg_idx"]
        E["Version++ / Archive Prior"]
        F["Publish Event<br>PUBLISH cfg:{id} 'set'"]
        G["Update Config<br>setConfig(namespace, id, newData)"]
        H["Remove Config<br>removeConfig(namespace, id)"]
        I["Archive to History<br>ZADD cfg_htr_idx"]
        J["Rollback<br>rollback(namespace, id, targetVer)"]
        K["Read History Entry"]
        L["Write as New Version"]
        M["Publish 'rollback' Event"]
        N["Config Removed"]
    end

    A --> B
    B -->|No| A
    B -->|Yes| D --> C --> E --> F
    G --> B
    H --> I --> N
    J --> K --> L --> M
    F --> G
    F --> H
    F --> J
    M --> G
  • Consistency Layer -- Learn how CoSky achieves 1000x performance improvement through local caching and Redis PubSub invalidation
  • Service Discovery -- Service registration and discovery with similar architecture patterns
  • REST API -- HTTP endpoints for configuration management

References

Released under the Apache License 2.0.