動かざることバグの如し

近づきたいよ 君の理想に

sysctl -p と sysctl --system の違い

環境

  • Ubuntu 24.04

問題

カーネルパラメータを変更したく、 /etc/sysctl.d/配下にの/etc/sysctl.d/99-my.conf に

net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

設定した。しかし sysctl -p しても反映されない。AIに聞いてみたら sysctl -p ではなかった。

誤解してた

結論から言うと、引数なしの sysctl -p/etc/sysctl.conf しか読み込まない。/etc/sysctl.d/ 配下の .conf ファイルは最初から対象外だ。

つまり 99-my.conf に何を書こうが sysctl -p では永遠に反映されない。コメントしか書かれていない実質空の /etc/sysctl.conf を読み直して、何事もなかったかのように終了していただけだった。

正解はこっち。

sudo sysctl --system

読み込む対象の違いはこうなっている。

コマンド 読み込む対象
sysctl -p /etc/sysctl.conf のみ(引数でファイル指定も可)
sysctl -p /etc/sysctl.d/99-my.conf 指定した1ファイルのみ
sysctl --system /etc/sysctl.d//run/sysctl.d//usr/lib/sysctl.d/ などの配下の .conf ファイルを優先順位に従って全部読み込み、最後に /etc/sysctl.conf も反映

ややこしいのは、同じ /etc/sysctl.d/vm.swappiness あたりを置いていると「そっちは効いてるじゃん」となるケース。あれはブート時にsystemdの systemd-sysctl.service/etc/sysctl.d/*.conf を全部読んで適用しているからで、sysctl -p が効いたわけではない。再起動さえ挟めば反映されるので、余計に原因が分かりにくい。

反映されたかどうかの確認

sysctl net.core.rmem_max

sysctl --system は適用したファイルを順に出力してくれるので、パラメータ名のtypoやカーネルモジュール未ロードがあれば sysctl: cannot stat ... のようなエラーがその場で出る。文法チェックも兼ねられて便利。

それでも値が変わらないなら、他のファイルに上書きされている可能性を疑う。

grep -r "rmem_max" /etc/sysctl.d/ /etc/sysctl.conf /etc/ufw/sysctl.conf 2>/dev/null

/etc/sysctl.d/ 内では同じパラメータがあった場合、ファイル名のアルファベット順で後勝ちになる。Ubuntuにはデフォルトで 99-sysctl.conf -> ../sysctl.conf というシンボリックリンクが置かれていて、これは 99-my.conf より後("s" > "m")に処理される。/etc/sysctl.conf 側に同じキーが書いてあれば当然そっちが勝つ。

あとufwを使っているなら /etc/ufw/sysctl.conf も見ておいたほうがいい。こいつが /etc/sysctl.conf の設定を上書きすることがあり、地味に見落としやすい罠になっている。

昔は sysctl -p が正解だと思ってた

10年以上前に触っていたときは sysctl -p で通っていたはずなんだが、と思って調べてみたら、sysctl -p の挙動自体は当時から今まで一切変わっていなかった。変わったのは周辺のエコシステムのほうだった。

  • 設定を /etc/sysctl.d/ に分割して置くやり方が普及した。
    • これはsystemdが持ち込んだ仕組みで、systemd-sysctl.service がブート時に /etc/sysctl.d/*.conf を自動で読み込む。
    • Ubuntuがsystemdを正式採用したのは15.04(2015年)で、それ以前はUpstartやSysV initが主流。
    • 設定は /etc/sysctl.conf に全部書いて sysctl -p で読ませる、という運用がごく普通だった
  • procps側に --system オプションが追加された。
    • それ以前は /etc/sysctl.d/ 配下を手動でまとめて反映させたければ、ファイルを1個ずつ sysctl -p ファイル名 で指定するしかなかった

なので「昔は sysctl -p でよかった」という記憶自体は間違っていない。今でも /etc/sysctl.conf に直書きしている環境なら sysctl -p で問題なく反映される。設定の置き場所だけ今風の /etc/sysctl.d/ に乗り換えておいて、コマンドは昔のまま、というギャップにハマっただけだった。

ちなみに --system の読み込み順は /etc/sysctl.d//run/sysctl.d//usr/lib/sysctl.d/ → ... → 最後に /etc/sysctl.conf となっている。この一覧の最後に /etc/sysctl.conf が居座っているあたり、今でもこいつは「最後に読まれて他を上書きできる」特別な立ち位置を保ち続けているらしい。

参考リンク

macOSのシリアル番号をターミナルで確認するコマンド

環境

  • macOS Tahoe

やりたいこと

Macのシリアル番号は「このMacについて」を開けば見られるが、いちいちマウスでポチポチするのが面倒。SSH越しでも取れるようにターミナルから一発で引きたい。

コマンド

これ。以上。

❯ ioreg -c "IOPlatformExpertDevice" | awk -F'"' '/IOPlatformSerialNumber/ {print $4}'
XXXXXXXX

ioreg はI/O Kitのレジストリをダンプするコマンドで、-c IOPlatformExpertDevice でハードウェア情報を持つノードだけに絞れる。この中の IOPlatformSerialNumber が本体のシリアル番号。

出力は "IOPlatformSerialNumber" = "XXXXXXXX" という形式なので、awkでダブルクォートを区切り文字にして4番目のフィールドを取ればシリアル番号だけが残る。余計な文字が一切付かないので、そのまま変数に入れて使える。

もうひとつのやり方が system_profiler を使う方法。

❯ system_profiler SPHardwareDataType | grep "Serial Number"
2026-08-11 19:55:04.868 system_profiler[89634:2489984] hw.cpufamily: 0x1d5a87e8
      Serial Number (system): XXXXXXXX

こっちのほうが人間には読みやすいのだが、見てのとおり hw.cpufamily: ... という謎のログが標準エラー出力に混ざってくる。

あとsystem_profilerは全ハードウェア情報を集めてから吐き出す仕様なので、ioreg と比べると体感で分かるレベルで遅い。スクリプトから叩くなら素直に ioreg のほうを使っておけば間違いない。

MyISAMで大量DELETEしたあとOPTIMIZE TABLEすべきなのか

環境

  • MySQL

状況

MySQLで数百GBまで肥大化した1テーブルをダイエットすべく、古いレコードを大量にDELETEした。

仮に300GBで3億レコードあったテーブルから2億レコード消して1億レコードにしたとする。単純計算なら100GBになってほしいところだが、そうはならない。DELETEしてもディスク上のファイルサイズは1バイトも減らない。

ネットで調べてみるとOPTIMIZE TABLEを実行すれば実サイズが減るらしい。ただ、OPTIMIZEした場合としなかった場合で、以降のINSERTでファイルサイズがどう変わるのかがよく分からなかったので調べてみた。

Optimize table後の挙動

まず前提として、MyISAMではDELETEしても.MYDや.MYIファイルそのものは縮まない。削除されたレコードの領域は「空き領域」として内部的にマークされるだけで、OSから見たファイルサイズは変わらないままだ。OPTIMIZE TABLEはこの空き領域を詰め直してファイルを作り直す処理なので、実行して初めてサイズが減る。

今回の想定を整理するとこうなる。

  • 削除前: 300GB(3億レコード)
  • 2億レコードをDELETE後: ファイルサイズは300GBのまま
  • OPTIMIZE TABLE後: 100GB
  • 空き領域相当: 約200GB
  • そのあとX件追加したときの実データ増加: 約10GB

ここで気になるのが、OPTIMIZEせずに放置したテーブルにX件(10GB分)をINSERTしたら300 + 10で310GBになるのか?という点。

結論から言うと310GBにはならず、300GBのままになる可能性が高い。

MyISAMはINSERT時にいきなりファイル末尾へ追記するのではなく、まずファイル内部の空き領域を再利用しようとするからだ。今回は200GBという十分すぎる空きがあるので、10GB分のレコードはその中に収まる。ファイルの枠自体を広げる必要がないので、サイズは300GBのまま据え置きになる。

つまり概念的にはこういう違いになる。

  • OPTIMIZEした場合: 空き領域を除去して100GBに縮小 → X件追加で110GB
  • OPTIMIZEしなかった場合: ファイルは300GBのまま、内部の約200GBが再利用可能領域 → X件追加分の10GBがそこに収まるので約300GBのまま

ただし「きっちり300GBのまま」を保証するものではない。次のようなケースでは多少増えることがある。

  • 挿入する行が既存の空き領域のブロックに収まらない
  • 可変長行で空き領域が細かく断片化している
  • インデックス領域を完全には再利用できない
  • concurrent_insertの設定や挿入方法によって末尾へ追記される
  • 追加行のサイズやキー分布が以前と異なる

なので実際には301GBや305GBに増えることはあるし、条件次第で310GBを超えることもありうる。とはいえ「削除した分がまるごと無駄になり続ける」わけではなく、消した領域はちゃんと再利用される、というのが要点だ。

ディスクを今すぐ空けたいならOPTIMIZE TABLEを打つしかないが、また同じ規模までデータが増えるのが分かっているなら、テーブル全体のロックと作り直しのコストを払ってまで急いで実行する必要はない、という判断もできる。

確認するSQL

空き領域がどれくらい溜まっているかはSHOW TABLE STATUSのData_free、もしくはinformation_schema.tablesのdata_freeで確認できる。テーブル単位でまとめて見たいならこっちのほうが扱いやすい。

SELECT table_name, engine,
  ROUND(data_length / 1024 / 1024 / 1024, 2) AS data_gb,
  ROUND(index_length / 1024 / 1024 / 1024, 2) AS index_gb,
  ROUND(data_free / 1024 / 1024 / 1024, 2) AS free_gb,
  ROUND(100 * data_free / NULLIF(data_length, 0), 2) AS free_pct
FROM information_schema.tables
WHERE table_schema = 'YOUR_DATABASE' AND table_name = 'uids';

free_gbが実データ(data_gb)に対して無視できない割合になっていれば、それがDELETEで空いた再利用待ちの領域ということになる。free_pctが数百%みたいな数字になっていたら、まさに今回のケースだ。

注意点として、data_freeの値がそのまま100%効率よく再利用されるとは限らない。断片化していれば実際に使える量はこれより少なくなるので、あくまで目安として見ておくのがいい。

確認URL

UbuntuでDockerを完全初期化する方法

環境

  • Ubuntu

やりたいこと

Dockerの調子が悪いので完全初期化したい。生半可な削除ではむしろ環境が壊れてしまったので完全削除対応メモ。

手順

Dockerを停止する。 docker.socket を止めないとsocket activationでdockerdが勝手に起き上がってくるので、両方止める。

$ sudo systemctl stop docker.socket docker containerd

remove ではなく purge で削除

$ sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

イメージ・コンテナ・ボリュームを削除する。パッケージを消してもこのディレクトリは残るので必ず削除する。

$ sudo rm -rf /var/lib/docker
$ sudo rm -rf /var/lib/containerd

daemon.json の記述ミスで起動しない、みたいなケースはここを消さないと直らない。ユーザーごとの ~/.docker にもログイン情報やcontextの設定が残っているので一緒に消す。

$ sudo rm -rf /etc/docker
$ sudo rm -rf /etc/systemd/system/docker.service.d
$ sudo rm -rf /var/lib/docker-engine
$ rm -rf ~/.docker
$ sudo systemctl daemon-reload

dockerグループを削除する。グループを消すと usermod -aG docker $USER をやり直すことになるが、GIDがズレたまま残っているのが原因のパーミッションエラーもあるので、完全初期化を謳うなら消す。

$ sudo groupdel docker

Ubuntuでsshログインが毎回25秒待たされる問題を解決した話

環境

  • Ubuntu 24.04

症状

SSHでログインしようとすると、認証が通ってからプロンプトが出るまでやたら待たされる。体感で20秒以上。

しかもSSHだけの話ではなく、ログイン後に sudo su - したときも同じくらい待たされる。

負荷が高いわけでもない。ロードアベレージは平常運転だし、いったんシェルが返ってきてしまえば他の作業は普通に動く。要するに「入り口だけ」が遅い。

この手の症状は経験則上DNSの名前解決が詰まっているケースが多いので真っ先に疑ったのだが、

$ nslookup google.com

は即返ってくる。逆引きも問題なし。つまりDNSは白だった。

調査

DNSが白なら何が問題なんだ、ということでAIに聞いたら「journalctlを見せろ」と言うので渡したところ、pam_systemd が怪しいと指摘された。

# journalctl -u ssh --since "30 min ago" -o short-precise | grep -i pam_systemd
8月 03 15:53:25.490898 sshd[22961]: pam_systemd(sshd:session): Failed to create session: Connection timed out
8月 03 15:54:23.513148 sshd[23028]: pam_systemd(sshd:session): Failed to create session: Connection timed out
8月 03 15:54:36.814603 sshd[23040]: pam_systemd(sshd:session): Failed to create session: Connection timed out
8月 03 15:56:38.677345 su[23170]: pam_systemd(su:session): Failed to create session: Connection timed out
8月 03 15:59:12.416183 sshd[23273]: pam_systemd(sshd:session): Failed to create session: Connection timed out
8月 03 16:00:22.013104 sshd[23300]: pam_systemd(sshd:session): Failed to create session: Connection timed out
8月 03 16:01:31.837444 su[23379]: pam_systemd(su:session): Failed to create session: Connection timed out
8月 03 16:02:43.493113 sshd[23470]: pam_systemd(sshd:session): Failed to create session: Connection timed out
8月 03 16:04:42.625850 sshd[23521]: pam_systemd(sshd:session): Failed to create session: Connection timed out

SSHとsuの両方で同じログが出ている。しかもタイムアウトしているだけで、最終的にログイン自体は成功している。体感の遅さと一致する。

次に、認証が通った瞬間とタイムアウトした瞬間の時刻を突き合わせてみる。

15:53:00.462 sshd: Accepted publickey ...(ログイン成功)
15:53:25.490 sshd: pam_systemd(sshd:session): Failed to create session: Connection timed out
                    ↑ ちょうど 25.03 秒後

きれいに25秒。毎回この値なので、ネットワークの揺らぎのような不定な待ちではなく、どこかに設定された固定のタイムアウト値を待ち切っていることになる。

そこでsystemd-logindとdbus-daemon側のログを見ると、その25秒の出どころが出てきた。

systemd-logind: Failed to start user slice user-1000.slice, ignoring:
                Connection timed out (org.freedesktop.DBus.Error.Timeout)
dbus-daemon:    [system] Failed to activate service 'org.freedesktop.systemd1':
                timed out (service_start_timeout=25000ms)

service_start_timeout=25000ms がまさに25秒で、観測された遅延と完全に一致する。

読み取れるのは、logindもdbus-daemon自体も正常に動いていて、その先にいるsystemd本体(PID1)がD-Bus越しの要求に応答していないということ。ログインのたびにPAMのsessionフェーズがそれを25秒待たされているわけだ。

となると次の疑問は「PID1が何か重い処理で詰まっているのか」だが、これは違った。

# systemctl list-jobs
No jobs running.

# systemctl --failed
session-222422.scope    failed
session-234294.scope    failed
apparmor.service        masked, failed

# systemctl status
State: degraded
 Jobs: 0 queued

ジョブキューは空。滞留しているユニットは無い。

failedになっている2つの session-*.scope は、過去のログインで同じ25秒タイムアウトが起きたときにセッションscopeの起動が失敗した残骸であって、原因ではなく結果のほう。

これで「特定のユニットがジョブキューをブロックしている」という線は消えた。残るのは、systemdマネージャのD-Busバス接続そのものが壊れている、つまり org.freedesktop.systemd1 というバス名の登録がおかしくなっている、という仮説になる。

このマシン、稼働開始から4年6ヶ月無停止だった。その間のどこかでPID1とdbus-daemonの間の接続状態が壊れ、PID1が org.freedesktop.systemd1 としてバスに応答できなくなっていたと考えるのが自然だろう。重い処理で詰まっているのではなく、通信経路そのものの不整合ということになる。

原因

systemd (PID1) が自分自身のD-Bus管理インターフェースである org.freedesktop.systemd1 への要求に応答しなくなっていた。

そのせいでログインのたびに pam_systemd のセッション作成要求が宙に浮き、D-Busの25秒という固定タイムアウトを待ち切るまで先に進めなくなる。ログインが失敗しないのは、pam_systemd がタイムアウトしてもエラーを無視して処理を続けるため。要するに「必ず25秒無駄に待ってから成功する」状態だった。

SSHログインも sudo su - も、PAMのsessionフェーズで systemd-logind にセッション登録を依頼するという同じ経路を通る。だから両方が揃って同じだけ遅くなる。

対応

壊れているのがPID1のD-Bus接続状態だけなら、systemd自身を再起動してバスに繋ぎ直させればいい。それをやってくれるのが daemon-reexec である。

sudo systemctl daemon-reexec

daemon-reload はユニットファイルを読み直すだけだが、daemon-reexec はsystemdのバイナリ自体を再実行する。稼働中のサービスやプロセスは維持したまま、マネージャの内部状態とD-Bus接続だけが作り直される。要するにOSを再起動せずにPID1だけ入れ直すやつ。

実行後、SSHもsudoも即座にプロンプトが返るようになった。