Если пайплайн решения CAPTCHA деградирует ночью, а вы узнаёте об этом только от клиентов утром — логов уже недостаточно, нужен APM. New Relic превращает каждый вызов in.php/res.php в трейс с задержкой: у команд на нестабильных региональных сетях poll_count растёт раньше, чем падает success_rate. Ниже — инструментация на Python и Node.js, NRQL-запросы и пороги алертов.
Три сигнала, которые обычно показываются на дашборде раньше, чем клиенты напишут в поддержку:
- Растёт
poll_count, аsuccess_rateещё в норме. Первый спокойный признак — тормозит сеть или solver, а не то, что задачи массово проваливаются. - Растягивается P95 по времени решения. Обычно это предвестник таймаутов ещё до того, как сработает алерт по доле успешных решений.
- Баланс тает без заметного роста трафика. Часто означает не рост нагрузки, а повторные попытки после ошибок — сам по себе повод завести отдельный алерт.
Какие метрики CAPTCHA-пайплайна важны для APM
[Submit Task] → [Wait for Solution] → [Apply Token]
↓ ↓ ↓
Submit latency Poll duration Token usage
API errors Timeout rate Success rate
Python: кастомные события New Relic для решения CAPTCHA
import os
import time
import requests
import newrelic.agent
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
session = requests.Session()
@newrelic.agent.background_task(name="captcha_solve", group="CaptchaAI")
def solve_captcha(sitekey, pageurl, captcha_type="recaptcha_v2"):
"""Solve a CAPTCHA with full New Relic instrumentation."""
# Add custom attributes for filtering
newrelic.agent.add_custom_attributes([
("captcha_type", captcha_type),
("target_url", pageurl),
])
# Submit phase
submit_result = _submit_task(sitekey, pageurl, captcha_type)
if "error" in submit_result:
newrelic.agent.record_custom_event("CaptchaSolveError", {
"error": submit_result["error"],
"phase": "submit",
"captcha_type": captcha_type,
})
return submit_result
# Poll phase
captcha_id = submit_result["captcha_id"]
poll_result = _poll_result(captcha_id, captcha_type)
# Record solve event
event_data = {
"captcha_type": captcha_type,
"captcha_id": captcha_id,
"success": "solution" in poll_result,
}
if "solution" in poll_result:
event_data["solve_time"] = poll_result.get("elapsed", 0)
newrelic.agent.record_custom_event("CaptchaSolveSuccess", event_data)
else:
event_data["error"] = poll_result.get("error", "unknown")
newrelic.agent.record_custom_event("CaptchaSolveError", event_data)
return poll_result
@newrelic.agent.function_trace(name="captcha_submit")
def _submit_task(sitekey, pageurl, captcha_type):
payload = {
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1
}
resp = session.post("https://ocr.captchaai.com/in.php", data=payload)
data = resp.json()
newrelic.agent.add_custom_attributes([
("submit_status", data.get("status")),
])
if data.get("status") != 1:
return {"error": data.get("request")}
return {"captcha_id": data["request"]}
@newrelic.agent.function_trace(name="captcha_poll")
def _poll_result(captcha_id, captcha_type):
start = time.time()
poll_count = 0
for _ in range(60):
time.sleep(5)
poll_count += 1
result = session.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
newrelic.agent.add_custom_attributes([
("poll_count", poll_count),
("solve_time_seconds", round(elapsed, 2)),
])
return {"solution": result["request"], "elapsed": elapsed}
if result.get("request") != "CAPCHA_NOT_READY":
return {"error": result.get("request")}
return {"error": "TIMEOUT"}
def report_balance():
"""Record balance as a custom event."""
resp = session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance", "json": 1
})
data = resp.json()
if data.get("status") == 1:
balance = float(data["request"])
newrelic.agent.record_custom_event("CaptchaBalance", {
"balance": balance,
"low": balance < 10,
})
return balance
return None
Конфигурация агента New Relic
# newrelic.ini
[newrelic]
app_name = CaptchaAI Pipeline
license_key = YOUR_NEW_RELIC_LICENSE_KEY
monitor_mode = true
log_level = info
transaction_tracer.enabled = true
transaction_tracer.transaction_threshold = 5.0
custom_insights_events.enabled = true
custom_insights_events.max_samples_stored = 5000
Чек-лист перед первым деплоем агента
- Сверьте
license_keyвnewrelic.iniс нужным аккаунтом — несовпадающий ключ выглядит так, будто агент работает, а данные при этом летят не туда. - Держите
transaction_tracer.transaction_thresholdна уровне 1–2 секунд на тестовом контуре, иначе часть трейсов решения CAPTCHA просто не попадёт в трассировку. - Заранее прикиньте
custom_insights_events.max_samples_storedпод объём задач в минуту — при пиковой нагрузке лимит выбирается быстрее, чем кажется. - Прогоните
newrelic-admin validate-config newrelic.iniперед первым деплоем: это быстрее, чем через час искать причину пустого дашборда. - Зафиксируйте базовый
success_rateиsolve_timeза первые сутки — без такой базовой линии пороги алертов ниже выбираются на глаз.
JavaScript: трассировка решения CAPTCHA через New Relic
const newrelic = require("newrelic");
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
async function solveCaptchaWithNewRelic(sitekey, pageurl, captchaType = "recaptcha_v2") {
return newrelic.startBackgroundTransaction(
"CaptchaSolve",
"CaptchaAI",
async () => {
const transaction = newrelic.getTransaction();
newrelic.addCustomAttributes({
captchaType,
targetUrl: pageurl,
});
const startTime = Date.now();
try {
// Submit
const submitResp = await axios.post(
"https://ocr.captchaai.com/in.php",
null,
{
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageurl,
json: 1,
},
}
);
if (submitResp.data.status !== 1) {
newrelic.recordCustomEvent("CaptchaSolveError", {
error: submitResp.data.request,
phase: "submit",
captchaType,
});
transaction.end();
return { error: submitResp.data.request };
}
const captchaId = submitResp.data.request;
newrelic.addCustomAttributes({ captchaId });
// Poll
let pollCount = 0;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
pollCount++;
const pollResp = await axios.get(
"https://ocr.captchaai.com/res.php",
{
params: {
key: API_KEY, action: "get", id: captchaId, json: 1,
},
}
);
if (pollResp.data.status === 1) {
const elapsed = (Date.now() - startTime) / 1000;
newrelic.recordCustomEvent("CaptchaSolveSuccess", {
captchaType,
solveTime: elapsed,
pollCount,
});
newrelic.addCustomAttributes({
solveTime: elapsed,
pollCount,
});
transaction.end();
return { solution: pollResp.data.request, elapsed };
}
if (pollResp.data.request !== "CAPCHA_NOT_READY") {
newrelic.recordCustomEvent("CaptchaSolveError", {
error: pollResp.data.request,
phase: "poll",
captchaType,
});
transaction.end();
return { error: pollResp.data.request };
}
}
newrelic.recordCustomEvent("CaptchaSolveError", {
error: "TIMEOUT",
phase: "poll",
captchaType,
pollCount,
});
transaction.end();
return { error: "TIMEOUT" };
} catch (err) {
newrelic.noticeError(err);
transaction.end();
throw err;
}
}
);
}
// Balance monitoring
async function monitorBalance() {
try {
const resp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "getbalance", json: 1 },
});
if (resp.data.status === 1) {
const balance = parseFloat(resp.data.request);
newrelic.recordCustomEvent("CaptchaBalance", { balance });
}
} catch (err) {
newrelic.noticeError(err);
}
}
setInterval(monitorBalance, 60000);
module.exports = { solveCaptchaWithNewRelic };
Мониторинг для команд в СНГ и на нестабильных сетях
Команды, которые гоняют трафик через европейские регионы или разворачивают воркеров в Казахстане и Центральной Азии, чаще видят рост poll_count не из-за solver'а, а из-за самого канала до ocr.captchaai.com. Мобильные и спутниковые каналы добавляют джиттер, который на офисном десктопном интернете просто не заметен.
- Разносите пороги алертов по региону, если разворачиваете несколько воркеров: единый порог для стабильного и нестабильного канала будет либо шуметь, либо молчать не вовремя.
- Логируйте RTT до
ocr.captchaai.comотдельным кастомным атрибутом — это отделяет сетевую деградацию от деградации самого solver'а прямо на дашборде. - Не сохраняйте в атрибутах New Relic больше персональных данных, чем нужно для отладки: для РФ это требование 152-ФЗ «О персональных данных», для международных команд — GDPR.
NRQL-запросы для дашборда
Соберите дашборд New Relic из этих запросов NRQL:
-- Solve success rate (last hour)
SELECT percentage(count(*), WHERE success = true)
FROM CaptchaSolveSuccess, CaptchaSolveError
SINCE 1 hour ago
-- Average solve time by CAPTCHA type
SELECT average(solveTime)
FROM CaptchaSolveSuccess
FACET captchaType
SINCE 1 hour ago TIMESERIES
-- Error breakdown
SELECT count(*)
FROM CaptchaSolveError
FACET error
SINCE 1 hour ago
-- P95 solve latency
SELECT percentile(solveTime, 95)
FROM CaptchaSolveSuccess
SINCE 1 hour ago TIMESERIES
-- Balance over time
SELECT latest(balance)
FROM CaptchaBalance
SINCE 24 hours ago TIMESERIES 5 minutes
-- Tasks per minute
SELECT rate(count(*), 1 minute)
FROM CaptchaSolveSuccess, CaptchaSolveError
SINCE 1 hour ago TIMESERIES
FAQ
Дашборд показывает рост poll_count, а success rate ещё в норме — уже проблема?
Да: решения приходят позже обычного из-за сети, а не потому что задачи проваливаются. Добавьте виджет average(pollCount) рядом с success_rate.
Custom events включены в newrelic.ini, но в NRQL пусто — что проверить сначала?
Не исчерпан ли max_samples_stored, и совпадает ли license_key с нужным аккаунтом.
Агент New Relic добавляет заметную задержку к решению CAPTCHA?
Нет: доли миллисекунды на вызов — на фоне секунд-минут решения это не измеримо.
Мониторинг из региона с нестабильной сетью — те же пороги алертов, что и для дата-центра?
Нет: для мобильных и региональных каналов держите отдельный порог high latency и не смешивайте его с трафиком из дата-центра — иначе алерт либо шумит на команду с нестабильной сетью, либо молчит на реальную деградацию у остальных.
Пороги для алертов
Откалибруйте пороги под конкурентность тарифа: команда на ADVANCE ($90/мес, 50 потоков) увидит совсем другой профиль нагрузки, чем на BASIC ($15/мес, 5 потоков). Ниже — стартовые значения, которые можно ужесточать по мере накопления истории.
| Алерт | Условие NRQL | Порог |
|---|---|---|
| Низкая доля успешных решений | SELECT percentage(count(*), WHERE success = true) |
< 85 % в течение 5 мин. |
| Высокая задержка | SELECT percentile(solveTime, 95) FROM CaptchaSolveSuccess |
> 120 с в течение 10 мин. |
| Низкий баланс | SELECT latest(balance) FROM CaptchaBalance |
< $10 |
| Пик ошибок | SELECT count(*) FROM CaptchaSolveError |
> 50 за 5 минут |
Диагностика типичных проблем
Прежде чем разбираться в самом NRQL, проверьте четыре типичные причины тишины на дашборде — в большинстве случаев дело в конфигурации агента, а не в запросе.
| Проблема | Причина | Решение |
|---|---|---|
| Кастомные события не появляются в NRQL | custom_insights_events.enabled выключен |
Включите параметр в newrelic.ini |
| Трейсы транзакций отсутствуют | Порог трассировки задан слишком высоким | Понизьте transaction_threshold до 1.0 с |
| Атрибуты обрезаются | Значение атрибута слишком длинное | Держите значения атрибутов короче 255 символов |
| Нет данных после деплоя | Неверный license key или агент не стартовал | Проверьте newrelic-admin validate-config newrelic.ini |
Следующие шаги
Дашборд из этого руководства закрывает основной цикл пайплайна: отправка → опрос → применение токена → баланс. Дальше стоит подключить смежные темы, которые чаще всего всплывают в первую неделю эксплуатации: