サーバーエージェント
Ruby & Rails
手順2のない唯一の導入です。railtie が起動時にミドルウェアを追加し、sql.active_record を購読し、コントローラのアクションを包みます — 書くべき initializer はありません。
- パッケージ
sixty/ RubyGems- 動作環境
- Ruby 2.7 以降。Rails 6.1+ なら自分で入ります。それ以外は Rack だけで十分です。
- ソース
- sixty-sh/sixty-ruby
導入方法
Rails installs itself — the railtie adds the middleware, subscribes to sql.active_record and wraps controllers on boot. Postgres, MySQL and MongoDB, with query plans on Postgres.
導入手順は、あなた向けのチェックリストではなく、すでに開いているコーディングエージェント向けのプロンプトとして書かれています。これは意図的です。どのファイルを編集するかではなく、導入が終わった時点で何が成り立っていなければならないかを述べています。コードをどこに置くかはフレームワーク次第で、置き場所を間違えると静かに失敗するからです。エージェントはリポジトリを読んでそれを判断できますが、ドキュメントの段落にはできません。
同じ文面は、 install_sixty が MCP サーバー 経由で返すものであり、コレクタが次の場所で配信しているものでもあります: /v1/setup?kind=ruby. 実体は1つだけです。
Ruby & Rails の導入手順(全文)
Install the sixty agent in this Ruby application so its controllers, service
objects and database queries report to sixty.
1. Add the gem to the main group of the Gemfile — not to :development and not
to :test. It runs in production; that is the entire point. It has no
dependencies of its own, so nothing else in the lockfile moves.
gem 'sixty'
Then bundle install.
2. Rails needs no further wiring. The railtie adds the middleware, subscribes
to sql.active_record and wraps controller actions on boot. Do NOT write an
initializer that calls Sixty.init — in a Rails app that is a second boot
path for the same thing.
If this is Sinatra, Hanami, a bare Rack app or a non-web process, then and
only then do it by hand: require 'sixty' and call Sixty.init at startup,
and add "use Sixty::Instrument::Rack" to config.ru.
3. Measure the layer between the controller and the database. This is the step
the install exists for: without it the feed has routes and SQL and nothing
in between, and an N+1 can be seen but not attributed to the object that
causes it. In a Rails app that object is a query object, a service object,
a serializer or a job — rarely the controller.
class OrdersQuery
include Sixty::Instrumented
end
Every public instance method of that class becomes an operation, with its
queries and rows attributed to it. Private methods and plain accessors are
skipped. Add it to the few classes that do real work per request — not to
models, and not to anything called in a tight loop.
For a class you do not own: Sixty.instrument(Stripe::Charge, :create)
For a block: Sixty.trace('nightly-reconciliation') { ... }
4. Set these environment variables wherever the application is deployed:
SIXTY_API_KEY = a secret key starting sixty_sk_ — ask me for it. Do not
invent one, and do not commit it.
SIXTY_SERVICE = my-app
SIXTY_ENDPOINT = https://ingest.sixty.sh
The release identifier is picked up automatically on Vercel, Render,
Railway, Fly, Heroku and GitHub Actions. If this deploys some other way,
set SIXTY_RELEASE to the commit SHA — without one, every measurement lands
in a single nameless bucket and no comparison can ever be made.
Constraints — correctness requirements, not style preferences:
- Do NOT change any application behaviour. This is instrumentation only: no
refactors, no reordering of business logic, no "while I was in here" fixes.
- Do NOT include Sixty::Instrumented in ActiveRecord models or in anything
called per row. A span costs a few microseconds, which is nothing next to a
request and everything next to a loop over a result set.
- Do NOT add any analytics, user id, session id, or cookie to what is
reported. The agent is deliberately anonymous and must stay that way.
- Leave the database configuration alone. pg, mysql2, trilogy and mongo are
all instrumented already; if this application reaches its database some
other way, tell me rather than wiring something up.
If this runs under Puma with workers, or under Sidekiq, note how many
processes it runs: each reports independently and the collector merges them,
which is correct, but it is worth knowing when you read the numbers.
When you are done, tell me which files you changed, so I can confirm data is
arriving.秘密鍵が必要です — sixty_sk_ で始まり、サーバー側に留まります。ログイン後、設定ページで発行してください。
何を測るか
| シグナル | 単位 | 意味 |
|---|---|---|
rows | rows per call | this query returns more rows than it used to |
fanout | queries per call | this operation now issues more database calls per invocation — an N+1 |
latency | ms per call | this operation takes longer end to end than it used to |
self_latency | ms per call | the time spent in this function itself got longer — its children did not |
payload | bytes per call | the serialized result of this operation got bigger |
errors | error rate | a larger fraction of calls are throwing |
runaway | calls per minute | this operation is being called far more often than anything triggers it |
repeated_query | times per request | the identical query runs several times within one request |
overfetch | rows per call | far more rows are fetched than the code appears to use |
unbounded | rows per call | this query has no upper bound on what it can return |
recursion | levels deep | this operation calls itself, deeper than it should |
new_error | occurrences | an error that did not occur in the previous release |
missing_tenancy | — | This reads a table of per-person data without saying whose rows it wants. Unless your database is filtering it for you, everyone gets everyone else's. |
collapse | — | This is handing back roughly half the data it used to, or less. If that was not deliberate, something is filtering out rows that somebody expects to see. |
vanished | — | It was being used steadily until this release and has not been used once since. Usually the link, button, or redirect that led here stopped working. |
traffic_drop | — | This is still being used, but a fraction as often, and its share of your traffic fell too — so it is not just a quiet period. |
round_trips | round trips per read | one read now waits on the database many times instead of once |
plan | — | the database chose a different plan for this query |
どこに入り込むか
- Rails — 自動。コントローラが操作になり、controller#action の形で名前が付きます。
- Rack — config.ru で use Sixty::Instrument::Rack。Sinatra、Hanami、素のアプリ向け。
- あなた自身のクラス — include Sixty::Instrumented — 公開インスタンスメソッドがすべて操作になります。private メソッドとアクセサには手を触れません。
- それ以外 — Sixty.instrument(Stripe::Charge, :create)、あるいは Sixty.trace("job") { }。
データベース
- pg — 行数、文の形、そして EXPLAIN によるクエリプラン。
- mysql2 / trilogy — Postgres ではなく MySQL の引用符の規則で読みます — 二重引用符の文字列が、一方ではリテラルで、もう一方では識別子だからです。
- mongo — Mongoid が使うもの。コマンドの形を identity に。
これだけができること
- 書くものがない — Rails には文書化された起動フックと、すでにすべてのクエリを知らせるイベントバスがあるので、この gem には initializer もラッパーも要りません。
- Postgres のクエリプラン — Node のエージェントと同じ EXPLAIN を、文ごとにキャッシュして。
できないこと
- クエリプランは Postgres のみです。MySQL には汎用プランの EXPLAIN がなく、MongoDB には説明すべき文がありません。
- CPU と待ち時間は分けません。
- ワーカー付きの Puma や Sidekiq の下では、各プロセスが個別に報告し、コレクタがまとめます。
設定
どのエージェントも同じ4つの変数を読みます。そして SIXTY_* が答えるところでは DRIFT_* も引き続き答えます — 製品名は変わりましたが、その名前は他人のデプロイから引き上げてよい類のものではありません。
SIXTY_API_KEY | これがないとエージェントは何もせず、そのことを伝えます。推測もせず、未知のエンドポイントに再試行もせず、例外も投げません。 |
|---|---|
SIXTY_SERVICE | このサービスを何と呼ぶか。読み取れる場合はプロジェクト名が既定になります。 |
SIXTY_RELEASE | いちばん重要なもの。Vercel、Render、Railway、Fly、Heroku、GitHub Actions では自動で拾われます。それ以外ではコミットの SHA を設定してください。これがないとすべての計測が名前のない1つのバケツに入り、比較は永遠にできません。 |
SIXTY_ENDPOINT | どこへ報告するか。既定は http://localhost:4319で、ノートPCの上では正しく、そのアプリが他人に配信された瞬間に間違いになります。 |
残り — 送信間隔、サンプリング率、何を計測するか — はパッケージ自身の README にあります。エージェントが変わっても正しいままでいられる場所だからです。