0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CloudWatch Logs の料金を激減させる OSS「S4 Logs」をリリースしました (v1.1.1)

0
Last updated at Posted at 2026-06-27

abyo software 合同会社から、CloudWatch Logs の請求を 1〜2 桁圧縮することを目的とした OSS S4 Logs をリリースしました。 Apache-2.0 で公開しています。

  • リポジトリ: https://github.com/abyo-software/s4-logs
  • 最新リリース: v1.1.1 (2026-06-27)、 直前の v1.1.0 (2026-06-15) と合わせて紹介します
  • 言語: Rust (静的リンク musl バイナリ配布)
  • 兄弟プロダクト: S4 (S3 の料金を圧縮するほう)

なんの課題を解くのか

CloudWatch Logs の請求書を眺めると、ほとんどの場合「取り込み (ingest) $0.50/GB」が支配的です。 保管 ($0.03/GB·月、しかも gzip-6 圧縮後のバイト数に対して) はその一部にすぎません。 そして取り込まれたログのほとんどは「書いて、二度と読まれない」。 高い ingest 単価を払って読みもしないデータを CloudWatch に山積みしている、というのが典型的な状況です。

S4 Logs はこれを 2 つの独立したモードで攻めます。

Mode A — Drain: 既存ログを S3 に退避する

FilterLogEvents で既存ロググループからログを引き出し、 標準 RFC 8878 の zstd フレームとして S3 に書き出します。 1 時間 UTC アライン窓を単位とし、各窓ごとにマニフェストを残します。 同じウィンドウを再実行してもマニフェスト済みなのでスキップされる (冪等)。

# ドライランで「いくら浮くか」だけ見る
s4logs plan --all

# 実際にアーカイブ
s4logs drain --log-group /aws/lambda/payments \
  --bucket my-archive-bucket --prefix s4logs

# Glacier Instant Retrieval に直送 ($0.004/GB·月、ミリ秒応答)
s4logs drain --log-group /aws/lambda/payments \
  --bucket my-archive-bucket --prefix s4logs --storage-class glacier-ir

# CloudWatch 側のリテンションを縮める (全窓アーカイブ済みのときだけ通る fail-closed ゲート)
s4logs drain --log-group /aws/lambda/payments \
  --bucket my-archive-bucket --retention-days 7 --apply-retention

Mode A だけでは「すでに払った ingest は戻ってこない」 ─── 削減できるのは保管料金です。 ただし S3 Standard で約 50–70%、Glacier IR で約 90% カット、 さらにリテンションを縮めれば CW 側の保管料金そのものが消えます。

Mode B — Bypass: ingest を回避する

ここが本命です。 CloudWatch Logs の AWS JSON 1.1 API サブセット (PutLogEvents, CreateLogGroup, CreateLogStream, DescribeLogGroups, DescribeLogStreams) を喋るゲートウェイを立て、 Fluent Bit / CloudWatch Agent / SDK の エンドポイントを差し替えるだけで取り込みを横取りします。 アプリ側のコード変更は不要。

# routing.toml ─── first-match wins
default_action = "s3"            # s3 | cloudwatch | both | drop

[[rule]]
log_group = "/aws/lambda/payments-*"
action    = "cloudwatch"          # アラート系だけは CW にも流す

Fluent Bit はこれだけ:

[OUTPUT]
    Name              cloudwatch_logs
    Match             *
    region            us-east-1
    log_group_name    /app/api
    log_stream_prefix node-
    auto_create_group On
    endpoint          http://s4logs-gateway.internal:8080

--wal-dir を渡せば ack 前に fsync されるので、 クラッシュしても at-least-once で復元されます。

ざっくりいくら浮くのか

AWS 公式の list price (2026-06、us-east-1) で 1 TiB/月のログを想定:

CloudWatch そのまま Mode A (S3 Standard) Mode A (Glacier IR) Mode B
ingest $512 $512 (既払、回収不能) $512 (同) $0
storage ~$7.7/月 (gzip-6 換算) ~$3.8/月 ~$0.66/月 同左

Mode B にすれば 1 TiB あたり毎月 $512 の取り込み料金がまるごと消えます。 Mode A 単独だと「保管の何割か」のオーダーで、ドル単位の話。 つまり「請求書を本気で減らしたいなら Mode B、削れるだけ削りたいなら両方」です。

ロックインしません ─── データは普通の zstd

S4 Logs に「依存」しないことを v1.0 で契約として固定しました (DESIGN.md §14)。

  • 書き出すのは標準 RFC 8878 の zstd フレーム。 独自コンテナは使っていません
  • 中身は 1 行 1 イベントの JSONL: {"timestamp":…,"stream":"…","message":"…","ingestion_time":…,"event_id":"…"}
  • サイドカー (.s4index/.s4lts) は range-read 索引なので、無視しても本体は読める
  • S4 Logs が消えても aws s3 cp … - | zstd -dc で読める

Athena もそのまま使えます (docs/athena.md 同梱)。

実 AWS での検証

LocalStack だけで終わらせると怒られる気がしたので、本物の us-east-1 で計測しました (2026-06-10 / 06-12)。

  • Mode A: 5.00 GiB / 33,163,647 イベントを seed → drain 94.6 分、 ThrottlingException 0、 アーカイブ 1.6 GiB zstd (圧縮率 6.2×)、 Athena count(*)33,163,613 件一致 (誤差 0.0001% は CloudWatch 側の seeder 残差で別件)
  • Mode B: ゲートウェイ越しの PutLogEvents が実 S3 に正しいレイアウト (dt=…) で着地、 ルーティング (s3 / cloudwatch / both) が分離していること、 restore --to-log-group が 14 日制約付きで動くこと、 SIGTERM でバッファをドレインすることを確認
  • 2 時間ソーク: 715,817 req / 7,158,170 events ack / 0 失敗、 RSS 増分 2.3 MiB (リーク無し)

実験総コスト ~$2.60。 結果は README に表で出しています。

入れる

Linux x86_64 / aarch64 musl 静的バイナリ:

curl -fsSL https://raw.githubusercontent.com/abyo-software/s4-logs/main/scripts/install.sh | sh

Docker でも動きます (docker build -t s4logs . で ~176 MB のランタイムイメージ)。

v1.1.0 + v1.1.1 で変わったところ

v1.1.0 (2026-06-15): 共有レイヤ抽出 + Gateway TLS

S4 Logs を作っている過程で「S3 オブジェクトストア層」「観測性 (/health//ready//metrics) 層」「TLS 層」「EMF パーサ」は S4 ファミリの次の製品 (S4 Metrics) でもまるごと使えると判明したので、 v1.1.0 では 4 つの crate に切り出しました。

  • s4-objstore ─── ペイロード非依存の S3 ObjectStore (CRC32C-on-PUT、 range GET、 paginated list)
  • s4-observability ─── Prometheus + readiness probe + graceful shutdown
  • s4-tls ─── rustls による TLS 終端 (PEM と ACME 両対応)
  • s4-emf ─── CloudWatch Embedded Metric Format のパーサ/シリアライザ

加えて Gateway に TLS オプション (PEM パス) が入りました:

s4logs serve --tls-cert /etc/s4logs/cert.pem --tls-key /etc/s4logs/key.pem ...

v1.1.1 (2026-06-27): ACME (Let's Encrypt) CLI + EMF DoS 防御

v1.1.0 で s4-tls の中には ACME (Let's Encrypt) 経路を実装していたものの、 CLI フラグまでは公開できていませんでした。 v1.1.1 で公開します:

s4logs serve --listen 0.0.0.0:443 \
  --bucket my-archive-bucket --prefix s4logs --account 123456789012 \
  --acme-domain logs.example.com \
  --acme-contact ops@example.com \
  --acme-cache-dir /var/lib/s4logs/acme

TLS-ALPN-01 チャレンジを使うので、 同一ポート (通常 :443) で証明書取得と通常トラフィックを共有します。 --acme-staging で Let's Encrypt staging 環境に切り替え可能 (rate limit 緩和されているので構成確認に便利)。 --tls-cert/--tls-key (PEM 静的) との同時指定は usage error で reject。

もう 1 つの追加が s4-emf::EmfDocument::samples_bounded():

let doc = s4_emf::parse(json_bytes)?;
let samples = doc.samples_bounded(max_samples, max_values)
    .map_err(|e| /* 拒否 */)?;

EMF ドキュメントは flattening 段で爆発します。 長さ 1 万の values 配列を 1 万通りの dimension set に展開すれば、 百 KB 台の入力が約 800 MB (= 約 763 MiB) のアロケーションになります (1 万 × 1 万 × f64 8 byte = 8 × 10^8 byte)。 samples_bounded()materialize する前にサイズ計算して、 cap を超える時点で EmfError::ExpansionTooLarge を返します。 untrusted な EMF を食わせる経路 (バイパス Gateway や S4 Metrics) で使ってください。 正常な document に対しては samples() と完全に同じ Vec<EmfSample> を返します。

on-disk フォーマットは 1.x で凍結したので、 v1.0 で書いたデータは v1.1.x で全部読めます

v1.1.0 + v1.1.1 の設計判断 (なぜ 4 crate なのか、 なぜ ACME を 1.1.1 に分けたか、 expansion DoS をどう pre-materialization で弾くか) は、 Zenn の方に書いた深掘り記事に分けてあります

ライセンスと連絡先

商用版 (LRU リテンション、 マルチアカウントなど) も別途用意していますが、 まずは OSS 版で十分なケースが大半です ─── 月 $500 未満なら OSS で打ち止めにしてしまって構いません。 README に「いつ商用版を考えるべきか」の break-even ラインも書いてあります。

質問・要望は GitHub issue に投げてください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?