記事一覧

技術メモを残していきます

NVMe PRのFencingがおもしろい:旧Brokerに交代を伝えず、書き込みだけを止める

読んだもの Alibaba Cloudの阿里云云原生チームによる、RocketMQの高可用性の記事(出典は末尾)。生成AIと一緒に読んで整理したメモで、記事本文は自分でも開いて確認した。細部(実測値や予約種別など)は追えていないので、ここでは設計の発想だけ書く。 そもそも何が問題か Active/Standbyで、Broker AからBroker Bへ切り替えたい。ただ、Aが応答しないからといって、Aが止まっているとは限らない。ネットワーク分断やI/Oハングで、AもBも「自分がActiveだ」と思って同じデータを書くと、Split-Brainでデータが壊れる。 「BがActiveになったとAに通知すればいい」と最初は思う。でも通知が届かないから困っているのであって、リースや分散ロックを足しても、失効を認識できない旧ノードは残る。 旧ノードが交代を認識することに依存する設計は、障害時にいちばん当てにならない。 NVMe PRでどう解くか NVMe Persistent Reservation(PR)は、ストレージ側で「書き込める保持者」を決める仕組み。保持者以外の書き込みは、デバイスが拒否する。 記事の構成では、Multi-Attachで新旧ノードから同じディスクを見えるようにしておき、障害時に新ノードがPRを奪う。つまり、 Multi-Attach:同じディスクが見える(書き手の制限はしない) NVMe PR:書ける人を一人に絞る という役割分担になる。旧Brokerのプロセスが生きていても、交代を知らなくても、書き込みはストレージ側で止まる。 流れを図にするとこうなる(理解用の簡略図で、実際の実装手順そのままではない)。 【通常時】 Broker A (Master) Broker B (Shadow) │ WRITE可能 │ データ領域は未マウント │ │ leaseを読むだけ ▼ ▼ ┌─────────────────────────────────────┐ │ 共有ブロックストレージ │ │ NVMe PR: Aが書き込み保持者 │ └─────────────────────────────────────┘ 【障害発生】 Broker A Broker B │ 応答なし │ leaseが進まないことを検知 │ ※実は生きているかも │ │ ※交代を知らない ▼ │ PRを奪う(preempt) ▼ ▼ ┌─────────────────────────────────────┐ │ 共有ブロックストレージ │ │ A: WRITE拒否 ✕ B: WRITE許可 ○ │ └─────────────────────────────────────┘ 【引き継ぎ後】 Broker A Broker B │ 旧保持者 │ 新保持者 │ WRITEしても拒否される │ キャッシュ処理 → マウント │ │ → journal/WAL回復 │ ▼ │ サービス再開 Aは交代を知らないまま動いていてもいい。止めているのはAの心がけではなく、ストレージだ。 ...

2026年10月25日 · 1 min

Raspberry Piで見かけた「irq/173-vc4 hdmi hpd」は何者か

はじめに htop を眺めていたらこんな名前のスレッドが並んでいた。 irq/173-vc4 hdmi hpd connected irq/174-vc4 hdmi hpd disconnected irq/175-vc4 hdmi cec rx irq/176-vc4 hdmi cec tx HDMIは挿していないのに connected も disconnected もいるので、少し気になって調べた。 調べた範囲では、HDMIドライバが作る割り込み処理用のカーネルスレッドで、特に異常なものではなさそう。 以下はそのメモ なお番号は環境によって違う。 名前の読み方 部分 意味 irq/173 IRQ番号173の割り込みを処理するカーネルスレッド vc4 Raspberry Piのディスプレイ周りを担当するLinuxのDRMドライバ hpd Hot Plug Detect。HDMIの抜き挿しの検知 cec HDMI経由でテレビなどを操作する仕組み。rxが受信、txが送信 なぜHDMIを挿していなくてもいるのか カーネルのソースを見ると、vc4のHDMIドライバは初期化のときに devm_request_threaded_irq() で割り込みを登録している。このとき渡す名前の文字列が、そのままスレッド名(irq/番号-名前)になる。 以下はLinux mainlineのコードをさらに簡略化したもの。 Raspberry Pi OSのカーネルは独自のツリーなので細部は違うかもしれない。 /* drivers/gpu/drm/vc4/vc4_hdmi.c より */ ret = devm_request_threaded_irq(&pdev->dev, hpd, NULL, vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, "vc4 hdmi hpd connected", vc4_hdmi); つまりスレッドは HDMIが挿さったとき ではなく ドライバが割り込みの待ち受けを登録したとき に作られる。 ...

2026年10月20日 · 1 min

AIに丸投げしたコードを見直したら、SELECTだけで死ぬほど遅かった話

前提 個人開発のEDINET分析ツール(財務レポートを解析してDiscordに投げる趣味プロジェクト)で、フロントエンドのコードをAIにほぼ丸投げして書かせた部分があった。 後から見直していたら、明らかに厳しい実装が出てきたので調査した記録。今回は原因の特定と応急処置(SELECT修正)までで、DBスキーマ自体の見直しは今後の課題として持ち越す。 出てきたコード API利用状況を表示するダッシュボードコンポーネントで、以下のように4本のAPIをPromise.allで並列取得していた。 const [statsRes, logsRes, tsRes, statusRes] = await Promise.all([ apiClient.get(`/api-logs/stats?days=${days}`), apiClient.get('/api-logs/logs?limit=50'), apiClient.get(`/api-logs/timeseries?days=${days}&interval=${interval}`), apiClient.get(`/api-logs/status?days=${days}`), ]) 取得後、4つのstateをfinallyブロックで一括更新し、loadingも1つのフラグで管理していた。 最初の疑問:Promise.allの並列度は妥当か Promise.all自体は4本のリクエストを同時発火するので、直列でawaitを重ねる実装(MDN: Promise.all)と比べれば理にかなっている。 ただし設計上の課題は残る。 loadingが1つのフラグなので、一番遅いレスポンスに画面全体の表示タイミングが引っ張られる useEffectの依存配列が[days, interval]のみで、intervalしか変えていない操作でも無関係なlogsまで再取得される 対策案としては、各エンドポイントを個別のuseEffect+個別stateに分割し、届いた順にレンダリングする方式が考えられる。この場合、依存配列も自然に分離でき、interval変更時に不要な再取得も防げる。ただしこれは「体感速度」の改善であり、根本的なネットワーク・サーバー負荷の解消にはならない。 実測結果 Chrome DevToolsのNetworkタブで実測したところ、以下の結果になった。 エンドポイント サイズ レスポンスタイム(1回目) レスポンスタイム(2回目) stats?days=7 0.3kB 5.69秒 6.25秒 logs?limit=50 7.7kB 200ms 351ms timeseries?days=7&interval=hour 14.5kB 11.52秒 6.01秒 status?days=7 0.5kB 7.92秒 11.58秒 同一アプリの別画面(Stats.tsx)にあるsummary・alerts・ranking系のエンドポイントは、同程度の小さいペイロードで30〜111msと桁違いに速い。 結論:問題はフロントの並列化ではなくバックエンド側 数百バイト〜十数キロバイトのレスポンスに対して秒単位〜十数秒のレイテンシは、ネットワークやReact側の描画方式では説明がつかない差だった。並列fetch自体は問題ではなく、疑うべきは以下。 集計クエリのN+1(プロバイダーごとにループでクエリを投げている可能性) 日時カラムへのインデックス未設定(daysによるWHERE句がフルスキャンになっている可能性) interval=hourの集計処理(DB側の集計かアプリ側ループかで原因が分岐) 2回目の計測でむしろ悪化(stats5.69→6.25秒、status7.92→11.58秒)していることから、キャッシュが効いていない、あるいはテーブルの成長に伴って劣化する設計になっている可能性 N+1問題自体についてはN+1 query problem (MDN Glossary)を参照。 今回の対応範囲 今回は該当エンドポイントのSQLクエリを見直し、集計方法をSELECT文レベルで修正するところまでを実施した。 DBスキーマ自体(インデックス追加や正規化の見直しなど)には手を付けていない。テーブルが育つほど劣化する可能性が実測から示唆されているため、これは今後の課題として残す。 教訓 AIに丸投げしたコードは、動いているように見えても「並列化されているか」程度の表面的なチェックだけでは不十分 フロントエンドの実装が妥当でも、DevToolsのNetworkタブで実測しない限りボトルネックの所在は特定できない 「並列処理にしたから速いはず」という思い込みは、実測前の仮説に過ぎない

2026年9月16日 · 1 min

省電力設計の本質:通信・位置情報・ディスプレイをどう絞るか

「Androidアプリ開発の極意」(木田学ほか著、テックファーム監修、技術評論社、2017年)を読み進めながら整理したメモがベースになっている。刊行から年数が経っている本だが、バッテリー消費を抑えるための設計判断そのものは古びていない部分が多い。 はじめに アプリが「重い」「電池が減る」と言われるとき、原因の多くはCPU負荷そのものより、通信・位置情報・ディスプレイの3つの使い方に集約される。個別の最適化テクニックを覚える前に、「なぜその実装がバッテリーを食うのか」という設計原理を押さえておくと、新しいAPIが出てきたときにも応用が効く。 基本方針はシンプルで、避けられるなら避ける、必要なら節約する設計にするの一言に尽きる。以下、この3領域それぞれで何が問題になり、Androidがどう対処してきたかを整理する。 通信:タイミングをずらし、量を減らす 決まった時間に一斉通信しない 複数のアプリが同じタイミングで定期通信を行うと、端末は本来スリープしていられる時間まで叩き起こされることになる。これに対するAndroidの答えがAlarmManagerのアラームバッチング機構だ。 AlarmManager.set()(setExact()ではない通常版)は、API 19(Android 4.4)以降、指定時刻ちょうどの起動が保証されない仕様に変わっている。システムが複数アプリのアラームをまとめて処理し、意図的に遅延・分散させることで端末のウェイクアップ回数そのものを減らす仕組みだ。厳密な時刻指定が必要なカレンダーの通知などはsetExact()を使う設計になっており、「本当に正確な時刻が必要か」を自問するだけで、使うAPIが変わる。 電源状態・接続状態に応じて挙動を変える 満充電/充電中/放電中/充電なし、バッテリー残量は取得できるので、これに応じて通信頻度を変える設計が可能 そもそもネットワークに繋がっていなければ、到達しないとわかっている通信を試みるだけ無駄になる。接続状態はAndroidでは容易に取得できる 3G等の従量課金回線では、通信量そのものを絞る設計も選択肢に入る(ただし経路をつど判定するコストとのトレードオフは意識しておきたい) バックグラウンド通信制限(データセーバー) Android N以降には「データセーバー」機能があり、有効化されているとフォアグラウンドでも通信を抑える方向に働く。アプリ側はこの挙動があり得ることを前提に設計しておく必要がある。 通信データ量そのものを減らす GZIP圧縮は最低限やっておく データはまとめて取得し、通信回数自体を減らす キャッシュを活用する レジュームダウンロード(範囲指定でのデータ取得)を使えば、中断位置から再取得して結合できる ポーリング vs プッシュ 定期的に通信して更新を確認する「ポーリング」に対し、サーバー側から必要なときだけ通知する「プッシュ」はバッテリー面で有利になりやすい。AndroidではFCM(Firebase Cloud Messaging)が定番で、以下の制約を踏まえて設計する。 項目 内容 データメッセージのペイロード上限 4096バイト(4KB) 通知メッセージのペイロード上限 2KB メッセージの有効期限(デフォルト) 4週間(約28日) ペイロードには最小限のデータ(IDなど)だけを載せ、本体データはAPI経由で取得する設計が定石になる。ペイロードに実データを詰め込みすぎると、将来的にサイズ上限に引っかかって通知が届かなくなるか、最悪クラッシュにつながる。 リトライ間隔を空ける 通信失敗時のリトライを短い間隔で繰り返すと、サーバー負荷とクライアントのバッテリー消費の両方を悪化させる。指数バックオフなど、間隔を徐々に空ける設計が基本になる。 通信量の主導権をユーザーに渡す 通信量に関わる挙動(画質、同期頻度など)をユーザー自身が選べるようにするのも有効な設計の一つ。 ディスプレイ:点灯そのものを減らす ディスプレイは単純に「光っている時間」がそのままバッテリー消費に直結する。不要な点滅を避けるのは当然として、注意すべきはWakeLockの扱いだ。 WakeLockは画面スリープを防ぐ機能で、バッテリーの敵として扱われがちだが、ダウンロード処理中など「画面がオフになると処理自体が失敗する」場面では必須になる。**「使わない」ではなく「必要な範囲だけ、必要な時間だけ保持して確実に解放する」**設計判断が求められる。 位置情報:APIの選び方で消費が変わる LocationServices(Google Play Services必須)を使う方が、LocationManager単体よりバッテリー効率が良い傾向にある LocationManagerを使う場合、プロバイダをNETWORK_PROVIDERに指定するとWiFi経由の測位のみになり、GPSを使うより省電力になる 精度が本当に必要な場面かどうかを見極め、過剰に高精度なプロバイダを選ばないことが基本方針になる。 実装をまとめて任せる:JobScheduler ここまでの対策(電源状態に応じた実行判断、リトライ間隔、実行タイミングの分散など)を個別に手組みするのではなく、JobScheduler一本に任せるという選択肢がある。バッテリー状態・実行遅延・リトライ間隔などを条件として設定でき、標準APIとして安定している。特別な理由がない限り、これを軸に設計するのが妥当だろう。 なお、Android N以降ではブロードキャスト配信の仕組み自体にも変更が入っている。CONNECTIVITY_ACTION(ネットワーク変更)のような特定のブロードキャストを受信対象アプリが一斉に受け取ることでパフォーマンスが悪化する問題への対処で、対象イベントは複数あるが、ここでは深追いしない。 Expo/React Nativeでは何に当たるか ここまでの話はAndroidネイティブAPIの話だが、Expo管理下のReact Nativeアプリでは、それぞれ対応するモジュールに置き換わる。「素のAndroid APIを直接叩けない」制約はあるが、内部的には同じ仕組みに乗っているケースが多い。 Androidネイティブの概念 Expoでの対応 備考 AlarmManager / JobScheduler expo-background-task SDK 53でexpo-background-fetchを置き換え。Android側は内部的にWorkManager(JobSchedulerベース)を利用 FCM(プッシュ通知) expo-notifications リモート通知の受信・権限管理をラップ。ペイロードサイズ等の制約はFCM側の仕様がそのまま適用される WakeLock expo-keep-awake useKeepAwake()フックまたは命令的APIで画面スリープを抑制。必要な範囲だけ有効化する設計方針は変わらない 接続状態の判定 expo-network オンライン/オフライン、接続種別(WiFi/セルラー等)の取得に使う バッテリー状態の取得 expo-battery 充電状態・残量の取得。電源状態に応じた通信頻度の出し分けに使える LocationManager / LocationServices expo-location 精度指定(Accuracy)でGPS/WiFi測位のどちらに寄せるかを制御できる 注意点は主に2つ。 ...

2026年9月9日 · 1 min

PostgreSQLのrandom()、SELECTとORDER BYで同じ式書くと使い回される

担当 タイトル案、本文全般:私 環境・サンプルコード系全般:Claude SQL検証解説:Claude ヘッダ生成 TL;DR 暇つぶしに書いたタロットカードアプリで踏んだバグの話。 PostgreSQLの仕様の読み間違えによるバグが思ったより分かりづらかったのでその流れと判明した具体的な原因、そしてそれらにまつわる検証内容を記事にする。 アプリはdocker compose環境下で動くようになっている。 時系列 タロットカードの結果が4回連続逆位置・・・気の所為か タロットカードアプリを書いた。コード自体はほぼCodexで生成した。細部の実装までは見てない。 実際に使ってみて4日、逆方向が妙に続いている。 確率としては 1/2^4 = 1/16 = 0.0625(6.25%) 約6%程度の可能性があるので、まあ・・・気の所為レベルではある。 とはいえ、少し気になったのでCodexに「逆位置が妙に続いているので実装に問題ないか調べてほしい」と指示→結論だけいえば「問題ありません」というニュアンスの解答が戻ってきたので安心(?)して閉じた。 5回目も逆方向 でも実装に問題ないと言われてるし、偶然か 6回目も逆方向は流石におかしい というわけで再度Codexに調査を依頼。 とはいえ、抽象的な指示だと同じような結果になることはわかったので 実際にバックアップを取得した上で、実際に無限にタロットを引けるようにして検証コード作ってもらって検証して調査するように指示した。 結果、以下のSQLに原因があった。 SELECT id, number, name, upright_meaning, reversed_meaning, random() < 0.5 AS is_reversed FROM tarot_cards ORDER BY random() LIMIT 1; このSQLには二箇所random()が存在するが、 PostgreSQLで実行するとなんとrandom()の結果が使い回されてしまうようだ。 今回の実装では大アルカナのみなのでカードは22枚用意した。 ORDER BY random() LIMIT 1は、乱数が最も小さいカードを選ぶため22個の乱数から最小値を選んだ場合、その値が0.5以上になるのは"22個すべてが0.5以上"の場合だけであるため 確率は (1/2)^22 = 1 / 4,194,304 となる。 つまり元の実装では 逆位置:約99.999976% 正位置:約0.000024% となる。 そりゃ全部逆になるわけだ。 修正SQL WITH picked_card AS MATERIALIZED ( SELECT id, number, name, upright_meaning, reversed_meaning FROM tarot_cards ORDER BY random() LIMIT 1 ), picked AS MATERIALIZED ( SELECT picked_card.*, random() < 0.5 AS is_reversed FROM picked_card ) SELECT id, number, name, CASE WHEN is_reversed THEN 'reversed' ELSE 'upright' END, CASE WHEN is_reversed THEN reversed_meaning ELSE upright_meaning END FROM picked; 素直にWITHで区切ったところ、期待通りにタロットカードの位置がバラついた。 ...

2026年9月6日 · 2 min

日記 EatからVtermへ出戻りした話とかeglot(rust)動かない問題とか

TL;DR 以前vtermのC-kまわりの挙動を追いかけていて、その流れでEatへ移行していた 今回もう一度vtermの素の設定を試したところ、C-kは特別な回避策なしで普通に動いた Eatは実運用してみると自分のワークフローと噛み合わない部分がいくつかあり、結局vtermに出戻りすることにした 副次的にEglotでRustのサーバーが起動しない問題も出ていたので、原因と対処もついでにメモしておく 経緯 もともとvtermのC-kバインドを追いかけていたらEatに移行することになった話(wasutech, 2026-07-04)でvtermからEatへ移行していた。この記事は逆に、そのEatからvtermへ戻した記録になる。 ちなみに、上の記事内でEat移行のきっかけとしてリンクされていたのが、Ki_chi氏のEmacsのターミナルエミュレーターをvtermからEatに移行しました(Ki_chi@Blog, 2026-01-29)。 削除した設定 Eatのuse-packageブロック一式を削除した。内容としては、straight経由でのcodebergリポジトリ指定、eat-semi-char-non-bound-keysの除外リスト編集、eat-reload呼び出しなど。 復元したvterm設定 元記事内で「移行前の設定」として紹介されていたvterm用のuse-packageブロックをベースに復元した。 (defun my/vterm-send-C-k () "Send C-k to vterm terminal." (interactive) (let ((inhibit-read-only t)) (vterm-send-key "k" nil nil t))) (use-package vterm :straight t :bind (:map vterm-mode-map ("C-h" . vterm--self-insert) ("C-k" . my/vterm-send-C-k)) :bind (("C-c v" . vterm)) :config (setq vterm-term-environment-variable "xterm-256color")) defunをuse-packageより前に置いているのは、Emacs Lispが上から評価される言語だから。先に定義しておかないと、use-packageの:bindが参照する段階で未定義エラーになる。 my/vterm-send-C-k自体は、元記事の著者本人も根本原因は特定できなかったと明記している回避策で、いわば対症療法である。 実際に設定を戻してみたところ、C-kは回避策なしの素の状態で普通に動いた。以前この問題を追いかけていた時点との違い(Emacsのバージョン、vtermのバージョン、あるいは単に環境差)は特定できていないので、なぜ動くようになったのかは正直わかっていない。ただ動いている以上、今のところ回避策は入れずに使っている。 別件: eglot(rust-analyzer)が起動しない vterm/eatの話とは別件だが、ちょうど同じタイミングでEglotのRustサーバー起動に失敗する問題が出た。 原因は単純で、rustcとrust-analyzerはrustupのコンポーネントとして別扱いになっており、公式toolchainをインストールしただけではrust-analyzerは入らない。個別にインストールする必要がある。 rustup component add rust-analyzer これで解決した。 出戻りマップ 項目 Eat Vterm 未Git管理ディレクトリを開いたとき ディレクトリを聞かれてモヤっとする 特に気にならない レイアウト 崩れることがしばしばあった 安定している 実装 Emacs Lispのみで完結(要確認) libvterm(C)に依存 C-k 今回は未検証 素の設定で動作した 所感 Eatは実装がEmacs Lisp単体で完結しているというのは魅力的で、パッケージとしての完成度も高いと思う。とはいえ、自分の使い方だと未Git管理のディレクトリを開こうとしたときにディレクトリを聞かれたり、レイアウトがよく崩れたりすることがしばしばあり、実際に使ってみるとかえって煩わしさの方が勝ってしまった。Emacsで完結するツールとしては素晴らしいパッケージだと思うが、結果的には私にはvtermの方が合っていたので、恥ずかしながら出戻りすることになった。とはいえ、また別のターミナルエミュレータを試すかもしれないので、結論は一旦保留にしておく。今はvtermを使う。 ...

2026年8月26日 · 1 min

Androidアプリ開発の極意 4,5章ユーザー体験と非同期処理

前の記事同様に Androidアプリ開発の極意 | 技術評論社の「ユーザーストレス軽減」まわりの実装Tipsと、並行処理の選択肢をざっくり整理してExpo周りと絡めたもの。細かい実装には踏み込まず、要点だけ。 はじめに アプリが固まる原因の大半は、メインスレッド(UIスレッド)を長時間ブロックしていること。非同期処理の設計は結局「ユーザーを待たせない」という一点のためにある。 二部構成で一部は本を読んだ感想+α、二部がExpoだとどうなるかって話。 基本的にAIで出して、それを読んで調べたりしたもの。 第1部:Androidの話 Android(Java/Kotlin)側だけで完結する話。UXまわりの実装Tipsと、非同期処理の手段がsynchronizedからVirtual Threadsまでどう変遷してきたかを、Expoの話を挟まずに整理する。 UXまわりの小さな判断 時間のかかる処理にはProgressBarで進捗を見せる SharedPreferencesは永続化のためディスクI/Oが発生する セルラー通信時は重い処理の前に一言断りを入れると親切 エラーはその場で表示しつつ、Crashlytics/ACRAのような開発者向けツールで後から追える形が理想 NDKは高速化と引き換えにデバッグの複雑さが増す。基本的には奥の手 非同期処理はなぜ増え続けているのか synchronizedは「同時に入らない」ことしか保証しない。順番までは保証しない。 Threadを直接生成した場合も同様で、生成順と実行順は一致しない。順番を保証したいならExecutors.newSingleThreadExecutor()のような直列化の仕組みを使う。 ExecutorServiceは、スレッドを直接管理せず、プールとキューで管理する仕組み。スレッド数の制御や再利用をアプリ側のロジックから切り離せる。 AsyncTaskはAPI 30で非推奨になった。ライフサイクル管理が弱く、Activity/Fragment破棄後もタスクが動き続けてリークやクラッシュを招くのが理由。 今の主流はKotlin Coroutines。スレッドをブロックせず中断できる軽量な並行処理で、排他制御が必要ならMutexを使う。UIに紐づく短い処理はCoroutines、画面を離れても続けるべき処理はWorkManagerと使い分ける。 JVM側ではJDK 21でVirtual Threads(JEP 444)が正式機能になった。I/Oバウンドな処理を大量にさばくのに向くが、CPUバウンドには向かない。ただしこれはJDK/Java SE側の機能で、Android Runtime(ART)は非対応。Androidアプリでそのまま使えるわけではない。 Akkaはアクター単位でメッセージパッシングする設計思想。2022年にライセンスがApache 2.0からBSL(Business Source License)に変わり、2024年10月以降はproduction利用にライセンスキーが必須になった。個人・OSS・startupは無料キー、商用は原則サブスクという体系。汎用ツールというより大規模分散システム向けの専門ツールという位置づけに変わっている。 比較表(Android) 手段 排他制御 順序保証 位置づけ synchronized あり なし 低レベルの排他制御プリミティブ Thread直接生成 なし なし 基本的には避ける ExecutorService 設計次第 直列化すれば可 CPUバウンド処理で有効 AsyncTask なし なし API 30で非推奨 Kotlin Coroutines Mutexで対応 設計次第 Android/Kotlinの主流 Virtual Threads 従来通り 設計次第 JVM側、I/Oバウンド向け(ART非対応) Akka アクターモデル 条件付き 大規模分散システム向け 「排他制御」と「順序保証」は別物。ここを混同すると設計は破綻する。AsyncTaskはAPI 30で非推奨、今はCoroutines(短命処理)とWorkManager(永続処理)を使い分けるのが主流。Virtual Threads(JDK 21)はJVM側の話でARTは非対応。 ...

2026年8月18日 · 1 min

Androidアプリ開発の極意 2,3章あたり ANR、UIスレッド

Androidアプリ開発の極意 | 技術評論社 この本読んでる。2017年に出版してて、内容には古いものがいくつかある。 そこはAIの使いどころ。とりあえず学習したことを軽くメモして、中身について古い場合は指摘を入れたり、今は何が主流なのかといったことを補完するような指示を出した。 サポート付きの読書っぽいことをしながら読み進めて、文章をまとめた。 概要 React Native/Expoでアプリを書いていると、「なぜかカクつくが、クラッシュログには何も残っていない」という現象に何度もぶつかる。 原因を追っていくと、大体はUIスレッドを塞いでいるか、メモリを食い潰しているかのどちらかという同じ根っこに行き着くことが多い気がする。 この記事では、Androidアプリ開発の極意 | 技術評論社の3章UIスレッド/ANRの基礎から、React NativeにおけるJS-ネイティブのスレッド分離、コルーチンとasync/awaitの正体、Android/iOS/RNの非同期処理対応、そして見落とされがちなメモリ管理(OOM・LMK・Javaのメモリリーク・useEffectのクリーンアップ)までを、実務で踏んだり踏みそうな地雷も交えて整理した。 なお、私は仕事でしばしReact Native Expoを利用しているため、なるべくそちらに沿った内容に寄せて本文を作成する。 それによって本の内容とは離れることがしばしあるが、そこは予め留意してほしい。 全体像 まずスレッドの分離構造をざっくり図にする。 [Android/ネイティブ] UIスレッド(メインスレッド) ── 描画・入力処理を担当 │ │ 5秒ブロック → ANRダイアログ ▼ ワーカースレッド/コルーチン(Dispatchers.IO等) ── 重い処理はここへ逃がす [React Native/Expo] JSスレッド ── ビジネスロジック・状態更新・レンダリング計算 │ │ (ブリッジ/JSI経由) ▼ ネイティブUIスレッド ── 実際の描画 │ (任意) Worklet用の別JSコンテキスト ── react-native-worklets-core / Reanimated ポイントは、Androidのネイティブ層には「ANR」という可視化された強制終了の仕組みがあるが、React NativeのJSスレッドが詰まっても同じ仕組みは働かないという非対称性にある。以下、順番に見ていく。 UIスレッド/ANRの基礎 Androidでは、UIスレッド(メインスレッド)が入力イベントに応答できない状態が一定時間続くと、システムが強制的にANR(Application Not Responding)ダイアログを出す。Android公式ドキュメントによれば、入力イベント(キー押下や画面タップなど)に5秒以内に応答しない場合にANRがトリガーされる。これはフォアグラウンドの入力ディスパッチに関する条件で、他にもブロードキャストレシーバーやサービスの応答遅延など複数のANR条件が存在する。 一方、React Native/Expoでは事情が異なる。RNのアーキテクチャはJSスレッドとネイティブUIスレッドが分離されている。JSスレッドが重い処理で詰まっても、ネイティブ側のUIスレッド自体がブロックされているわけではないため、Android OSが検知する「入力ディスパッチのタイムアウト」という意味でのANRダイアログは出にくい。ただし、体感としては「タップしても反応がない」「アニメーションがカクつく」という、ユーザーから見ればANRと区別のつかない現象が起きる。ANRダイアログが出ないぶん、むしろ発見が遅れて厄介とも言える。 補足: 「RNならANRが原理的に起きない」という理解は不正確。JSスレッドが完全にブロックされている間、そのJSスレッドに紐づくタッチイベント処理やRedux的な状態更新も止まるため、ユーザー体験としては同種の「固まり」が発生する。OS側の検知メカニズムが働かないだけで、問題自体は消えていない。 重い処理の逃がし方:InteractionManager(旧API)とWorkletsは別物 RN側で「重い処理をUI/JSスレッドから逃がす」ための手段として、従来は InteractionManager がよく使われていたが、2026年8月リリースのReact Native 0.87で InteractionManager は削除済みであり、公式は代替として requestIdleCallback を案内している。0.82〜0.86の時点では非推奨警告が出るようになっていたため、既存プロジェクトでこの警告を見た人も多いはずだ。以降は「過去にどう動いていたか」の説明として読んでほしい。 ...

2026年8月16日 · 2 min

自宅NW活動誌:固定IP撤去とdnsmasq移行で名前解決を建て直した話

TL;DR 電源断からの復旧で名前解決がおかしくなり、調べていったら「dnsmasqがhosts変更を未反映」「gw-a/gw-bがローカル固定IPとdns01のDNS管理で二重管理になっていた」の2つが絡んでいたという話。ついでに「自ホスト名pingが127.0.1.1を返す」件は仕様通りで実害なしと確認した。全部片付いたので、今回は「詰んだ」話ではなく「ハマったけど直した」話。 環境 セグメント: home.local(192.168.20.0/24) DNS/DHCP: dns01(dnsmasq) Keepalived構成のGW: gw-a, gw-b クライアント側ネットワーク管理: nmcli 発生した問題 電源断→起動後、あるノード(client01)から複数ノードにSSHできなくなった。調査を進める過程で、そもそもdns01自身の名前解決(dns01という名前そのもの)が引けなくなっていることが判明。/etc/hostsに手動でエントリを足しても、他ノードから引けない状態が続いた。 原因は1つではなく、「gw-a/gw-bがローカル固定IP設定+hosts記載を持っていたこと」と「dns01側のhostsエントリ消失」が絡み合っていた。 対応1:dnsmasqがhosts変更を反映していなかった 症状: /etc/hostsにエントリを書いても、他ノードからdnsmasq経由で引けない。 原因: dnsmasqは/etc/hostsを起動時に読み込むだけで、実行中のファイル変更を自動では拾わない。dhcp-hostsfileやdhcp-optsfileなどのDHCP関連設定ファイルは変更時にSIGHUPシグナルを送ることでdnsmasqに再読み込みさせられる仕様になっており、/etc/hostsについても同様に、SIGHUPを受け取るとキャッシュをクリアした上で再読み込みする挙動になっている。つまり「編集しただけ」では反映されず、シグナルを送る操作が別途必要というのがハマりどころだった。 対処: systemctl restart dnsmasq(reload = SIGHUP相当)で即解決。 学び: /etc/hostsをdnsmasq向けに編集したら、反射的に再起動する習慣をつける。 対応2:gw-a/gw-bを固定IP運用からdhcp-host予約運用に切り替え 目的: ローカル固定IP(dhcpcd.conf/nmcli static)とdns01のDNS管理が二重管理になっていたのを解消し、IP管理をdns01(dnsmasq)側に一元化する。 手順(実施順が重要) 1. dns01側を先に設定 /etc/dnsmasq.conf(またはdhcphostsファイル)に予約を追記する。 dhcp-host=<MACアドレス>,192.168.20.3 # gw-a dhcp-host=<MACアドレス>,192.168.20.4 # gw-b MACアドレスの大文字/小文字はdnsmasqでは区別されない(AA:BB:...でもaa:bb:...でも可)。区切り文字は:固定。設定後はsystemctl restart dnsmasq。 2. クライアント側(gw-a/gw-b)を動的取得に変更 nmcli管理の場合は以下の通り。 nmcli connection show # 対象接続名を確認 nmcli connection modify "<接続名>" ipv4.method auto nmcli connection modify "<接続名>" ipv4.addresses "" nmcli connection modify "<接続名>" ipv4.gateway "" nmcli connection modify "<接続名>" ipv4.dns "" nmcli connection up "<接続名>" 確実に反映させるため最終的にreboot。 ...

2026年7月30日 · 1 min

Terraform学習1:variable/outputが分離しているのは何のためか

Terraformを触り始めると、variableとoutputが真逆の役割を持っていることはすぐわかる。だが「なぜこの2つが分離しているのか」「なぜvalidationが型チェックと別枠なのか」まで理解しないと、秘匿情報をうっかりstateに残す事故につながる。今回は自分のTerraform構成(variables.tf, outputs.tf, versions.tfのbackend部分)を題材に、設計意図を整理する。 variableは「設定とロジックの分離」のための入口 variableは、値をリソース定義本体に直書きせず、外部から注入するための仕組みだ。値を変えるたびにリソース定義そのものを編集しなくて済むようにする、という目的がある。 値の入力元は主に4つある。 default(ブロック内で指定) terraform.tfvars 環境変数(TF_VAR_xxx) -varオプション(CLI実行時) defaultの有無で意味が変わる 意味 defaultあり 共通・変更頻度が低い値。省略可能(例: aws_region, project_name) defaultなし 環境固有・必須の値。指定しないとplan/apply時にエラー(例: budget_alert_email, github_org) defaultの有無は「省略可能かどうか」の表明であり、そのままその変数の性格(共通設定か、環境固有の必須値か)を表している。 validationは型チェックとは別レイヤーの検証 variableのtypeは形式のチェックしかしない。type = stringは「文字列であること」しか保証せず、「その文字列が正しい値かどうか」は見ない。 そこを埋めるのがvalidationブロックだ。値の中身・意味をチェックする。例えばSSMパラメータ名は/始まりである必要がある、EBSルートボリュームは8GiB以上必要、といった業務ルールをここに書く。 variable "image_id" { type = string description = "The ID of the machine image (AMI) to use for the server." validation { condition = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-" error_message = "The image_id value must be a valid AMI ID, starting with \"ami-\"." } } validationはapply前、ローカルのplan段階でエラーを弾ける。無駄なapply実行を未然に防げるのが最大の利点で、クラウド側にリクエストが飛ぶ前に「その値はそもそもおかしい」と教えてくれる。 ...

2026年7月25日 · 2 min