Если единственное, что вы знаете о своём решении CAPTCHA, — это «сейчас всё работает», о деградации вы узнаете от пользователей, а не от графика. Ряд наблюдений во времени отвечает на другой вопрос: что изменилось, когда именно и куда это движется. Ниже — рабочая схема метрик, код инструментирования на Python и JavaScript, запросы для Prometheus и InfluxDB и пороги, по которым имеет смысл ставить алерты.
Сбор метрик стоит подключать сразу, на первой интеграции — вместе с быстрым стартом CaptchaAI. Дописать инструментирование в работающий воркер дороже, чем заложить четыре счётчика в первый прототип.
Какие метрики решения CAPTCHA действительно нужны
Начните с минимального набора. Семь рядов ниже покрывают почти все инциденты, которые случаются с очередью решения на практике.
| Метрика | Тип | Зачем |
|---|---|---|
| Доля успешных решений (%) | Gauge | Видно изменение качества на стороне провайдера |
| Задержка решения (мс) | Histogram | Замедления и подбор тайм-аутов |
| Частота ошибок по коду | Counter | Ранние признаки новой проблемы |
| Стоимость решения ($) | Gauge | Контроль бюджета, аномалии расхода |
| Глубина очереди | Gauge | Планирование числа потоков |
| Токены, истёкшие до использования | Counter | Сигнал к настройке TTL |
| Баланс аккаунта | Gauge | Триггер пополнения |
Отдельно про стоимость. CaptchaAI тарифицируется по потокам, а не по числу решений: BASIC ($15/мес, 5 потоков), STANDARD ($30/мес, 15 потоков), ADVANCE ($90/мес, 50 потоков), PREMIUM ($170/мес, 100 потоков), CORPORATE ($240/мес, 150 потоков), ENTERPRISE ($300/мес, 200 потоков), а выше идут VIP-тарифы: 1 000 потоков — VIP-1 ($1,500/мес), 3 000 потоков — VIP-2 ($4,500/мес), 5 000 потоков — VIP-3 ($7,500/мес). Число решений внутри плана не ограничено, поэтому ряд «стоимость» полезен не как счёт к оплате, а как способ пересчитать фактическую цену одного решения при вашей загрузке.
Схема с Prometheus и Python
Prometheus — самый дешёвый вход, если он уже стоит в вашем контуре для инфраструктурных метрик. Воркер решения обычно живёт короткими процессами или задачами очереди, поэтому pull-модель к нему не подходит: пишем через Push Gateway.
Инструментируйте свой воркер
import os
import time
import requests
from prometheus_client import CollectorRegistry, Counter, Histogram, Gauge, push_to_gateway
registry = CollectorRegistry()
SOLVE_TOTAL = Counter(
"captcha_solve_total", "Total CAPTCHA solve attempts",
["type", "status"], registry=registry
)
SOLVE_LATENCY = Histogram(
"captcha_solve_latency_seconds", "CAPTCHA solve latency",
["type"], buckets=[5, 10, 15, 20, 30, 45, 60, 90, 120],
registry=registry
)
SOLVE_COST = Counter(
"captcha_solve_cost_dollars", "Total cost of CAPTCHA solves",
["type"], registry=registry
)
API_BALANCE = Gauge(
"captcha_api_balance_dollars", "CaptchaAI account balance",
registry=registry
)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
PUSHGATEWAY = os.environ.get("PUSHGATEWAY_URL", "localhost:9091")
def solve_with_metrics(sitekey, pageurl, captcha_type="recaptcha_v2"):
start = time.time()
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1
})
data = resp.json()
if data.get("status") != 1:
SOLVE_TOTAL.labels(type=captcha_type, status="submit_error").inc()
push_metrics()
return {"error": data.get("request")}
captcha_id = data["request"]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get",
"id": captcha_id, "json": 1
}).json()
if result.get("status") == 1:
elapsed = time.time() - start
SOLVE_TOTAL.labels(type=captcha_type, status="solved").inc()
SOLVE_LATENCY.labels(type=captcha_type).observe(elapsed)
SOLVE_COST.labels(type=captcha_type).inc(0.00299)
push_metrics()
return {"solution": result["request"]}
if result.get("request") != "CAPCHA_NOT_READY":
SOLVE_TOTAL.labels(type=captcha_type, status="error").inc()
push_metrics()
return {"error": result.get("request")}
SOLVE_TOTAL.labels(type=captcha_type, status="timeout").inc()
push_metrics()
return {"error": "TIMEOUT"}
def push_metrics():
try:
push_to_gateway(PUSHGATEWAY, job="captcha_solver", registry=registry)
except Exception:
pass # Don't fail solving because metrics push failed
def update_balance():
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance"
})
try:
balance = float(resp.text)
API_BALANCE.set(balance)
push_metrics()
except ValueError:
pass
Обратите внимание на push_metrics(): отправка метрик обёрнута в try/except и молча гасит исключения. Это сделано намеренно — недоступность Push Gateway не должна ронять решение CAPTCHA. Границы бакетов гистограммы (5–120 с) подобраны под реальное время решения, а не под обычные HTTP-запросы: бакеты по умолчанию дадут бесполезные перцентили.
Запросы PromQL
# Success rate over last hour
rate(captcha_solve_total{status="solved"}[1h])
/ rate(captcha_solve_total[1h]) * 100
# P95 solve latency
histogram_quantile(0.95, rate(captcha_solve_latency_seconds_bucket[1h]))
# Error rate by type
rate(captcha_solve_total{status="error"}[1h])
# Hourly cost
increase(captcha_solve_cost_dollars_total[1h])
Схема с InfluxDB
InfluxDB удобнее, когда метрики решения нужны как отдельное хранилище с высокой кардинальностью — например, если вы тегируете точки по типу CAPTCHA, коду ошибки и клиентскому проекту одновременно.
Запись показателей решения
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
INFLUX_URL = os.environ.get("INFLUX_URL", "http://localhost:8086")
INFLUX_TOKEN = os.environ.get("INFLUX_TOKEN", "")
INFLUX_ORG = os.environ.get("INFLUX_ORG", "captcha")
INFLUX_BUCKET = os.environ.get("INFLUX_BUCKET", "captcha_metrics")
influx_client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = influx_client.write_api(write_options=SYNCHRONOUS)
def record_solve_metric(captcha_type, status, elapsed_ms, cost=0.0, error=None):
point = (
Point("captcha_solve")
.tag("type", captcha_type)
.tag("status", status)
.field("elapsed_ms", elapsed_ms)
.field("cost", cost)
.field("success", 1 if status == "solved" else 0)
)
if error:
point = point.tag("error_code", error)
write_api.write(bucket=INFLUX_BUCKET, record=point)
def record_balance(balance):
point = Point("captcha_balance").field("balance", balance)
write_api.write(bucket=INFLUX_BUCKET, record=point)
Запросы на Flux
// Success rate over last 24 hours (1-hour windows)
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "success")
|> aggregateWindow(every: 1h, fn: mean)
|> map(fn: (r) => ({r with _value: r._value * 100.0}))
|> yield(name: "success_rate")
// Average solve time by type
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "elapsed_ms" and r.status == "solved")
|> group(columns: ["type"])
|> aggregateWindow(every: 1h, fn: mean)
|> yield(name: "avg_latency")
// Cumulative cost
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "cost")
|> cumulativeSum()
|> yield(name: "cumulative_cost")
Реализация на JavaScript
Для Node.js-воркеров подход тот же: prom-client, те же имена метрик и те же границы бакетов, чтобы графики Python- и Node-воркеров складывались в один дашборд.
const client = require("prom-client");
const axios = require("axios");
const register = new client.Registry();
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const solveTotal = new client.Counter({
name: "captcha_solve_total",
help: "Total CAPTCHA solve attempts",
labelNames: ["type", "status"],
registers: [register],
});
const solveLatency = new client.Histogram({
name: "captcha_solve_latency_seconds",
help: "CAPTCHA solve latency",
labelNames: ["type"],
buckets: [5, 10, 15, 20, 30, 45, 60, 90, 120],
registers: [register],
});
async function solveWithMetrics(sitekey, pageurl, type = "recaptcha_v2") {
const start = Date.now();
const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: { key: API_KEY, method: "userrecaptcha", googlekey: sitekey, pageurl, json: 1 },
});
if (submit.data.status !== 1) {
solveTotal.inc({ type, status: "submit_error" });
return { error: submit.data.request };
}
const captchaId = submit.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const poll = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (poll.data.status === 1) {
const elapsed = (Date.now() - start) / 1000;
solveTotal.inc({ type, status: "solved" });
solveLatency.observe({ type }, elapsed);
return { solution: poll.data.request };
}
if (poll.data.request !== "CAPCHA_NOT_READY") {
solveTotal.inc({ type, status: "error" });
return { error: poll.data.request };
}
}
solveTotal.inc({ type, status: "timeout" });
return { error: "TIMEOUT" };
}
// Expose metrics endpoint
const express = require("express");
const app = express();
app.get("/metrics", async (req, res) => {
res.set("Content-Type", register.contentType);
res.end(await register.metrics());
});
app.listen(9090);
Здесь метрики отдаются через HTTP-эндпоинт /metrics на порту 9090, то есть Prometheus забирает их сам. Push Gateway нужен только там, где процесс живёт меньше интервала опроса.
Как выбрать базу временных рядов
| Особенность | Prometheus | InfluxDB | TimescaleDB |
|---|---|---|---|
| Подходит для | Оперативный мониторинг | Метрики высокой кардинальности, IoT | Аналитика на SQL |
| Язык запросов | PromQL | Flux | SQL |
| Хранение | По конфигурации | По политике хранения | Средствами PostgreSQL |
| Интеграция с Grafana | Нативная | Нативная | Нативная |
| Порог входа | Низкий | Средний | Низкий, если вы знаете SQL |
| Self-hosted | Да | Да | Да, расширение PostgreSQL |
Практическое правило: Prometheus, если он уже развёрнут; InfluxDB, если нужен изолированный сервис метрик; TimescaleDB, если аналитику по решениям будут строить люди, которые пишут SQL, а не PromQL.
Локальный сценарий: распределённые воркеры и разная сетевая задержка
Типичная конфигурация для команд, читающих по-русски: часть воркеров работает в европейском регионе облака, часть — на хостинге в Казахстане или Центральной Азии, а очередь общая. Без разбивки по географии график задержки покажет лишь смазанное среднее, и рост P95 на одной площадке утонет в общей картине.
Решение — добавить один низкокардинальный тег, например region, и сравнивать перцентили по площадкам. Время приёма-передачи (RTT) до API стоит вести отдельным рядом: тогда видно, растёт ли задержка из-за сети или из-за очереди. На нестабильных каналах именно это различие подсказывает, что чинить — тайм-ауты и повторные попытки на своей стороне.
Если рядом с метриками вы пишете логи запросов, ограничьтесь техническими полями и собирайте только те данные, которые вы вправе обрабатывать: в РФ это 152-ФЗ «О персональных данных», в трансграничных проектах — требования уровня GDPR. Сами метрики решения таких данных содержать не должны.
Пороги алертов и типичные неисправности
Скользящие средние работают лучше мгновенных значений: одиночный сбой не должен будить дежурного. Разумные стартовые пороги — падение часовой доли успешных решений ниже 90 %, рост P95 задержки выше 45 с, а также обнуление глубины очереди при ненулевом входящем потоке.
| Проблема | Причина | Что делать |
|---|---|---|
| Разрывы на графиках | Push Gateway не получает данные | Проверьте сеть между воркером и шлюзом и срок жизни процесса |
| Перцентили выглядят неправдоподобно | Границы бакетов не соответствуют нагрузке | Используйте бакеты [5, 10, 15, 20, 30, 45, 60, 90, 120] для решения CAPTCHA |
| Стоимость на графике не сходится с фактом | Не учтена тарификация по потокам | Считайте цену решения как стоимость плана, делённую на фактическое число решений за месяц |
| Слишком высокая кардинальность | Много значений у меток | Оставьте только type, status, error_code; не тегируйте по URL |
| Тайм-ауты растут только по одному типу | Этот тип задачи требует больше времени | Разделите SLO по типам: reCAPTCHA v2, Cloudflare Turnstile и GeeTest v3 ведут себя по-разному |
Разбивка по типам особенно полезна, если вы решаете несколько семейств задач: reCAPTCHA v2, Cloudflare Turnstile и GeeTest v3 дают разные распределения времени решения, и общий график усредняет их до бесполезности.
Часто задаваемые вопросы
Сколько хранить метрики решения CAPTCHA?
Посекундное разрешение — около 7 дней, часовые агрегаты — 90 дней, суточные сводки — бессрочно. Такой каскад даёт и разбор свежего инцидента, и годовой тренд без роста стоимости хранения.
Чем метрики отличаются от логов решений?
Метрики — это числовые ряды с низкой кардинальностью, по ним строят тренды и алерты. Логи хранят отдельные задачи с их ID и кодами ошибок и нужны для разбора конкретного случая. Держите оба контура, но не тегируйте метрики идентификаторами задач.
Что писать в ряд стоимости при тарификации по потокам?
Фиксируйте расчётную цену решения: месячную стоимость плана делите на фактическое число решений. Ряд покажет, как удельная цена падает с ростом загрузки — и в какой момент увеличение числа потоков окупается.
Нужно ли отдельно отслеживать баланс аккаунта?
Да, отдельным Gauge с редким опросом — раз в несколько минут достаточно. Это единственная метрика, которая ломает конвейер целиком, а не постепенно, поэтому алерт на неё ставят жёсткий, без сглаживания.
Какие метрики вести по бета-типам CAPTCHA?
CaptchaFox (beta), Friendly Captcha (beta) и Lemin (beta) имеет смысл вести отдельными рядами и не смешивать с общими SLO: по бета-типам нет опубликованных измеренных показателей, поэтому опирайтесь на собственные наблюдения за период.