설정
설정 하나는 플래그 하나이고, 모든 플래그에는 뜻이 같은 환경 변수가 있습니다. 명령줄에 담기에는 구조가 복잡한 몇 가지는 JSON 파일 하나에 들어 있습니다. 이 페이지는 그 전부입니다.
같은 것을 말하는 두 가지 방법
인자 없이 mcpd를 실행하면 그대로 동작합니다. 0.0.0.0:4317에서
듣고, 데이터를 ./mcpd.db에 두고, 비밀 값을 마스킹하고,
모든 트레이스를 영구히 보관합니다. 아래의 모든 항목은 그 결정 가운데
하나를 바꿉니다.
플래그는 언제나 대응하는 환경 변수를 이깁니다. 둘 다 전역이므로 하위 명령 앞에 두든 뒤에 두든 받아들여집니다.
레퍼런스
| 플래그 | 환경 변수 | 기본값 |
|---|---|---|
--listen | MCPD_LISTEN | 0.0.0.0:4317 |
--data | MCPD_DATA | ./mcpd.db |
--project | MCPD_PROJECT | default |
--retention-days | MCPD_RETENTION_DAYS | 설정 안 함: 전부 보관 |
--ingest-key | MCPD_INGEST_KEYS | 없음: 수집이 열려 있음 |
--read-key | MCPD_READ_KEYS | 없음: 읽기가 열려 있음 |
--write-key | MCPD_WRITE_KEYS | 없음: 수집 키를 받아들임 |
--admin-key | MCPD_ADMIN_KEYS | 없음: 읽기 키를 받아들임 |
--execute-key | MCPD_EXECUTE_KEYS | 없음: 쓰기 키를 받아들임 |
--worker-key | MCPD_WORKER_KEYS | 없음: 쓰기 키를 받아들임 |
--runs-data | MCPD_RUNS_DATA | --data 경로의 확장자 앞에 .runs를 붙인 것 |
--trusted-identity-header | MCPD_TRUSTED_IDENTITY_HEADER | 없음: 주체를 기록하지 않음 |
--trusted-proxy-cidr | MCPD_TRUSTED_PROXY_CIDRS | 없음: 어떤 주소도 그 헤더를 설정할 수 없음 |
--sample | MCPD_SAMPLE | 설정 안 함: 모든 트레이스를 보관 |
--quota-per-minute | MCPD_QUOTA_PER_MINUTE | 설정 안 함: 상한 없음 |
--no-redact | MCPD_NO_REDACT | 꺼짐: 마스킹이 켜져 있음 |
--redact-key | MCPD_REDACT_KEY | 내장 키이며, 이 키는 공개되어 있음 |
--redact-key-contains | MCPD_REDACT_KEY_CONTAINS | 없음: 내장 규칙만 적용 |
--redact-max-string | MCPD_REDACT_MAX_STRING | 128 KiB |
--cors-origin | MCPD_CORS_ORIGINS | 없음: 동일 출처만 허용 |
--request-timeout-seconds | MCPD_REQUEST_TIMEOUT_SECONDS | 30 |
--max-body-bytes | MCPD_MAX_BODY_BYTES | 33554432, 즉 32 MiB |
--max-batch-signals | MCPD_MAX_BATCH_SIGNALS | 10000 |
--log-format | MCPD_LOG_FORMAT | text |
--clickhouse-url | MCPD_CLICKHOUSE_URL | 꺼짐: 내장 파일을 사용 |
--clickhouse-database | MCPD_CLICKHOUSE_DATABASE | mcpd |
--clickhouse-user | MCPD_CLICKHOUSE_USER | 없음 |
--clickhouse-password | MCPD_CLICKHOUSE_PASSWORD | 없음 |
--postgres-url | MCPD_POSTGRES_URL | 꺼짐: 내장 파일을 사용 |
--config-file | MCPD_CONFIG_FILE | 없음 |
--forward-endpoint | MCPD_FORWARD_ENDPOINT | 꺼짐: 아무것도 전달하지 않음 |
--forward-header | MCPD_FORWARD_HEADERS | 없음 |
| 없음 | MCPD_LOG | mcpd=info,mcpd_server=info,tower_http=warn |
--listen
바인드할 주소입니다. 포트 하나가 OTLP 엔드포인트, 네이티브 수집 엔드포인트, 쿼리 API, 프로브, 메트릭, 인터페이스를 모두 서비스하므로 mcpd가 가지는 주소는 이것 하나뿐입니다.
루프백 주소에 바인드하는 것이 조용한 선택입니다. mcpd는 바인드한 주소가
루프백이 아닐 때만 수집이 열려 있다고 경고합니다. 키를 설정하지 않은 채
0.0.0.0에 바인드하면 시작할 때 두 번 경고합니다. 한 번은
수집에 대해, 한 번은 읽기에 대해서입니다.
--data
SQLite 파일의 경로입니다. 상위 디렉터리가 없으면 만들어집니다. 아무것도
남기지 않는 스토어가 필요하면 :memory:를 넘기십시오. 데모나
테스트에는 맞고, 내일 다시 들여다볼 것에는 맞지 않습니다.
--project
보내는 쪽이 프로젝트를 지목하지 않았을 때 시그널이 들어가는 프로젝트입니다.
프로젝트는 테넌시 경계입니다. 모든 쿼리는 프로젝트 하나로 한정됩니다. 보존
처리는 이 프로젝트와 projects 블록에 있는 모든 프로젝트를
대상으로 돕니다. 두 곳 어디에도 없는 프로젝트는 절대 정리되지 않으므로,
보내는 쪽이 프로젝트 이름을 직접 정한다면 이 점이 중요합니다.
--retention-days
이 일수보다 오래된 시그널을 삭제합니다. 설정하지 않으면 전부 보관합니다. 노트북에서는 그것이 올바른 기본값이고, 그 밖의 곳에서는 의식해서 정할 만한 결정입니다.
기간은 1일부터 36500일까지이며, 프로젝트의 retention_days도 같습니다. 0은 보존 기간 없음으로
읽히지 않고 시작할 때 거부됩니다. 0은 기준 시각을 지금으로 만들고 첫 삭제 작업이 시작할 때 돌기 때문에, 그 프로젝트의 시그널을
모두 지우게 됩니다. 모두 보관하려면 이 설정을 빼십시오.
이 처리는 쿼리 시점이 아니라 한 시간 주기 타이머로 돌기 때문에, 큰 삭제가 사용자 요청 안에서 벌어지는 일은 없습니다. 스팬, 트레이스, 로그, 메트릭은 mcpd가 받은 시각을 기준으로 잘리고, 점수, 피드백, 관점 노트는 각자의 타임스탬프를 기준으로 잘립니다. 행을 지운다고 파일이 작아지지는 않습니다. 운영하기를 보십시오.
--ingest-key, --read-key, --write-key, --admin-key, --execute-key와 --worker-key
스코프가 여섯 개인 이유는 각각이 실제로 다른 위험을 지기 때문입니다. 모든
엔드포인트는 필요한 스코프를 spec/mcpd.spec.json에 선언하고,
그 핸들러에도 같은 태그가 붙으며, make check가 둘을
비교합니다. 그래서 여기에 적힌 스코프가 실제로 동작 중인 서버가 강제하는
스코프입니다.
| 스코프 | 설정하는 플래그 | 무엇이 열리는가 |
|---|---|---|
ingest | --ingest-key | 수집 엔드포인트 네 개. 아무것도 읽지 않습니다. |
read | --read-key | 쿼리 API. 트레이스, 스팬, 로그, 메트릭, 점수, 분석입니다. |
write | --write-key | 점수, 피드백, 데이터셋, 실험, 확인 처리, 그리고 실행을 데이터셋으로 담는 일. |
admin | --admin-key | 트레이스 삭제, 감사 로그 읽기, 그리고 아래 두 스코프가 여는 모든 것. |
execute | --execute-key | 실행의 시작, 취소, 시그널 보내기, 그리고 실행에 이벤트 게시. |
worker | --worker-key | 정의 등록, 단계 확보, 결과 보고. 트레이스는 읽지 않습니다. |
가장 중요한 구분은 ingest와 나머지 전부 사이의 구분입니다.
수집 키는 스팬을 내보내는 모든 프로세스에 들어가며, 여기에는 직접 쓰지
않은 코드를 돌리는 프로세스도 들어 있습니다. 그 집단에 저장된 프롬프트를
전부 되읽을 수 있는 자격 증명까지 쥐여 주는 것은 mcpd가 쉽게 저지르도록
두지 않는 실수입니다. 수집 키를 쿼리 API에 제시하면 거부됩니다.
write가 ingest와 나뉜 것도 규모만 작을 뿐 같은
이유입니다. 실행을 데이터셋으로 담는 일은 그 실행의 입력과 출력을 읽습니다.
쓰기의 옷을 입은 읽기이므로, 에이전트 프로세스들이 들고 다니는 키에 있어서는
안 됩니다.
admin이 read와 나뉜 이유는 실행을 지우는 일이
읽는 일이 아니기 때문입니다. --admin-key를 설정하기 전까지는
읽기 키가 읽기뿐 아니라 삭제도 하며, mcpd는 시작할 때 그 사실을 말합니다.
execute와 worker는 실행 표면의 두 스코프입니다.
실행을 시작하면 누군가의 워커가 일을 하고 돈을 씁니다. 트레이스에 주석을
다는 일은 그렇지 않으므로, 이것은 write가 아닙니다. 워커 키는
단계의 본문을 돌리는 프로세스에 들어가고 그 프로세스는 보통 제공자 자격
증명을 쥐고 있으므로, 워커 키는 리스 프로토콜만 열고 저장된 프롬프트를 읽는
것은 아무것도 열지 않습니다. 두 플래그를 설정하기 전까지는 쓰기 키가 둘 다에
받아들여지고, 관리자 키는 언제나 받아들여집니다.
중단 없이 업그레이드하기
--write-key를 설정하지 않으면 수집 키가 받아들여지고,
--admin-key를 설정하지 않으면 읽기 키가 받아들여집니다. 그래서
이 두 스코프가 생기기 전에 구성한 배포는 가지고 있던 범위를 그대로 유지하며,
업그레이드해도 깨지는 것이 없습니다. 더 좁은 권한을 쓰려면 두 플래그를
설정하십시오. 지금 어느 상태인지는 mcpd config가 알려 줍니다.
스코프가 자기 키를 가지면 key required를 출력하고, 물려받고
있으면 key required (ingest keys) 또는
key required (read keys)를 출력합니다.
키를 여러 개 두려면 여섯 플래그 중 무엇이든 반복해서 쓰십시오. 중단 없이 키를
교체하는 방법이 이것입니다. 환경 변수는 쉼표로 구분한 목록을 받습니다.
호출하는 쪽은 키를 Authorization: Bearer KEY 또는
X-Mcpd-Key: KEY로 제시하며, 비교는 상수 시간에 이루어지므로
길이도 처음 달라지는 위치도 관찰할 수 없습니다.
mcpd가 모르는 키는 401입니다. mcpd가 아는 키를 그 스코프가
다루지 않는 엔드포인트에 쓰면 403입니다. 둘을 갈라 두는 데는
값이 있습니다. 401을 받으면 사실은 아무 문제 없는 키에서 오타를 찾아 헤매게
되기 때문입니다.
키를 하나도 구성하지 않으면 모든 스코프가 열려 있습니다. 이것은 의도한 것입니다. 처음 실행할 때 자격 증명을 요구해서는 안 되기 때문이며, 시작 시 경고가 있는 이유이기도 합니다.
키가 다루지 않는 것도 알아 두십시오. 이 빌드에서 /healthz,
/readyz, /metrics와 인터페이스 자체는 키 없이
서비스됩니다. 그것이 문제가 된다면 직접 인그레스 뒤에 두십시오.
빈 항목은 무엇이든 목록을 읽기 전에 버려집니다. MCPD_EXECUTE_KEYS=나 Kubernetes Secret의 빈 값처럼 설정은 되었지만 키가 하나도 없는 목록은 데몬이 시작하지 못하게 하며, 거부 메시지는 플래그와 변수를 알려 줍니다. 스코프를 설정하지 않은 채로 두려면 변수 자체를 빼십시오. 빈 키 헤더를 보내는 요청은 키를 제시하지 않은 것으로 봅니다. deploy/의 예제 파일에 있던 자리 표시자, 즉 대소문자와 상관없이 REPLACE_WITH나 replace-me로 시작하는 값은 데몬이 시작하지 못하게 합니다. 거부 메시지는 플래그나 프로젝트 목록을 알려 주고 값은 절대 알려 주지 않습니다. --redact-key도 같습니다. 키는 각각 openssl rand -hex 32로 만드십시오. --write-key, --execute-key, --worker-key가 모두 없으면 수집 키로 실행을 시작하고 스텝을 가져갈 수도 있으며, mcpd는 시작할 때와 mcpd doctor에서 이를 경고합니다.
감사 로그
페이로드를 돌려주는 핸들러는 누가 무엇을 읽었는지 기록합니다.
GET /v1/api/traces/{id}, 스팬 검색과 로그 검색, 피드백,
데이터셋 항목, 캡처, 삭제입니다. 각 행에는 시각, 동작한 키의 짧은
다이제스트, 동작, 프로젝트, 리소스, 그리고 인가가 허용했는지 여부가
담깁니다. 키 자체는 절대 기록되지 않습니다.
로그는 GET /v1/api/audit로 읽으며, 이 엔드포인트는 admin
스코프를 요구합니다. 이 로그는 --retention-days로 정리되지
않습니다. 자기가 설명하는 데이터와 같은 일정으로 스스로를 지우는 로그는,
이미 사라진 데이터를 누가 읽었는지 말해 줄 수 없기 때문입니다. 행을 쓰는
일이 그 행이 설명하는 읽기를 실패시키는 일은 없습니다. 쓸 수 없는 행은
로그로 남고 /metrics의
mcpd_audit_write_failures_total로 집계되며, 여기에는 알림을
걸어 둘 만합니다.
--sample
0.0과 1.0 사이의 비율입니다. 범위를 벗어난 값은 조용히 잘리는 대신 시작
오류가 됩니다. --sample must be between 0.0 and 1.0, got
2.
판단은 트레이스 id를 해시해서 내리므로, 로드 밸런서 뒤에 있는 여러 mcpd 인스턴스가 서로 이야기하지 않고도 같은 트레이스를 남기고 같은 트레이스를 버립니다. 판단은 트레이스마다 한 번만 내려지고 그 배치에 있는 해당 트레이스의 모든 스팬이 이를 물려받습니다. 절반만 기록된 트레이스는 관측 중인 시스템의 버그처럼 보이기 때문입니다.
해시는 트레이스 id의 뒤쪽 바이트를 읽습니다. 어떤 생성기는 앞쪽 바이트를 타임스탬프에서 만들어 내므로, 앞쪽을 보는 샘플러는 시각과 상관관계를 갖게 되어 몇 시간치를 통째로 조용히 버리게 됩니다.
정책 타입에는 "오류는 항상 보관"과 "이보다 느리면 항상 보관"도 들어 있지만, 이 둘은 트레이스가 끝난 뒤에 내리는 테일 판단에 적용됩니다. 이 빌드는 헤드에서만 샘플링하며, 테일 샘플링을 켜는 플래그는 없습니다. 샘플링에서 그 절충을 다룹니다.
--no-redact, --redact-key, --redact-key-contains와 --redact-max-string
마스킹은 기본으로 켜져 있고 무엇이든 기록되기 전에 돕니다. 그래서 애초에 저장되면 안 되는 값은 저장되지 않습니다. 나중에 지우는 것은 같은 보장이 아닙니다. 내장 규칙은 세 벌이며, 키 이름을 소문자로 바꾼 부분 문자열로 일치를 봅니다.
| 규칙 | 키에 다음이 들어 있을 때 적용 | 저장되는 값 |
|---|---|---|
| 마스킹 | api_key, apikey, api-key,
secret, password, passwd,
access_token, refresh_token,
id_token, bearer,
authorization, auth_token,
private_key, client_secret,
session_token, credential,
signature, x-api-key | [redacted] |
| 부분 마스킹 | email, phone, ssn,
credit_card, card_number,
iban |
앞의 두 글자와 뒤의 두 글자, 즉 ab…yz. 여섯 글자
이하인 값은 자기 대부분을 흘리는 대신 통째로 마스킹합니다
|
| 해시 | user_id, userid | sha256:과 키를 쓴 다이제스트 12바이트. 사용자를
저장하지 않고도 사용자별로 묶을 수 있습니다
|
일치한 키가 객체나 배열을 담고 있으면 그 아래에 있는 것도 모두
마스킹되므로, {"credentials": {"token": "..."}}
가 중첩된 이름만으로 빠져나가지 못합니다. 128 KiB보다 긴 문자열은 하나하나
잘리며 몇 바이트였는지 적은 표시가 붙습니다. 이는 폭주한 프롬프트가
디스크에 미치는 영향을 묶어 둡니다.
--redact-key는 해시 규칙에 쓰는 키입니다. 같은 입력은 같은 키
아래에서 같은 해시가 되고, 그래서 묶기가 동작합니다. 키를 교체하면 그
연결이 의도적으로 끊어집니다. 해시를 쓰는 배포라면 반드시 설정하십시오.
내장 기본값은 소스 안에 있으므로, 키 없이 만든 다이제스트는 추측할 수
있습니다.
--redact-key-contains는 내장 규칙 세 벌 위에 직접 만든
마스킹 규칙을 더합니다. 내장 규칙은 자격 증명이 보통 어떤 이름으로
저장되는지는 알지만, 쓰고 있는 도구가 고객의 계좌 번호를 어느 필드에 넣는지는
모릅니다. 값은 내장 규칙과 같은 방식으로, 키를 소문자로 바꾼 부분 문자열로
일치를 봅니다. 그래서 --redact-key-contains ticket은
ticket_id와 supportTicket을 똑같이 마스킹하고,
일치한 키 아래에 중첩된 것도 함께 마스킹합니다. 여러 개를 두려면 플래그를
반복하거나 MCPD_REDACT_KEY_CONTAINS에 쉼표로 구분한 목록을
설정하십시오.
--redact-max-string은 내장 기본값인 128 KiB에서 길이 상한을
낮춥니다. 상한보다 긴 문자열은 저장되기 전에 거기서 잘리며, 몇 바이트가
버려졌는지 적은 표시가 붙습니다.
--no-redact는 길이 상한을 포함해 파이프라인 전체를 끕니다.
환경 변수로는 MCPD_NO_REDACT=true입니다. 위쪽 어딘가에서 이미
마스킹을 하고 있을 때만 말이 되며, 위의 두 추가 설정 가운데 하나라도
함께 설정되어 있으면 데몬은 시작을 거부합니다. 적용할 대상이 없기
때문입니다.
--runs-data
실행 스토어가 있는 곳입니다. 정의, 단계 시도의 저널, 모든 기한과 모든
대기가 여기에 있습니다. 시그널 스토어가 무엇이든 이것은 내장 파일이므로,
Postgres나 ClickHouse를 쓰더라도 실행은 여전히 데몬 옆의 파일 하나에 놓이고
실행 표면은 레플리카 하나가 서비스합니다. 기본값은 --data
경로의 확장자 앞에 .runs를 붙인 것으로, 기본 설정에서는
./mcpd.runs.db이고 --data가
:memory:이면 이쪽도 그렇습니다. --runs-data off는
실행을 아예 서비스하지 않습니다. 모든 실행 엔드포인트가 이 플래그를 이름으로
짚으며 503을 돌려주고, 데몬의 나머지는 그대로입니다.
이 스토어의 시간 설정은 설정 파일의 runs 아래에 밀리초 정수로
들어 있습니다. lease_ttl_ms, reclaim_window_ms,
stall_window_ms, idempotency_window_ms,
park_ttl_ms, max_depth입니다. 기본값인 30초 리스와
30초 회수 윈도는 머신 한 대에서 데몬 하나를 돌릴 때 알맞으며, 워커가 등록할
때 되읽는 값이기도 합니다.
--cors-origin
다른 사이트에서 쿼리 API를 호출할 수 있는 브라우저 오리진입니다. 아무것도
설정하지 않으면 교차 출처 요청에 access-control-allow-origin
헤더가 붙지 않고, 브라우저는 응답을 페이지에 넘겨주기를 거부합니다. 이것이
기본값이며, 읽기 API가 열린 채 노트북에서 도는 데몬이 같은 브라우저에 열려
있는 아무 웹 페이지에나 읽히지 않게 막아 주는 것이 바로 이 동작입니다. 함께
들어 있는 인터페이스는 데몬 자신의 오리진에서 서비스되므로 이 플래그가 전혀
필요 없습니다.
인터페이스를 다른 곳에서, CDN이나 사내 포털에서 서비스하는 경우가 이
플래그가 있는 이유입니다. 그 오리진을 스킴과 호스트로 적으십시오. 예를 들어
--cors-origin https://console.example입니다. 여러 개를 두려면
반복해서 쓰거나 MCPD_CORS_ORIGINS에 쉼표로 구분한 목록을
설정하십시오. 오리진이 아닌 값, 즉 console.example이나 끝에
슬래시가 붙은 값은 시작을 거부하면서 문제의 플래그를 짚어 줍니다. 연결하지
못하는 인터페이스만 남기고 이유는 알려 주지 않는 대신입니다.
--cors-origin '*'는 모든 오리진을 허용합니다. 이전 빌드가 묻지도
않고 하던 일이 이것입니다.
/healthz, /readyz, /metrics는 이
플래그의 통제를 받지 않습니다. 저장된 데이터를 담고 있지 않고, 인터페이스가
다른 무엇보다 먼저 /healthz를 확인하기 때문에 어느
오리진에서든 읽을 수 있게 둡니다.
--request-timeout-seconds
데몬이 응답하기까지 요청에 허용되는 최대 시간이며, 본문을 읽는 시간도
들어갑니다. 기한을 넘겨 계속 도는 핸들러에는 timeout과 이
플래그의 이름을 본문에 담은 503이 돌아갑니다. 기본값은 30초로,
큰 배치를 읽어서 저장하기에 충분히 길고, 연결은 받아들이면서 응답하지 않는
스토리지 백엔드를 마냥 기다리는 대신 보고할 만큼 짧습니다. 끌 수는 없습니다.
어떤 작업이 정말로 더 오래 걸린다면 값을 올리십시오.
--max-body-bytes와 --max-batch-signals
하나의 요청에 걸리는 두 가지 한계입니다. --max-body-bytes는
데몬이 읽는 가장 큰 본문으로 기본값은 32 MiB이며, 아직 아무것도 디코딩되지
않은 상태에서 요청 하나가 메모리를 얼마나 쓸 수 있는지를 정합니다. 이보다
큰 본문은 두 수치를 모두 짚으며 413으로 거부되고, 압축된
본문은 푼 뒤의 크기를 같은 수치로 재므로 천 배로 부푸는 본문은 부풀려지는
대신 거부됩니다.
--max-batch-signals는 디코딩을 마친 요청 하나가 실어 나를 수
있는 시그널의 개수이며 기본값은 10,000입니다. 요청 전체에 걸쳐 세므로
배치의 배열이든 NDJSON 스트림이든 배치 하나와 같은 수가 적용됩니다. 이 수를
넘긴 요청은 아무것도 저장되지 않습니다. 거부는 절반쯤 쓰고 난 뒤가 아니라
첫 번째 쓰기보다 먼저 일어납니다.
둘 다 설정 파일 키이기도 하며 이름은 max_body_bytes와
max_batch_signals입니다. 양쪽에 다 있으면 플래그가 이깁니다.
어느 쪽도 끌 수 없고, 0은 "제한 없음"으로 읽히는 대신 시작할 때 거부됩니다.
본문 상한이 0이면 모든 요청이 거부되므로, 너그러워 보이는 해석이 오히려
수집을 멈추는 해석입니다.
--log-format과 MCPD_LOG
터미널에는 text, 로그 파이프라인에는 json입니다.
MCPD_LOG는 tracing 필터 문자열을 받으며 대응하는
플래그가 없습니다. 배포 결정이 아니라 디버깅용 조절 장치이기 때문입니다.
요청 단위의 자세한 내용을 보고 싶으면 mcpd_server=debug로
설정하십시오.
두 요청 한계가 적용되는 곳
본문 상한은 본문을 읽는 모든 라우트에 적용되며 쿼리 API도 여기에 들어갑니다. 시그널 상한은 수집 창구에 적용되고, 창구마다 무엇을 세는지는 다음과 같습니다.
| 창구 | 시그널 상한이 세는 것 |
|---|---|
POST /v1/ingest | 요청 안의 모든 스팬, 로그, 메트릭, 점수, 피드백, 관점 노트를 전부 합한 수. 배치 하나로 왔든, 배치의 배열로 왔든, NDJSON 줄의 스트림으로 왔든 같습니다 |
POST /v1/traces | 보내는 쪽이 보낸 스팬의 수. 변환하기 전에 세므로, 보내는 쪽이 손을 쓸 수 있는 수입니다 |
POST /v1/logs | 익스포트 안의 로그 레코드 |
POST /v1/metrics | 익스포트 안의 모든 메트릭에 걸친 데이터 포인트 |
하위 명령
| 명령 | 하는 일 |
|---|---|
mcpd serve | 데몬을 실행합니다. 하위 명령을 아예 주지 않으면 이것이 실행됩니다 |
mcpd demo | 실제에 가까운 예시 실행을 스토어에 써 넣은 다음 서비스합니다. 인터페이스를 처음 열었을 때 볼 것이 있도록 하기 위함입니다 |
mcpd config | 이 호출이 쓰게 될 설정을 출력하고 종료합니다 |
mcpd mcp | Model Context Protocol을 stdio로 서비스합니다. 에이전트가 도는 데몬에서 자기 트레이스를 읽을 수 있습니다 |
mcpd doctor | 이 배포를 채점하고 무엇이 잘못 구성되었는지 보고합니다. 결함이 있으면 2로 종료하므로 배포를 막는 관문으로 쓸 수 있습니다. 운영하기를 보십시오 |
mcpd config는 곧 컨테이너와 벌이게 될 언쟁을 위해
있습니다. 플래그와 환경 변수와 기본값은 모두 한곳으로 모이고, 이 명령은
서버를 띄우거나 데이터 파일을 건드리지 않고 그 한곳을 출력합니다.
$ MCPD_INGEST_KEYS=k1,k2 mcpd --retention-days 30 --sample 0.1 configlisten 0.0.0.0:4317storage sqlite at ./mcpd.dbconfig file nonelog format textproject defaultprojects noneretention 30 daysingest auth key requiredread auth openwrite auth key required (ingest keys)admin auth openexecute auth key required (ingest keys)worker auth key required (ingest keys)runs sqlite at ./mcpd.runs.dbrun settings lease_ttl_ms 30000, reclaim_window_ms 30000, stall_window_ms 300000, idempotency_window_ms 86400000, park_ttl_ms 86400000, max_depth 8, max_step_repeats 16identity offsampling deterministic 0.1quota unlimitedredaction onredact key built in, which is public; set --redact-keycors same-origin onlytimeout 30s per requestlimits 32 MiB per request, 10000 signals per batchderived duration and token metricsaspects offmonitors noneobjectives nonejudge offforwarding off
무엇이든 배포하기 전에 auth 여섯 줄을 읽으십시오. open은 포트에
닿을 수 있는 누구에게나 열려 있다는 뜻입니다. 괄호 안의 내용은 그 스코프가
물려받고 있는 더 넓은 목록의 이름이며, 예전 릴리스에서 업그레이드한 배포가
처음 놓이는 상태가 이것입니다.
설정을 갖춘 배포
여기에 mcpd만의 것은 없습니다. 변수는 평범한 환경 변수이므로 Compose에서든, Kubernetes 매니페스트에서든, systemd 유닛에서든 똑같이 동작합니다.
compose
services:
mcpd:
image: ghcr.io/mcpd/mcpd
ports: ['4317:4317']
volumes: ['mcpd-data:/data']
environment:
MCPD_LISTEN: 0.0.0.0:4317
MCPD_DATA: /data/mcpd.db
MCPD_PROJECT: checkout
MCPD_RETENTION_DAYS: '30'
MCPD_INGEST_KEYS: write-key-one,write-key-two
MCPD_READ_KEYS: read-key
MCPD_REDACT_KEY: a-long-random-string
MCPD_LOG_FORMAT: json
MCPD_LOG: mcpd=info,mcpd_server=warn
volumes:
mcpd-data: 같은 설정을 플래그로 쓰면 이렇습니다. 셸이나 유닛 파일에 쓰기 좋습니다.
mcpd --listen 0.0.0.0:4317 --data /data/mcpd.db --retention-days 30 --ingest-key $KEY --log-format json SDK가 읽는 것
이 변수들은 데몬이 아니라 에이전트 프로세스가 읽습니다. 두 묶음이 이름을 나눠 쓰고, 에이전트와 mcpd를 함께 돌리는 컨테이너는 하나의 환경으로 구성되기 때문에 여기에 함께 적어 둡니다.
| 환경 변수 | 기본값 |
|---|---|
MCPD_ENDPOINT | http://localhost:4317 |
MCPD_API_KEY | 설정 안 함 |
MCPD_PROJECT | default |
MCPD_ENVIRONMENT | 설정 안 함 |
MCPD_SERVICE_NAME | 설정 안 함 |
MCPD_SERVICE_VERSION | 설정 안 함 |
MCPD_SAMPLE | 1.0 |
MCPD_ENABLED | true |
MCPD_CAPTURE_PAYLOADS | true |
MCPD_LOG_LEVEL | debug |
MCPD_PROJECT와 MCPD_SAMPLE은 양쪽에서 같은
뜻이며, 보통은 그것이 원하는 바입니다. 한 번 설정해 두면 에이전트와 데몬이
같은 프로젝트를 두고 이야기합니다. 생각해 볼 것은 샘플링입니다. SDK는
트레이스를 보내기 전에 버리므로 네트워크를 아낍니다. 데몬은 저장하기 전에
버리므로, 통제하지 못하는 발신자에게 비율을 강제할 수 있는 유일한
지점입니다. 비율은 양쪽이 아니라 한쪽에서만 설정하십시오. 그렇지 않으면 이미
걸러진 스트림을 다시 거르게 됩니다.
MCPD_ENABLED=false는 모든 SDK 호출을 아무 일도 하지 않되 유효한
객체는 돌려주는 호출로 만듭니다. 그래서 코드를 고치지 않고도 테스트에서
mcpd를 끌 수 있습니다. MCPD_CAPTURE_PAYLOADS=false는 프롬프트와
컴플리션을 프로세스 안에 붙들어 둡니다. 스팬은 타이밍, 사용량, 모델, 오류를
그대로 담고 도착하지만 input과 output은 큐에
들어가기 전에 버려지므로 네트워크를 건너가지 않습니다. 데몬 쪽의 마스킹은
같은 선의 반대쪽 끝이며, 바이트를 아예 보내면 안 되는 팀에게는 잘못된
끝입니다. 언어별 자세한 내용은 각
SDK 페이지에 있습니다.
--config-file
명령줄에 담기지 않는 설정을 담은 JSON 파일입니다. 모니터, 서비스 수준 목표와 그것들을 평가하는 주기, 전달, 판정 모델, 실행 스토어의 시간 설정, 두 가지 요청 한계, 그리고 아래의 프로젝트별 블록이 여기에 들어갑니다. YAML이나 TOML이 아니라 JSON인 이유는 mcpd가 이미 다른 모든 것에 JSON을 파싱하기 때문입니다. 의존성도, 새로 익힐 두 번째 문법도 늘지 않습니다. Kubernetes ConfigMap으로 고칠 것 없이 그대로 마운트됩니다.
max_body_bytes와 max_batch_signals는 명령줄뿐
아니라 여기에도 있습니다. 그래서 여러 대를 함께 운영할 때 이 값들을 함께
배포되는 모니터 옆에 둘 수 있습니다. 양쪽에 다 있으면 플래그가 파일을
이기며, 그것이 공유 파일을 모두를 위해 고치지 않고도 한 인스턴스만 다른
한계를 갖게 하는 방법입니다.
알 수 없는 키는 무시되는 대신 시작을 실패시킵니다. 오타 하나 때문에 아무것도 감시하지 않게 되는 것이 나올 수 있는 결과 가운데 가장 나쁘기 때문입니다.
백그라운드 타이머가 다룰 수 없는 값도 마찬가지로 시작을 실패시킵니다. 0 이상 1 미만이 아닌
objective, 1에서 365를 벗어난 목표의 window_days, 86400을 넘는
monitor_interval_seconds, 1에서 31536000을 벗어난
aspects.window_seconds, 604800을 넘는
aspects.interval_seconds, 1에서 300을 벗어난
judge.timeout_seconds가 그렇습니다. 거부할 때마다 키와 범위를 알려 줍니다.
{
"max_body_bytes": 33554432,
"max_batch_signals": 10000,
"monitor_interval_seconds": 60,
"monitors": [
{
"id": "error-rate",
"name": "Error rate above 5%",
"project": "checkout",
"watch": "error_rate",
"compare": "above",
"threshold": 0.05,
"window_seconds": 300,
"min_samples": 20,
"webhook": "https://hooks.example.com/mcpd"
}
],
"forward": {
"endpoint": "https://otlp.example.com",
"headers": [["Authorization", "Bearer sk-..."]],
"batch_size": 256,
"enabled": true
}
} 프로젝트, 그리고 프로젝트에 닿는 키
프로젝트는 테넌시의 키입니다. 그 아래에 키를 나열하는 일이 이 말을 장식이 아니라 사실로 만듭니다. 어떤 프로젝트의 블록에 있는 키는 그 프로젝트에만 닿고 다른 프로젝트에는 닿지 않으므로, 데몬 하나가 여러 팀을 서비스하면서도 어느 팀도 다른 팀의 프롬프트를 읽을 수 없습니다.
{
"projects": {
"checkout": {
"ingest_keys": ["ck-write-..."],
"read_keys": ["ck-read-..."],
"admin_keys": ["ck-admin-..."],
"retention_days": 30,
"sample": 0.25
},
"support": {
"ingest_keys": ["sp-write-..."],
"read_keys": ["sp-read-..."],
"retention_days": 90
}
}
}
여섯 가지 목록은 모두 프로젝트마다 있으며 이름은 ingest_keys,
read_keys, write_keys, admin_keys,
execute_keys, worker_keys입니다. 물려받는 방식도 같습니다.
write_keys가 없는 프로젝트는 자기 수집 키를 받아들이고,
admin_keys가 없는 프로젝트는 자기 읽기 키를 받아들입니다. 위의
support 블록은 둘 다 설정하지 않았으므로, 두 스코프가 생기기
전과 똑같이 동작합니다.
최상위 플래그 여섯 개 가운데 무엇으로든 설정한 키는 모든 프로젝트에 닿습니다. 그것이 단일 테넌트 경로이며, 계속 단순한 쪽으로 남습니다. 이 블록을 한 번도 열지 않는다면 여기 있는 것을 만날 일도 없습니다.
retention_days와 sample은 프로젝트가 값을 정하지
않으면 전역 플래그로 되돌아갑니다. 둘이 프로젝트별인 이유는 키가
프로젝트별인 이유와 같습니다. 한 팀의 90일 요구가 나머지 모두의 데이터까지
붙들어 두게 해서는 안 되고, 한 팀의 트래픽이 다른 팀의 샘플링 비율을
정해서도 안 됩니다.
하나의 배치가 여러 프로젝트를 지목할 수 있습니다. 범위가 정해진 수집 키는 닿는 시그널만 쓰고 나머지는 거부하며, 조용히 버리는 대신 응답에 그 거부를 알립니다. 그래서 잘못된 프로젝트를 향하고 있는 발신자 무리는 이미 읽고 있는 응답만으로 드러납니다.
키가 닿지 않는 프로젝트에 대한 읽기 요청은 그 프로젝트가 있는지 없는지 말하지 않은 채 거부됩니다. 공유 데몬에서 프로젝트 이름은 팀이나 고객의 이름인 경우가 많습니다.
모니터와 서비스 수준 목표는
모니터와 알림에서 다룹니다.
목표는 monitors 옆의 slos 배열에 들어가고 같은
타이머로 평가됩니다. judge 블록은 모델이 채점하는 스코어링을
켜며 회귀 테스트에서
다룹니다. 설정하기 전까지는 꺼져 있고, 요청이 이름으로 지목하지 않는 한 판정
스코어러는 절대 돌지 않습니다. 채점은 읽는 실행마다 돈을 쓰기 때문입니다.
{
"judge": {
"endpoint": "http://localhost:11434/v1",
"model": "qwen2.5:14b",
"api_key": "",
"criteria": {
"answer_relevance": "Does the answer address the question that was asked?"
}
}
} monitor_interval_seconds는 모니터와 목표를 평가하는 간격입니다. 기본값은 60이고, 5보다 작은 값은 5로 올라갑니다.
derived_metrics
데몬이 저장한 span에서 끌어내는 메트릭입니다. 블록이 없으면 두 가지 모두 켜져 있습니다. durations는 끝난 에이전트, 도구, 워크플로, 모델 생성, 프로세서 span마다 소요 시간 점을 하나 씁니다. tokens는 사용량을 보고한 모델 생성 span마다 토큰 카운터를 최대 열세 개 씁니다. 어느 쪽이든 false로 두면 그 쓰기를 멈추고, 빠진 필드는 켜진 채로 남습니다. 이것을 위한 플래그는 없습니다. 끌어낸 점은 프로젝트별 상한 다음에 더해지며, 수집 응답이 알려 주는 accepted 수에 들어가지 않습니다.
{
"derived_metrics": { "durations": true, "tokens": false }
} aspects
데몬이 관점 노트를 테마로 묶는지와 그 범위입니다. 블록이 없으면 꺼져 있습니다. enabled는 패스를 켜며 모델도 자격 증명도 필요 없습니다. outcome 관점은 데몬이 직접 쓰기 때문입니다. interval_seconds는 패스가 도는 간격으로 기본값은 300이고, 30보다 작은 값은 30으로 올라갑니다. window_seconds는 한 번의 패스가 노트를 거슬러 읽는 범위로 기본값은 86400입니다. disabled에는 어떤 패스도 묶지 않는 관점을 이름으로 적습니다. 이미 기록된 노트는 계속 읽을 수 있습니다.
{
"aspects": {
"enabled": true,
"interval_seconds": 300,
"window_seconds": 86400,
"disabled": ["sentiment"]
}
} --forward-endpoint
저장된 모든 스팬을 다른 OTLP/HTTP 엔드포인트로 이어서 보냅니다. 베이스 URL을
주면 /v1/traces가 뒤에 붙습니다. 로컬 쓰기가 언제나 먼저
일어나므로, 목적지가 죽어 있어도 잃는 것이 없습니다.
이 플래그는 설정 파일의 forward 블록을 이깁니다. 그래서 공유
ConfigMap을 고치지 않고도 한 인스턴스만 다른 곳을 향하게 할 수 있습니다.
전달을 보십시오.
--forward-header
전달되는 모든 배치와 함께 보낼 헤더이며 Name: value 형태로
씁니다. 두 개 이상이면 플래그를 반복하십시오. 환경 변수는 쉼표로 구분한
목록을 받습니다.
콜론이 없는 값은 버려지지 않고, 올바른 형식을 보여 주며 시작할 때 거부됩니다. OpenTelemetry 방식의
key=value 헤더는 여기서 key: value로 씁니다.
--clickhouse-url
ClickHouse 서버의 HTTP 엔드포인트입니다. 예를 들어
http://clickhouse:8123입니다. 이 값을 설정하면 스토어가 내장
파일에서 ClickHouse로 바뀌고 --data는 더 이상 읽히지 않습니다.
스키마는 시작할 때 만들어집니다.
내장 스토어의 단일 기록자 제약과 단일 레플리카 제약을 없애는 설정이 이것입니다. 보존 기간을 실서비스 트래픽 기준으로 몇 달 단위로 재게 되었을 때 손을 뻗으십시오. 그 전에는 아닙니다. 운영하기에서 그 절충을 다룹니다.
--clickhouse-database, --clickhouse-user, --clickhouse-password
테이블을 만들 데이터베이스, 그리고 거기에 닿을 자격 증명입니다. 비밀번호가 없는 사용자는 비밀번호 없이 보내집니다. 사설 네트워크의 컨테이너에서는 맞고, 그 밖의 어디에서도 맞지 않습니다.
--postgres-url
Postgres 연결 문자열입니다. 예를 들어
postgres://mcpd:secret@db:5432/mcpd입니다. 이 값을 설정하면
스토어가 내장 파일에서 Postgres로 바뀌고 --data는 더 이상
읽히지 않습니다. 스키마는 시작할 때 만들어집니다.
이것이 중간 선택지입니다. ClickHouse와 똑같이 단일 기록자 제약과 단일
레플리카 제약을 없애면서, 이미 쓰고 있지도 않은 데이터베이스를 운영하라고
요구하지 않습니다. 이 플래그와 --clickhouse-url을 함께 지목하면
둘 중 하나를 고르는 대신 시작이 실패합니다.
마이그레이션은 어드바이저리 락을 잡으므로 여러 레플리카가 함께 시작해도 안전하고, 적용할 것이 없는 시작은 스키마를 전혀 건드리지 않습니다.
TLS를 쓰려면 연결 문자열에 sslmode=require를 넣으십시오. 그러면 mcpd는 연결을 암호화하고 서버 인증서를 항상 검증합니다.
공개 인증 기관과 대조하거나, sslrootcert=/path/to/ca.pem을 주면 그 파일의 인증 기관과 대조합니다. 자체 인증 기관을 쓰는 관리형 서비스에는 이것이 필요합니다.
verify-ca와 verify-full은 require로 읽습니다. 기본값인 prefer나 disable에서는 연결이 암호화되지 않습니다.
어느 키인지만이 아니라 누구인지 기록하기
감사 로그는 동작한 키의 다이제스트를 기록합니다. 그것은 "관리자 키가 이 실행을 지웠다"에는 답하지만 "어느 사람이 했는가"에는 답하지 않습니다. mcpd에는 신원 제공자가 없으므로, 신원은 이미 앞에 서 있는 인그레스에서 와야 합니다.
프록시가 설정하는 헤더와, 그 헤더를 설정해도 되는 주소를 알려 주십시오.
신뢰하는 인그레스에서 신원 받기
mcpd serve \
--trusted-identity-header x-forwarded-user \
--trusted-proxy-cidr 10.42.0.0/16 두 쪽 모두 필요하며, 이것이 안전한 이유가 바로 주소 목록입니다. 헤더는 포트로 소켓을 열 수 있는 사람이라면 누구나 보낼 수 있는 문자열이므로, mcpd는 지목한 주소에서 온 것만 읽습니다. 그 밖의 곳에서 온 요청은 평소대로 처리되고 그 감사 행에는 주체가 담기지 않습니다. 추측하는 대신 정직하게 답하는 쪽입니다. 값은 길이도 제한되고 제어 문자도 제거됩니다. 프록시가 그렇지 않더라도 헤더는 공격자의 영향을 받는 값이기 때문입니다.
두 플래그를 모두 설정하지 않으면 아무것도 달라지지 않고 주체는 절대 기록되지 않습니다. 그것이 기본값입니다.
한 프로젝트가 데몬 전체를 써 버릴 수는 없습니다
mcpd에 시그널을 쏟아붓는 프로젝트 하나는 그 데몬 위의 다른 모든 프로젝트에
비용을 지웁니다. 디스크가 차고, 보존 처리가 따라가지 못하며, 잘못한 것이
없는 팀이 실행을 잃습니다. --quota-per-minute는 그것을 막는
상한입니다.
모든 프로젝트에 걸리는 상한, 그리고 그중 하나에만 주는 더 큰 상한
mcpd serve --quota-per-minute 10000
설정하지 않으면 상한이 없으므로, 한 번도 설정하지 않은 배포는 쿼터가 생기기
전과 똑같이 동작합니다. 설정하면 세 가지를 덮습니다. projects
블록에 있으면서 자기 quota_per_minute를 정하지 않은 모든
프로젝트, --project가 지목한 프로젝트, 그리고
설정에 아예 나열되지 않은 모든 프로젝트입니다.
그 마지막 하나가 이것을 권고가 아니라 상한으로 만드는 부분입니다. 프로젝트 이름은 요청 본문 안에 실려 오기 때문에, 어떤 설정 파일에도 없는 이름을 고른 발신자는 예전에는 아무 데도 청구되지 않았고 제한 없이 저장할 수 있었습니다. 발신자가 문자열 하나를 지어내어 빠져나갈 수 있는 제한은 제한이 아닙니다.
나열되지 않은 프로젝트는 각자 하나씩이 아니라 카운터 하나를 함께 씁니다.
이름마다 카운터를 두면 그것은 어떤 발신자든 이름을 지어내어 늘릴 수 있는
할당이 되고, 모양만 다른 같은 구멍이 됩니다. 함께 쓴다는 것은 지어낸 이름 천
개에 흩뿌린 홍수도 합계로는 이 숫자에 묶인다는 뜻이고, 자기 몫을 가질 만한
프로젝트라면 projects에 이름을 올려야 몫을 받는다는 뜻입니다.
이 상한이 실제로 묶는 것
카운터는 두 개의 윈도에 가중치를 주는 방식입니다. 이번 분의 개수에, 지금 끝나는 윈도가 아직 덮고 있는 만큼으로 가중한 직전 분의 개수를 더합니다. 이렇게 하면 지속 속도는 상한에 묶이면서도 그 위에 상한 하나만큼의 버스트가 허용됩니다. 큐를 가득 채웠다가 비우는 SDK는 하나의 버스트로 도착하는데, 그것을 거부하는 카운터는 평범한 트래픽도 거부하게 되기 때문입니다.
이 둘을 합치면 숨기기보다 밝혀 두는 편이 나은 산술적 결과가 하나 나옵니다. 한 프로젝트가 60초 안에 상한의 두 배에 조금 못 미치는 양을 저장할 수 있습니다. 한 윈도의 맨 끝에 상한만큼, 그리고 다음 윈도의 맨 끝에 다시 상한만큼입니다. 이것은 근사 때문에 생기는 부산물이 아닙니다. 프로젝트가 상한을 한 번에 쓰게 하고 그 뒤로 분마다 다시 상한을 쓰게 하는 리미터라면 어느 것이든 경계를 사이에 두고 이것을 허용하며, 없앨 방법은 버스트를 거부하는 것뿐입니다.
윈도는 1분이고 설정할 수 없습니다. 플래그 이름에 단위가 들어 있으므로, "분당"의 뜻을 바꾸는 두 번째 설정이 생기면 첫 번째 숫자를 읽을 수 없게 됩니다.
넘친 것은 어떻게 되는가
상한을 넘긴 시그널은 거부되고 보고되며, 조용히 버려지는 일은 없습니다. 배치 전체가 거부되지도 않습니다. mcpd는 들어가는 만큼 저장하고 무엇이 못 들어갔는지 알려 줍니다.
- 스팬은 트레이스 단위로 처리됩니다. 실행의 절반만 있으면 부모를 아무도 저장하지 않은 스팬이 생기고, 이는 빠진 실행이 아니라 망가진 실행처럼 읽힙니다. 남은 자리에 비해 너무 큰 트레이스는 건너뛰고 다음 트레이스를 시도하므로, 큰 실행 하나가 남은 몫을 아무 성과 없이 써 버리지 않습니다.
- 로그, 메트릭, 점수, 피드백은 각자 홀로 서므로 도착한 순서대로 남은 자리를 채웁니다.
- 청구되는 것은 저장된 것뿐입니다. 정리 과정은 트레이스를 통째로 남기므로 허용된 것보다 적게 저장할 수 있고, 그 차이는 되돌려 줍니다. 그렇게 하지 않으면 남은 몫보다 실행이 큰 프로젝트는 아무것도 저장하지 못한 채 몫을 0까지 태우게 됩니다.
- 같은 요청 안의 다른 프로젝트는 영향을 받지 않습니다. 상한을 넘긴 프로젝트 하나 때문에 함께 보내진 다른 프로젝트의 시그널이 사라지지는 않습니다.
응답에는 그 개수가 rejected에 담기고, errors에는
프로젝트와 그 상한을 짚는 줄이 담깁니다. 설정에 한 번도 나열된 적 없는
프로젝트에는 다른 문장이 나갑니다. analytics-v2 프로젝트가
상한을 넘겼다는 말만 들으면, 답이 프로젝트를 추가하는 것인데도 존재하지 않는
설정을 찾아 헤매게 되기 때문입니다.
$ curl -sX POST "$MCPD/v1/ingest" -H content-type:application/json -d @batch.json | jq{ "accepted": 5, "rejected": 3, "duplicate": 0, "errors": [ { "signal": "span", "index": 0, "message": "3 signal(s) named project analytics-v2, which no configuration lists. Every unlisted project shares one ceiling of 5 per minute and it was full, so they were not stored. Add analytics-v2 to the `projects` block to give it an allowance of its own, or raise --quota-per-minute." } ]}
거부는 mcpd_signals_rejected_total뿐 아니라
mcpd_signals_over_quota_total로도 집계되므로, 제한에 걸린
발신자 무리와 잘못된 형식의 데이터를 보내는 발신자 무리를 갈라 볼 수
있습니다. 운영하기를
보십시오.
설정할 때 확인할 두 가지
상한은 시그널을 저장하는 모든 창구를 덮습니다. 배치 창구인
/v1/ingest, /v1/traces, /v1/logs,
/v1/metrics와 단건 창구인 POST /v1/api/scores,
POST /v1/api/feedback입니다. 이 둘은 상한을 넘긴 프로젝트에
서로 다르게 답하며, 그것은 의도한 것입니다. 배치 창구는 들어가는 만큼
저장하고 나머지를 이유와 함께 rejected로 보고합니다. 하나의
배치가 여러 프로젝트를 지목할 수 있으므로, 요청 전체를 거부하면 상한 안에
있는 프로젝트까지 벌하게 되기 때문입니다. 단건 창구는 절반만 답할 방법이
없으므로 429와 함께, 그 프로젝트의 카운터가 다음 시그널을
받아들이기까지 남은 초를 담은 Retry-After 헤더로 거부합니다. 이
숫자는 카운터 자체에서 나오므로, 헤더를 지키는 클라이언트는 정해진 지연이
끝났을 때가 아니라 자리가 생겼을 때 돌아옵니다.
쓰기 창구 하나는 여기에 청구되지 않습니다. 저장된 실행에 스코어러를 돌리는
POST /v1/api/score입니다. 이 창구는 만들어 낸 점수를 곧바로
스토리지에 넘기므로, 상한을 이미 다 쓴 프로젝트에도 한 번의 채점 결과가
디스크에 남고, 그 행들은 mcpd_signals_over_quota_total로도
mcpd_signals_unbounded_total로도 세지 않습니다. 발신자 무리가
겨누는 창구가 아니라 직접 여는 창구이므로, 이 상한은 발신자가 밀어
넣을 수 있는 양의 한계로 보고 디스크에 닿을 수 있는 모든 것의 한계로는 보지
마십시오.
--max-batch-signals의 기본값인 10,000보다 낮은 상한을 두면,
합법적인 배치 하나가 1분치 몫 전체를 넘길 수 있고 발신자가 아무리 얌전해도
잘려 나갑니다. mcpd doctor는 이것을 limits 항목으로
채점하고 해당되는 프로젝트를 이름으로 짚습니다. 또한 프로젝트별로 상한을
두면서 최상위 상한을 함께 두지 않은 경우에도 경고합니다. 그 구성은 나열되지
않은 모든 프로젝트를 무제한으로 남겨 두기 때문입니다.
mcpd config는 제한이 걸린 프로젝트를 이름으로 출력하므로, 이
설정이 무엇을 덮는지 짐작하는 대신 되읽을 수 있습니다. 이름이 있는 상한만
출력하므로, 공유 상한은 플래그 자체에서 확인하십시오.