Rails in 1.44MB Challenge #kaigionrails_fd0 に参加した

osyoyu さんが企画したイベント ですね。

Rails in 1.44MB Challenge とは

Rails一式をフロッピーディスク(1.44MB)に収めてください。会場でフロッピーディスクに書き込んで動かしてプレゼントします!

Railsは結構でっかいフレームワークです。依存しているGemを全部足しあげると50MBぐらいになります。機能削減・圧縮・ズル、あらゆるテクを駆使して1/30のサイズにしてください!

ということで、50MB を何とかして 1.44MB に削るコンテストです。Kaigi on Rails 2025 のブース企画の中?いやほとんど個人企画だな?で開催されていました。

達成できたのか

$ du -sh .
1.3M    .
$ ./bin/rails s
=> Booting WEBrick
=> Rails 8.0.3 application starting in development http://localhost:3000
=> Run `bin/rails server --help` for more startup options
[2025-09-29 14:04:17] INFO  WEBrick 1.9.1
[2025-09-29 14:04:17] INFO  ruby 3.4.5 (2025-07-16) [arm64-darwin24]
[2025-09-29 14:04:17] INFO  WEBrick::HTTPServer#start: pid=27522 port=3000
Started GET "/?name=onk" for 127.0.0.1 at 2025-09-29 14:04:25 +0900
Processing by HomeController#index as HTML
  Parameters: {"name" => "onk"}
Completed 200 OK in 0ms (Views: 0.0ms | GC: 0.0ms)


127.0.0.1 - - [29/Sep/2025:14:04:24 JST] "GET /?name=onk HTTP/1.1" 304 0
- -> /?name=onk

で、達成としてもヨサソウ。

改めてレギュ読んでたら「Ubuntu で動く」とあって、やっべー忘れてた、となっているのが今です。試しにビルドしたらダメっぽかったので重い C 拡張 gem を殺さなきゃ……。

Rails が動く」の私の定義

普通の controller が動く、をゴールにしたい。

# config/routes.rb
Rails.application.routes.draw do
  root "home#index"
end
# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    render plain: "Hello, #{params[:name] || "World"}!"
  end
end
$ rails s

で、http://localhost:3000/ にアクセスして render されること。

Started GET "/" for 127.0.0.1 at 2025-09-29 13:47:39 +0900
Processing by HomeController#index as HTML
Completed 200 OK in 0ms (Views: 0.1ms | GC: 0.0ms)

理想的には scaffold の generator が動いて、その機能を全部網羅する、がやりたいけど、いつも通りの場所にアプリケーションコードを置けて、routing と render :plain があれば許せる。

rails s で起動させることを考えると railties も要るし、app/* を読み込むことを考えると zeitwerk も要るだろう。

状況確認

rails gem は依存多すぎるので入れない、actionpackrailties は必要として、これだけに削って、vendor/bundle を tgz で固めると達成できるのか。

source "https://rubygems.org"
gem "actionpack"
gem "railties"
$ bundle config set path "vendor/bundle" && bundle install
$ du -sh vendor/bundle/ruby/3.4.0/*
 40K    vendor/bundle/ruby/3.4.0/bin
  0B    vendor/bundle/ruby/3.4.0/build_info
7.4M    vendor/bundle/ruby/3.4.0/cache
  0B    vendor/bundle/ruby/3.4.0/doc
3.7M    vendor/bundle/ruby/3.4.0/extensions
 24M    vendor/bundle/ruby/3.4.0/gems
4.0K    vendor/bundle/ruby/3.4.0/plugins
184K    vendor/bundle/ruby/3.4.0/specifications

vendor/bundle 以下には cache/ に gem ファイルがそのまま置いてあったりするので固めるのが面倒だな。とりあえず extensions が 3.7M、gems が 24M あることが分かる。

$ rm -rf vendor/bundle/ruby/3.4.0/cache
$ tar czf vendor_bundle.tgz vendor/bundle
$ ls -hl vendor_bundle.tgz
-rw-r--r--  1 onaka  staff   6.1M  9 29 13:28 vendor_bundle.tgz

と最小の Gemfile で入ったものを tgz で固めても 6.1MB あるので、依存をもりもり削らないと達成できないことが分かる。なお試しに zstd や brotli にしても大差なかった。

この時点で C 拡張の重いヤツ:

$ du -sh vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/*
172K    vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/bigdecimal-3.2.3
292K    vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/date-3.4.1
 60K    vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/erb-5.0.2
 88K    vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/io-console-0.8.1
2.9M    vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/nokogiri-1.18.10
 80K    vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/psych-5.2.6
 60K    vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/racc-1.8.1
 88K    vendor/bundle/ruby/3.4.0/extensions/arm64-darwin-24/3.4.0/stringio-3.1.7

bigdecimal, date が重いのは、いやー外して動くのか?

ruby コードとして重い gem トップ 10 はこう。

$ du -s vendor/bundle/ruby/3.4.0/gems/* | sort -rn | head
14296   vendor/bundle/ruby/3.4.0/gems/nokogiri-1.18.10
5168    vendor/bundle/ruby/3.4.0/gems/rdoc-6.14.2
3440    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3
3368    vendor/bundle/ruby/3.4.0/gems/activesupport-8.0.3
2840    vendor/bundle/ruby/3.4.0/gems/concurrent-ruby-1.3.5
2792    vendor/bundle/ruby/3.4.0/gems/actionpack-8.0.3
2272    vendor/bundle/ruby/3.4.0/gems/actionview-8.0.3
1392    vendor/bundle/ruby/3.4.0/gems/date-3.4.1
1040    vendor/bundle/ruby/3.4.0/gems/irb-1.15.2
1024    vendor/bundle/ruby/3.4.0/gems/rack-3.2.1

nokogiri や rdoc はまぁ削れるだろう (勘) けど、railtiesactivesupport も削る必要がありそうな予感がありますね。

依存 gem を削減したい

Bundler には以下のように fork を指定する機能がある。

# GitHub 上の fork を指定したいとき
gem "foo", github: "user/name", ref: "commit"
# ローカルの任意の path を指定したいとき
gem "bar", path: "path/to/gem"

ので、各 gem の gemspec を真面目に書き換えながらやるかなーと考えたけど、publish されている gem ってテストコード削っていたり、割と丁寧目に gem 自体が軽くなるような処理がされているンですよね。

# actionpack.gemspec
Gem::Specification.new do |s|
  ...
  s.files        = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"]
  ...
end

lib だけにするとかを再現するのが面倒そうなので、

  • bundle install したあとのコードを直接編集していく
  • bundle を介さずに gem を読み込む

という作戦にしました。

bundle を介さずに gem を読み込む

$LOAD_PATH を弄って require するのは知っていたので、claude code にに丸投げした。(Kaigi on Rails の発表も聞きたいからね……)

コードとしては Bundler 以前の世界というか、普段 rubygems 作るときも exe とか spec_helper に同じコードは書くよねーって感じですね。

# config/boot.rb
gem_path = File.expand_path("../vendor/bundle/ruby/3.4.0/gems", __dir__)
Dir.glob("#{gem_path}/*").sort.each do |gem_dir|
  lib_path = "#{gem_dir}/lib"
  $LOAD_PATH.unshift(lib_path) if File.directory?(lib_path)
end

実際の gem 削減

vendor/bundle/ruby/3.4.0/gems/foo-x.y.z/ を消して動けばヨシ、動かなかったら require を見つけては殺す、みたいな仕事です。これも claude code にどんどん投げてる。

例えば actionview を外したかったら、https://github.com/rails/rails/blob/v8.0.3/actionpack/lib/abstract_controller/rendering.rb#L6-L7 とか https://github.com/rails/rails/blob/v8.0.3/actionpack/lib/abstract_controller/rendering.rb#L20 とかを消していく感じです。

diff --git a/vendor/bundle/ruby/3.4.0/gems/actionpack-8.0.3/lib/abstract_controller/rendering.rb b/vendor/bundle/ruby/3.4.0/gems/actionpack-8.0.3/lib/abstract_controller/rendering.rb
index 346bb4e..e9ebcc7 100644
--- a/vendor/bundle/ruby/3.4.0/gems/actionpack-8.0.3/lib/abstract_controller/rendering.rb
+++ b/vendor/bundle/ruby/3.4.0/gems/actionpack-8.0.3/lib/abstract_controller/rendering.rb
@@ -3,8 +3,8 @@
 # :markup: markdown
 
 require "abstract_controller/error"
-require "action_view"
-require "action_view/view_paths"
+# require "action_view"
+# require "action_view/view_paths"
 
 module AbstractController
   class DoubleRenderError < Error
@@ -17,7 +17,7 @@ module AbstractController
 
   module Rendering
     extend ActiveSupport::Concern
-    include ActionView::ViewPaths
+    # include ActionView::ViewPaths
 
     # Normalizes arguments and options, and then delegates to render_to_body and
     # sticks the result in `self.response_body`.

railties がデフォルトで入れてくる Rack middleware が邪魔だなーって消したのもある。

https://github.com/rails/rails/blob/v8.0.3/railties/lib/rails/application/default_middleware_stack.rb#L14-L110

あと、このコード見たら分かるけど、結構 if config.respond_to?(:active_record) 等がある。下手に config.acitve_record = ActiveSupport::OrderedOptions.new 等で起動時のエラーを回避しようとすると面倒なことになるので気をつけて。なので config/environments/*.rb の各行が何をやっているのかは知っておいた方が便利と思う。

concurrent-ruby 外し

nokogiri や rdoc、actionview 辺りはどこで使っているのか明確だし外せる自信があったのでサクッと作業したけど、concurrent-ruby はちょっと悩んだ。300KB 以上あるので外すのがいいに決まっているが、外せるのか。rails 全体の中で 34 件しか require されてないならまぁ行けるのかなーと思って取りかかった。

$ rg 'require "concurrent/' -l | wc -l
      34

だいたい

-Concurrent::Map.new
+Hash.new

の書き換えでいけるやろと思って作業させました。概ね行けたけど、僕まだ Concurrent::Map の実装読んだこと無いンだよな。そもそも git clone すらしてなかったと思って今回は反省した。

minifyrb

koic/minifyrb の存在を知っていたので、コレで適当にガーッと。

$ fd --type f "\.rb$" | xargs -I{} sh -c "minifyrb {} -o {}" 

koic.hatenablog.com

gzip する前にコメント行を削除するのが効くといいなと思ってやりました。コメントを削るだけなら他にやりようはあったと思うけど、目の前に落ちてたのでお手軽だった。

とりあえず ActiveSupportActionPack 全部に掛けたけど、何ファイルかは壊れたので戻してます。(フィードバックチャンス)

railties から generator 削除

railties でっけーなー、減らせるかな? と思って眺めたら、generators ディレクトリが 1MB って出たので。

$ du -hd 3 vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/
8.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/minitest
 32K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/tasks
4.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/plugin
4.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/rackup
4.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/testing
 28K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/test_unit
4.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/autoloaders
8.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/rack
 12K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/api
 44K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/templates
164K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/commands
 24K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/command
1.0M    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/generators
 56K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/application
8.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/railtie
 24K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/engine
4.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails/console
1.6M    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib/rails
1.6M    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/lib
4.0K    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/exe
1.7M    vendor/bundle/ruby/3.4.0/gems/railties-8.0.3/

もう 60% ぐらい generator じゃん。いやー、generator が rails の肝なんだなーって実感しますね。

tgz から起動

ソラで書ける自信無かったのでまるっと claude code に任せた。

tar czf vendor_bundle.tgz vendor/bundle した vendor_bundle.tgz があります。起動時に伸張するように config/boot.rb を書き換えてください

一発目は system("tar zxf ...") するコードが出てきたので、「pure ruby でできる?」って聞いたら zlib は予想通りとして、Gem::Package::TarReader が出てきた。これは初知りライブラリでした。

github.com

感想

数時間で遊べる、ちょうどいい難易度のクイズだった。osyoyu さんありがとうございます。

Gemfile.lock は普段から眺めているのでまぁまぁ何に依存しているか知っている、という前提を置けるのが良かったね。レギュB の Ruby 自体を最小化する方は普段から (ruby-build に任せずに) ビルドしまくってないと勘所が掴めなそう。

Bundler 使わずに gem 読むの 10 年ぶりに手書きしてるなーとか、actionview とこう密結合してるのかーとか、concurrent-ruby こんなでっかいの!とか、イマドキの gem は test 系の不要ファイルを本当に含んでないなとか、date や bigdecimal が gemify される前ならこんな苦労はとか、トークを聞きながら AI Agent に任せる粒度としてちょうどいいとか、面白かった点がいくつもあった。

皆さんもやってみてください :)

しかし、継続的に頭を使う必要があるとトークを聞けなくなるので、裏企画はあんまり増えないで欲しい!(>_<) これは途中で「やばっ、話している内容が頭に残ってねぇ」と気づいて焦った顔で発言しています。 アルコールを多少控えてホテルに帰ってから手を動かせば良いのはそうなんだが……。

京都で私が好きな場所

こんにちは。関西Ruby会議08オーガナイザー、Kyoto.rb 所属の id:onk です。 健康診断の再検査会場で待ち時間にヒマなのでスマホから書いています。みんなも予選突破したら検査行こうね。私は何年か放置していました。

日本一雅な松屋

このツイートで有名になったヤツ。

ただの松屋ですが、ホテルの1階なので庭が良い。ついったー映え写真にどうぞ。

会場からは徒歩15分ぐらい。烏丸線に乗りたいときとかに移動中に撮るといいんじゃないかな。

tabelog.com

ふる里

普通の居酒屋だけど、生搾りサワーがすごいで有名な店。アルコールの入ったスムージーです。

デイリーポータルZにも取り上げられていたね。僕もこれで知って行きました。

dailyportalz.jp

当日は大宮まで移動することまず無いだろう (会場からは徒歩30-40分) から、前後の日 (今日とか) にどうぞ。

琴ケ瀬茶屋

嵐山の穴場的な飲み屋。川のほとりで酒が飲めます。めっちゃいいので適当に画像検索してみて。

渡月橋を渡ったら人の居ない方に数分歩いて行くとあります。 橋を渡らずにボートで行くのも観光っぽくていいんじゃないかなと思うけど僕はボート未経験。

営業日分かってなくて、Day2 に営業してるかは自信ない。たぶんやってるはず……。

tabelog.com

キッチンゴン

知る人ぞ知る京都名物ピネライス。 チャーハンにトンカツを乗せてカレーをかけてある、小学生の考えた最強料理みたいなヤツです。 量は普通なのでビビんなくて大丈夫。

六角店は行きやすいと思う。徒歩7分ぐらい?

tabelog.com

天下一品 総本店

僕もまだ行ったこと無いので興味ある人いたら一緒に行きましょう

tabelog.com

限定の牛すじラーメンがオススメトノコト

www.tenkaippin.co.jp

餃子の王将 1号店

ただの王将ですが発祥の地の石碑はあります。 大宮駅前。

tabelog.com

オシャレ王将

王将で思い出した。日本に4軒しかないオシャレ王将が京都にあります。

www.ohsho.co.jp

ワイングラスが逆さまになって天井からぶら下がってるレベルのオシャレ度。 湯葉天津飯とか、せいろ蒸しとか、ごま団子とかがオシャレメニューかなぁ。

会場からは 20 分ぐらい。これも烏丸御池駅に行きたいときにどうぞ。

tabelog.com

池田屋 はなの舞

新撰組のあの池田屋騒動跡地が、はなの舞 (大衆居酒屋ですね) になってます。 どの辺が池田屋かと言うと、大階段があって、店員さんがダンダラ羽織着てるぐらい。

会場からは徒歩 3 分ぐらい?

池田屋騒動之址って石碑もある。

tabelog.com

すがり

一番京都感というか一見さんお断り感(?)を感じられるラーメン屋だと思う。

まず店の外観に看板やら暖簾やらのお店要素が無い。知ってなきゃドア開けられない系。 中は京町家の雰囲気を存分に楽しめると思う。 一方通行の構造で、他の人とすれ違わないで出られるようになっているのも「京都」っぽさのポイント高い。(入口と出口が別。)

tabelog.com

すがり近所に2店あって、どっちも京都感あると思うけど、会場に近い六角の方のすがりが僕はオススメです。キャッシュレスオンリーなのに注意。

貴船神社Wi-Fi

貴船神社のフリーWi-Fiが繋ぎたくなるいい名前なので見て行ってくれ。もし行くならぜひ小ネタにどうぞ。

貴船口との間にプログラマーが好きそうな名前のカフェもあります

tabelog.com

NINTENDO KYOTO

ニンテンドーショップです。近いし京都っぽい (任天堂さん、京都の会社ですね) し、いいんじゃないかな。

限定品もあるよ

www.nintendo.com

本能寺跡

街中に石碑があるぐらい。「ああそれも京都だっけ」って思い出して貰おうと思って書きました。

本能寺は本能寺の変のあとに移転しているので、現本能寺と本能寺跡は場所が違うことに注意。二条城との位置関係が全然違う。

羅生門

これもガッカリポイントとして有名。

にしんそば

京都以外で見ないし、京都名物だろう。魚がまるっとそばの上にのってるビジュアル見たことない人はぜひ。

純喫茶

本当にいっぱいあるので好きなところにどうぞ

鴨川

等間隔のカップル見たり、トンビにサンドイッチかっ攫われたりしましょう

割と普段から普通に鴨川ビールしてます。「川」Origin だし、当日夜もやるんじゃないかな。

(余談)デルタの勢力図はとても好き。

今日めっちゃ雨だったので、流れてきたオオサンショウウオ見られるかもしれないですね。

クラフトビール

エントリあるので見てください!

note.com

補足もどうぞ。(長文なので開いて見てね)

いくつかは前夜祭に持っていきます。

ランチ情報

エントリあるので見てください!

note.com

それでは楽しい関西Ruby会議08を~

macOS Sequoia (15.4 以降) で cal や date を打つと出力がおかしい

2025-07-30 追記
macOS 15.6 で ja_JP が直っているのを確認しました。助かる!!!
(ko_KR も同じ原因で同じ現象なんだけど、そっちはそのままなんだ……。別途報告しよう……)

$ cal
      3月 2025
日 月 火 水 木 金 土
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

5 月なのに 3 月と言われる。

$ date
#午後

date#午後 という文字列が返ってくる。

状況は cal -y するとある程度理解できて、

$ cal -y | grep "[0-9]月"
         PM            %Y年 %B%e日 %A %X %Z            1月
         2月                    3月                    4月
         5月                    6月                    7月
         8月                    9月                   10月

と、本来「1月」「2月」となるべきところに「PM」「%Y年 %B%e日 %A %X %Z」が入り込んでいる。どこかで何らかのズレが生じているのだろう。

どうやら日本語だけの問題っぽく、LANG を変えると正常に出力される。

$ LANG=C cal
      May 2025
Su Mo Tu We Th Fr Sa
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
$ LANG=C date
Thu May 22 02:01:25 JST 2025

これはもう LOCALE の問題であろう。と /usr/share/locale/ 以下を眺めると、ja_JP.UTF-8 を含むいくつかの言語ファイルで行数が違うのが見える。

$ wc -l /usr/share/locale/*.UTF-8/LC_TIME
...
      58 /usr/share/locale/is_IS.UTF-8/LC_TIME
      58 /usr/share/locale/it_CH.UTF-8/LC_TIME
      58 /usr/share/locale/it_IT.UTF-8/LC_TIME
      60 /usr/share/locale/ja_JP.UTF-8/LC_TIME
      58 /usr/share/locale/kk_KZ.UTF-8/LC_TIME
      60 /usr/share/locale/ko_KR.UTF-8/LC_TIME
      58 /usr/share/locale/lt_LT.UTF-8/LC_TIME
      58 /usr/share/locale/lv_LV.UTF-8/LC_TIME
      58 /usr/share/locale/mn_MN.UTF-8/LC_TIME
...

en_US.UTF-8ja_JP.UTF-8 を見比べると、確かにおかしい。

$ cat -n /usr/share/locale/en_US.UTF-8/LC_TIME
...
    38  Saturday
    39  %H:%M:%S
    40  %m/%d/%Y
    41  %a %b %e %X %Y
    42  AM
    43  PM
    44  %a %b %e %X %Z %Y
    45  January
    46  February
    47  March
...
$ cat -n /usr/share/locale/ja_JP.UTF-8/LC_TIME
...
    38  土曜日
    39  %H時%M分%S秒
    40  %Y/%m/%d
    41  %a %b/%e %T %Y
    42  #午前
    43  AM
    44  #午後
    45  PM
    46  %Y年 %B%e日 %A %X %Z
    47  1月
...

先ほど cal -y で出力されていたのもこの 45 行目、46 行目ですね。

じゃあ /usr/share/locale/ja_JP.UTF-8/LC_TIME を書き換えたら直るんじゃないかと思ったら sudo でも編集できない。SIP (System Integrity Protection) によって守られてるんだった。

ここまで見て「面白い〜」と社の Slack で共有したところ、id:KashEight から「man setlocale が参考になりそう?」という反応を貰った。

$ man setlocale
...
FILES
     $PATH_LOCALE/locale/category
     /usr/share/locale/locale/category  locale file for the locale locale and the category category.
     /usr/local/share/locale/locale/category
                                        locale file for the locale locale and the category category.
...

環境変数 $PATH_LOCALE を見ているので、リカバリーモードで無理矢理 /usr/share/locale/ 以下を書き換えなくてもどうにかなりそう。

$HOME/.config/locale/ 以下に ja_JP.UTF-8/ をまるっとコピーして、LC_TIME を弄る。

$ diff -u {/usr/share,$HOME/.config}/locale/ja_JP.UTF-8/LC_TIME
--- /usr/share/locale/ja_JP.UTF-8/LC_TIME       2025-04-12 14:16:09
+++ /Users/onaka/.config/locale/ja_JP.UTF-8/LC_TIME   2025-05-17 13:51:58
@@ -39,10 +39,8 @@
 %H時%M分%S秒
 %Y/%m/%d
 %a %b/%e %T %Y
-#午前
-AM
-#午後
-PM
+午前
+午後
 %Y年 %B%e日 %A %X %Z
 1月
 2月

環境変数を設定して、caldate を打つと……

$ export PATH_LOCALE=$HOME/.config/locale
$ cal
      5月 2025         
日 月 火 水 木 金 土  
             1  2  3  
 4  5  6  7  8  9 10  
11 12 13 14 15 16 17  
18 19 20 21 22 23 24  
25 26 27 28 29 30 31  
$ date
2025年 5月22日 木曜日 02時19分11秒 JST

やったぜ!

以下のディスカッションも見つけました。Sequoia 15.4 以降のバグっぽいのでフィードバックしておきましょう。先日出た 15.5 でも直ってない!

Mac - ターミナルにて2ヶ月前のカレンダーが表示されます - Apple コミュニティ

株式会社はてなに入社しました

株式会社はてなに入社しました

株式会社はてなに入社しました - hitode909の日記

8 年目。コード書かないマネージャーを 2 年やってるので、そろそろ戻る算段付けないとな

irumo のギガを Mackerel で監視する

この記事は Mackerel Advent Calendar 2024 の 9 日目の記事です。

普段使いのモバイル回線は docomo というか irumo なんですが、自宅から離れて生活するとギガを使い切って買い足す日々を送っています。

11 月は 3 GB 買い足した

データ通信量ってなんか上手いことアラート出せないですよね。2GB 使った後に「使ってるよ」って言われても困る。というわけで Mackerel で監視するようにしました。

まずはブラウザから https://irumo.docomo.ne.jp/ を触って Developer Tools の Network タブを眺める。

日ごとの数字が入っていそうな JSON 発見

https://irumo.docomo.ne.jp/api/uw/tra/v1.0/getdatareferenceinf への POST で JSON が返ってきているっぽいのが分かったので、リクエストヘッダをどこまで削っても取れるかを試す。

今回は spsp Cookie だけ渡せば動くっぽかったです。

curl 'https://irumo.docomo.ne.jp/api/uw/tra/v1.0/getdatareferenceinf' \
-H 'User-Agent: DataUsageCheckerBot/1.0 (@onk)' \
-H 'Cookie: spsp=xxxxxxxx' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-raw 'requestpatterncode=0009&getuser_div=3'
{
  ...
  "data": {
    ...
    "daydatainfo": {
      ...
      "dayusedatainfo_list": [
        {
          "targetdate": "20241201",
          "daydatadetailinfo": {
            "databypacket": "973737",
            "databygb": "0.93",
            "speedlimitreleasecount": "0"
          },
          "speedlimitreleasecountinfo": {
            "databypacket": "0",
            "databygb": "0.00"
          },
          "highspeeddatainfo": {
            "databypacket": "973737",
            "databygb": "0.93"
          }
        },
        ...

各日のデータに speedlimitreleasecount として買い足した回数が入ってるのがちょっと面白い。highspeeddatainfospeedlimitreleasecountinfo はそれぞれ通常時と速度制限時で使った通信量ですね。

JSON が取れたらあとは加工して Mackerel に送れば完成しそう!

値を取るのは jq でエイってやります。

... | jq -r '
.data.daydatainfo.dayusedatainfo_list[] |
 select(.targetdate | test("20241201")) |
 .daydatadetailinfo.databypacket'
973737

それっぽい数字が取れたので送信用にちょっと加工。

まずハードコードして試していた test("20241201") の部分は外から --arg today $(date '+%Y%m%d') と渡します。

また、Mackerel 向けに投げるならタブ区切りの文字列にしたいですよね。\(...) で String interpolation できるし、now で現在時刻を錬成できるので、全体はこうなります。

... | jq -r --arg today $(date '+%Y%m%d') '
.data.daydatainfo.dayusedatainfo_list[] |
 select(.targetdate | test($today)) |
 "custom.irumo.data-usage\t\(.daydatadetailinfo.databypacket)\t\(now)"'
custom.irumo.data-usage 908991  1733754915

あとは mkr throw で投げて完成です。

... | mkr throw --service irumo

グラフができました

データが溜まったら線形回帰関数を使ってアラートを投げたりして遊びましょう。身の回りのメトリックっぽいデータはとりあえず Mackerel に投げておくと、そのうち使い道を思いつくものです。

Mackerel Advent Calendar 2024、明日は id:taxintt さんです。

追記

1 日のデータ取り出しても毎日 0 から始まってしまうので、今月のデータを合計する必要がありました。

jq -r --arg month $(date '+%Y%m') '
  (.data.daydatainfo.dayusedatainfo_list |
  map(
    select(.targetdate | startswith($month)) |
    .daydatadetailinfo.databypacket | tonumber
  ) | add) as $total |
  "custom.irumo.data-usage\t\($total)\t\(now)"
'

コミュニティ生活で大切な三つの袋

これは はてなエンジニア Advent Calendar 2024 の 3 日目の記事です。昨日は id:todays_mitsui による 不動点コンビネータで無名再帰を作る流れをおさらい with JavaScript - 無駄と文化 でした。

さて、オフライン回帰している昨今、In-Person でのコミュニティ活動や大規模カンファレンスがだいぶ復活してきました。少なくとも私の周り (京都) では In-Person イベントがデフォルトに戻ったなぁと思っています。そんなコミュニティ生活で、学びや繋がりを深めるためにはちょっとした「コツ」があります。そのコツを、三つの袋に例えてみました。

ノベルティ

最初の袋は「ノベルティ袋」です。これは自分の存在を認知して貰うための袋です。

コミュニティ活動では、たくさんの人と出会いますが、後日「見たことはある気がするんだけど」となりがちですよね。ここで活躍するのが、ステッカー類です。

例えば自分のアイコンのステッカーや、ちょっとユーモアの効いた名刺などを配ると、それが記憶に残って次回も話しかけて貰えたりします。先日もこんな話を見かけました。コミュニティの性質を感じて面白いですね。

あるいは自社のノベルティも効果的です。「あっこれ前貰った!」と覚えてくれる可能性が高まります。

ステッカーを貰って困るという人も多いかもしれませんが、カンファレンスでのノベルティ類はカンファレンスや年ごとにクリアファイルにまとめて取っておくと、数年後に見返したときに時代を感じて面白いですよ。毎回処遇に困っている方はぜひやってみてください。

ノベルティ袋、ぜひみなさんも準備していきましょう。自分の存在感を強める武器になります。

胃袋

次は「胃袋」です。これは特に遠方でのカンファレンスで重要です。

遠方開催の場合、たいていの人が小旅行として泊まりがけで参加します。そういう場だと、懇親会も一次会、二次会、そして三次会まで続くことも珍しくありません。懇親会の最初の方はどうしてもフォーマルな雰囲気が残っていますが、時間が進むにつれてお互いに打ち解け、本音や深い話が出てくるものです。それを味わえるのが三次会などの深夜の時間帯です。

特に「同じ時間を共有する」というのが良いのでしょう。懇親会の後半になってリラックスした時間、本音が漏れてくるような時間帯は、ただの情報共有ではなく心の距離を縮める時間になります。単なる単純接触効果を超えた効果がここにある気がしています。

同じ時間を共有した結果、朝帰りになったことで「戦友」という肩書きが増えていくのも、グッと絆を深めるポイントです。

もちろん全員にすべての会に参加しろというわけではありません。健康第一です。でも、たまには「今日は締めのラーメンまで付き合うぞ」と覚悟を決めてみるのもいいものです。そのようにして生まれた絆や信頼関係はかけがえのないものになるはずです。

命を削って参加してますからね。

胃袋をしっかり鍛えて、楽しみながら会話を重ねて、信頼関係を構築していきましょう。

パッチ袋

最後は「パッチ袋」です。

コミュニティに初めて参加するときに、自分から積極的に自己紹介するのは難しいかもしれません。でもコードを GitHub にアップしておくことで、自然とコミュニティの一員になれます。

先日 Kaigi on Rails 2024 に参加した際、以前 sinatra-activerecord gem に Pull Request を数件出していたことで @cupnoodle (現 sinatra-activerecord のメンテナ) から話しかけて貰えました。この出来事自体は 3 年前の Pull Request に言及してくれるという、ちょっと本当に凄すぎるエピソードなんですが、こういうこともあるんですよ。いや本当にビックリした。

先ほど「胃袋」とお伝えしましたが、飲み会で生まれるのは一時的な親近感だと思います。それも大事なんですが、長期的なコミュニティの存続のためには具体的な成果物が根底にあるべきと考えます。

コーディングは、成果物を共有できる、共同で成長できる、という性質を持っています。「このコミュニティにいると技術的に成長できる」「価値のある貢献ができる」という体験が Social Coding にあります。プログラマとしての我々がコミュニティに提供できる最高のものは、やはり工数。継続的に労働力を提供し続けるのが一番の貢献です。

パッチ袋には、コミュニティの存続を支える力が詰まっています。コードを通じた繋がりもいいものですよ。

むすび

というわけで、「三つの袋」について話してきました。ノベルティ袋で繋がって、胃袋でイベントを楽しみ、パッチ袋で技術を共有する。この三つの袋を持っていれば、コミュニティ生活をより充実したものにできます。ぜひ準備して、今後のコミュニティ生活を思いっきり楽しんでください。

はてなエンジニア Advent Calendar 2024、明日は id:susisu です。

Modular Monolith はどの辺りから考え始めるものなのか

モノリスでは大変なので、マイクロサービスやモジュラーモノリスにして認知負荷を減らしたり、生産性の劣化に抗いたいという考え方がある。

モジュラーモノリスとは

モジュラーモノリスについては、だいたい infoq.com のモノリスシリーズ(?)を読めば良いんじゃないか。

有名なのは Shopify のヤツ

モノリスとマイクロサービスの中間にある、1 アプリケーションなんだけどモノリスでは無い、アプリ内でモジュール分けされているアーキテクチャのこと。app/ の直下に MVC を置くんじゃなくて、COMPONENTS (例えば billing)/app/ の下に MVC を置く、ようなイメージ。

モジュラーに移行するタイミング

僕の感覚だと、数百モデルは全然モノリスで扱えると思っている。少なくとも 300 models 程度でモジュラーにしていく必要はまったく感じない。

世の中で見つけたモデル数だと

の辺りは確かモノリスだったと思う。800 なり 1000 なりを超えても普通にモノリスで扱っているのは割と勇気が貰える数字だよね。

SmartBank さんは B/43のサーバーサイド開発の醍醐味と伸びしろ - inSmartBank の中で

現在はモノリスで開発していますがモジュラーモノリスだったりマイクロサービスのようなアーキテクチャも検討していきたい

と書かれてもいる。

800 は割としんどい数字なのだろう。この辺りが「移行したくなるタイミング」なのは僕の感覚とも合っている。

認知負荷が非常に高まってからでは遅い

じゃあ 800 になってからモジュラーに分割し始めるのかと言うと、痛みが表面化してから分け始めると、完了するまでずっと痛みを抱えていることになるし、既に密に絡まり合いまくっていて、分割するのはとてもコストがかかる作業になる。

その手前、400-500 models 辺りから着手し始めると、ちょうど安定的な生産性を得続けられるんじゃないだろうか。触っていて厳しさを感じずに開発できる環境を維持するチャンスがこのタイミング。ぜひ開発合宿とかでチャレンジしてみて欲しい。

逆に言うと、400-500 models にまだ手が届かない状態でモジュラーに分け始めるのは時期尚早っぽいと僕は思う。普通のリファクタリングがその手前にいっぱいあるんじゃないかな〜。

このタイミングでやるべきだった、という後知恵を集めたい。御社の例を教えてください。

僕はモノリス派なので話半分に聞いてもらうとして、じゃあ 200-250 models を超えたらモジュラーにし始めるタイミングなのかもしれないね。モジュラーでも爆速で走る方法を知っていて、それで困りが無いのであれば、いつ導入してもいいわけだし。

複雑なアプリケーションを運用する功夫クンフー》は無限に積んでおきたいものである。