
Feature flags — это переключатели для частей функциональности, которые позволяют включать/выключать поведение приложения без деплоя. Для бизнеса это означает:
Рекомендованная простая архитектура:
Преимущества: консистентность и простота, без отдельного коммерческого сервиса. Минусы: вам нужно реализовать rollout-логику и TTL/инвалидацию кэша.
-- Таблица флагов функций
CREATE TABLE feature_flags (
key text PRIMARY KEY,
enabled boolean NOT NULL DEFAULT false,
rollout_percent integer NOT NULL DEFAULT 0, -- 0..100
targeting jsonb DEFAULT '{}'::jsonb, -- произвольные правила, например {"countries": ["RU"]}
updated_at timestamptz NOT NULL DEFAULT now()
);
-- История изменений (опционально)
CREATE TABLE feature_flag_events (
id bigserial PRIMARY KEY,
flag_key text NOT NULL REFERENCES feature_flags(key),
action text NOT NULL,
payload jsonb,
created_at timestamptz NOT NULL DEFAULT now()
);
Пояснение: храните rollout_percent и targeting как простую структуру. Это даёт гибкость и лёгкость миграций.
Пример middleware: при запросе сначала проверяем Redis, если нет — читаем из Postgres и записываем в Redis с TTL.
// featureFlags.js
const crypto = require('crypto');
const { Pool } = require('pg');
const Redis = require('ioredis');
const pg = new Pool({ connectionString: process.env.DATABASE_URL });
const redis = new Redis(process.env.REDIS_URL);
// Хелпер — честный хеш для процентного rollout
function hashPercent(key, identifier) {
const h = crypto.createHash('sha1').update(`${key}:${identifier}`).digest();
// берем первые 4 байта как uint32
const num = h.readUInt32BE(0);
return (num / 0xffffffff) * 100; // 0..100
}
async function getFlagFromDb(key) {
const res = await pg.query('SELECT enabled, rollout_percent, targeting FROM feature_flags WHERE key = $1', [key]);
if (!res.rowCount) return null;
return res.rows[0];
}
async function getFlag(key) {
const cacheKey = `flag:${key}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const row = await getFlagFromDb(key);
// кешируем даже null на короткое время чтобы уменьшить нагрузку
await redis.set(cacheKey, JSON.stringify(row), 'EX', 30);
return row;
}
// Проверка флага для конкретного пользователя
async function isFlagEnabledFor(key, context = {}) {
const f = await getFlag(key);
if (!f) return false;
if (f.enabled && (!f.rollout_percent || f.rollout_percent === 100)) return true;
// таргетинг по простым правилaм (пример)
if (f.targeting && f.targeting.countries) {
const country = context.country;
if (country && f.targeting.countries.includes(country)) return true;
}
// процентный rollout
if (f.rollout_percent && context.userId) {
const p = hashPercent(key, String(context.userId));
return p < f.rollout_percent;
}
return false;
}
module.exports = { isFlagEnabledFor };
Использование в обработчике Express:
const express = require('express');
const { isFlagEnabledFor } = require('./featureFlags');
const app = express();
app.get('/checkout', async (req, res) => {
const userId = req.user && req.user.id;
const ctx = { userId, country: req.headers['x-country'] };
if (await isFlagEnabledFor('new_checkout', ctx)) {
return res.send('Новый checkout');
}
res.send('Старый checkout');
});
Feature flags — недорогой инструмент в руках команды: они ускоряют выпуск фич, снижают риски и дают гибкость для экспериментов. Простая архитектура на PostgreSQL как «источнике правды» + Redis для быстрого доступа даёт баланс надёжности и производительности, а пара чистых правил для lifecycle (создание, владельцы, критерии успеха, удаление) минимизирует долг и инциденты.
Реализовав всё описанное, вы получите готовый к реальным кейсам механизм, который приносит прямую бизнес‑выгоду: быстрее время до рынка, меньше регрессий и прозрачный путь для экспериментов.