ソフトウェアエンジニアリングチームはオーケストラか

専門的な技能を持った個人の集団がコミュニケーションを取りながら成果物を完成させるという点で、ソフトウェアエンジニアリングチームはオーケストラに似ている。長い期間に渡って同じシステムを育て続けるチームを想定している。

似ていること

  • 一人ひとりの能力の総和が単純にチームの能力にならない。良いコミュニケーションと良い文化・伝統が必要。
    • 一人のスーパースターの存在は、ある程度成果物の質に効いてくるが、決定的ではない。トランペットだけ超うまいオーケストラはトランペットパートで見せ場を作れはするが、それが他の弱点を覆い隠せるわけではない。スーパーエンジニアはアーキテクチャを設計できるが、同時にチームを教育し浸透させないと少しずつアーキテクチャの意図からズレたコピペが横行し、破綻する。
    • 技術的なボトルネックの解決とかは一人で大きく貢献できるかもしれない

違うこと

  • ソースコードは有形で永続化されている。いつ誰がどのような判断でその一行を書いたのか、高い確率で検証することができる。
    • オーケストラも楽譜に歴史が刻まれているとは言える。オーケストラは楽譜を所有しており、過去にやった曲であれば当時の書き込みが残った状態で使う。指揮者が変わって演奏指示が変わったりした場合は書き込みが更新される。しかしgitのように履歴を追跡できるわけではない。
  • コードを全く触らなくてもある程度の期間は動かし続けることができる
  • 新人をカバーし教育をすることができる。オーケストラでは一人音程が違えばバレてしまうが、ソフトウェアエンジニアリングではプルリクエストとレビューを通してカバーすることができる。
    • リアルタイム性の違い
    • しかし負担は大きい。『人月の神話』で論じられているように、新人の教育役として作業から離脱する人間が出るので人員追加の採算が取れるまでの時間が長い。

特にここから何かを主張したいというわけではない。それほど考えを深めているわけではない。しかしオーケストラの組織運営は何百年という歴史の中で洗練されてきたので、ソフトウェアエンジニアリングチームもそこからなにか学ぶことが、できたりできなかったりするかもしれない?

  • 全体の方向性を指示し、ときにはマイクロマネジメントを行う指揮者(ただしそれらの指示は全て全団員が聴いている)
    • 桜井政博氏も組織論の動画でディレクターの指示は誰でも自由に聞ける場でやるって言ってた
  • 全体に対してより具体性の高い指示を出すコンサートマスター
  • 同一の演奏機能を有する奏者が1つのパートを構成する(レイヤードアーキテクチャ+コンウェイの法則?)が、その中でも1st, 2ndのような役割分担がある
  • パート内ではパートリーダーに合わせる
  • パートリーダーは必要に応じて他パートと調整を行う

こう、風呂で思いついたときにアイデアとしては面白いと思うんだけど、文章に纏めてみると大した内容じゃないなってやつ、あるある。

Ubuntu23.10に上げたときの作業記録

私物のUbuntuは定期的に(だいたい年に2,3回)OSの再インストールをしている。このくらいの頻度でやっていると再インストール後の作業にも慣れてくる。手元にメモはあるが、一応インターネットにも載せておこう。

前提

  • インストールするのは純正のUbuntu
    • 日本語Remixではない
    • 普段はLTSしか使わないが今回はpipewireを今すぐ使いたかったので特別に23.10
    • 経験的にLTSとそれ以外の安定性にはかなりの差があるので、よほどの理由がない限りはLTSを使うべき
  • ルートはNVMe SSD
  • ホームディレクトリ下の以下はHDD上の同名のディレクトリへのシンボリックリンクにしてある
    • Documents
    • Downloads
    • Music
    • Pictures
    • Public
    • Templates
    • Videos
    • workspace
      • 個人開発のコードが置いてある
    • .ssh
  • なので、持ち越したいデータは全部HDDに置いているのでNVMe SSDはUbuntu Installerで全消ししている

再インストール作業

  • USBメモリにISOを焼いてUEFIの起動メニューからそちらを起動
  • Default Installation(全部入りではないほう)
    • サードパーティソフトウェアは入れる
    • メディアフォーマットサポートも入れる
  • NVMe SSDを完全に上書きする設定
    • LVM, ZFS, 暗号化はしない
  • パスワードはpasswordとしておいて後で変更する
    • インストールウィザード中に記号を含んだ複雑なパスワードを設定すると、なぜか正確に記録されず、後で同じパスワードを入力してもログインできない

セットアップ作業

  • ディスプレイの並びを物理空間と一致させる
  • 4KディスプレイをWQHDに設定しなおす
  • アクセシビリティから「大きな文字」を有効化、カーソルを最大に
  • ホームディレクトリ上のディレクトリ名を英語にする
    LANG=C xdg-user-dirs-gtk-update
  • 「ディスク」からHDDを ~/storage にマウントする
  • 前述のホームディレクトリ上のディレクトリをstorage下へのシンボリックリンクにする
    ln -s ~/storage/Documents/ ~/Documents
  • firefoxからvivaldiを入れる
  • vivaldiの拡張機能としてbitwardenを入れる
  • vivaldiの設定をsync機能で復旧する
    • syncにはID/パスワードとは別に復号パスワードも必要なのでそれもbitwardeに覚えさせておく
  • フォントをtakao系に戻す
    sudo apt install 'fonts-takao-*'
  • vscode入れる
  • steam入れる
  • 日本語変換をできるようにする
    sudo apt install fcitx5-mozc
    im-config # fcitx5を選択
    fcitx5-configtool # 変換・無変換をそれぞれ入力メソッドオン・オフに割り当てる
    再起動
  • nerdctl入れる
  • vim入れる
  • git入れる
    • ユーザー名・メールアドレス・デフォルトエディタ
  • thunderbird入れる
    • メールアカウントの登録。YahooはIMAPとPOPを選べるがIMAP

あとは必要になってから

  • 各種プログラミング言語処理系

Q. 今回はなんでUbuntu23.10にしたの?

A. https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux にpipewireが必須だったから(そして22.04のpulseaudioを止めてpipewireを入れたらなぜか日本語変換が壊れるという大事故が起きたから)

実際にやってみたところ音質が悪かったのでこれを使うのはやめたが、pipewireに移行したことによってbluetoothヘッドホンの扱いがよくなり、これまで何故か使えなかったHSP接続が可能になり完全に死に機能だったヘッドホンのマイクが使えるようになった。まあこれも音質悪いからあまり使う気ないけど…

あとはPC内での音声入出力の繋がり方が、pavucontrolでは見づらかったが、 https://github.com/Ax9D/pw-viz を使って視覚化できるようになった。

Q. LTSじゃないけど大丈夫?

OBSのソースに画像ファイルを選択するとき、ファイル選択ダイアログを開くと非常に重くなる。なんで?

AWS SAAを取った

合格。853点なのである程度余裕を持っての合格と言えそう。

インプット

AWSの知識はゼロスタートではなかった。使った教材はudemyのこれ。先に取得していた友人に勧められたので。

https://www.udemy.com/course/aws-associate/

1ヶ月前から1日あたり70分相当くらい見ていった。ハンズオンは全部見るだけにした。教材の質は結構良いと思う。日本語がおかしいとか、AWSのドキュメントのコピペだとか、重要な単語をずっと言い間違えてるとかそういうミスはある。でもそもそもAWSのドキュメントの日本語訳は読みにくいので、それを理解可能な日本語で整理してくれている時点でかなりありがたい。力作。

ハンズオンの割合が多いのだが、実際に画面を見ることで概念間の関係の理解が深まるということも多々あったので、試験対策として意義はあると思う(効率はまた別の話だが)し、それ以上に実務にも役立ちそう。

試験対策

練習問題としては、公式問題集の20問、なんかネットに落ちてるサンプル公式問題10問、udemyの各章の小テスト、udemyの模擬試験1(簡単)、udemyの模擬試験2,3(そこそこ)を解いた。全ての問題について、全ての選択肢がなぜ正解/不正解なのか復習した。

公式問題の20問

スキルビルダーの中でできるやつ。本番より少し簡単だった印象。最初にこれをやったので「重箱の隅をつつくような問題はでないんだな」と油断した。

udemyの各章の小テスト

重箱の隅をつつくような悪問だと思っていたが、実際このレベルの知識がないと解けない問題もある(捨てても合格はできるかもしれない)。

udemyの模擬試験

1は簡単。2はそこそこ簡単に思えたが70%で合格ライン以下だった。2を復習した後の3は難しく思えたが86%で合格圏内だった。問題の形式に慣れることが結構点数に影響するのだなと思った。詳細は以下。終わってみればどの模擬試験も本番より少し簡単だった。

たぶん問題はudemy側もAWS側も随時入れ替わるので、未来にこれを読む人はあまり信用しないでください。

非本質的な試験戦略

AWSの知識は当然ベースとして大事なのだが、4つの選択肢の中から間違っているものを3つ落とせればいいのだから、選択肢間の同じ部分と違う部分を見つけ出すという戦略に慣れることが必要だった。長い選択肢は一見すると難しそうに見えるが、他の選択肢と同じ部分は読む必要がない。

本番

思ったよりも幅広いサービスの、細かいところまで問われていて焦った。しかし冷静に選択肢を読んでみると必ずしも完全な知識がなくても解ける/絞り込める問題も多かったので、焦ってはいけない。点数に含まれない調整中の問題も含まれると知っていたので、難しい問題に対しては「これは採点対象外だろ〜」と思い込むことにした(あと各問の点数も難易度に応じて調整されるらしい)。

自信がない問題にはチェックを付けながら進め、65問解き終わったときに残り30分くらい。そこからチェック付きの問題20問を一通り確認して残り10分、後は最初から順に見直して真ん中あたりでタイムアップ。試験終了時まで自信がなかった問題は12問だった。

英語の試験問題の日本語訳は概ね問題ないのだが、英語を見ないと間違える問題や、英語を見ることで自分が日本語を誤読していたことに気づいた問題もあった。怪しい問題については英語に切り替えて確認するのも有効だと思う。日本語訳を見てからなら英語問題を読むのは難しくない。

あと、これは友人にも言われていたが、フォントが汚い。

試験のあれこれ

自宅の環境を整えるのが面倒だったので試験センターを予約した。早めに着いたら早めに受けられたが、逆に試験センターで勉強しながら待つということはできなかった。

試験が終わったのは16時頃で、その日の18時頃には結果が出た。思っていたよりも早かった。嬉しかったので飲酒して帰った。

ヴァイオリンで小指を傷めた

今日も土曜日のルーティンを完遂でき、気分が良い。

ゴミ捨て

いつものスポットに捨てた。

水泳

いつも通り2ビートクロールで1km。SWOLFは70。少し効率的な泳ぎができるようになってきた。大昔は自分のへそを見るように頭を入れて泳げと習ったが、今日は真下を向いて泳いでみた。2ビートクロールの場合少し体をロールさせながらの方が推進力が入りやすい気がする。

インドカレー

なんとなくチーズナンが食べたい気分だったのでインドカレー屋に行った。セットメニューのチーズナン変更は差額ではなく満額必要と言われたが、食べたいものは食べたいので払って食べた。冷静にチーズナンじゃなくて良かったなと思った。

ヴァイオリン

カラオケボックスで1時間ヴァイオリンを弾いた。1時間弾いてようやく鳴りも音程も準備が整ったなという感じなので、本当は1日2~3時間は弾いてないと上達はしないんだろうなあ。

屋内で弾くと弓が吸い付くような感覚がある。これは逆に普段屋外で弾くときに風に煽られて弓が暴れる感覚に慣れてしまっているからだろう。そうなると弓をコントロールするために力が必要になり、脱力した正しい弾き方ができなくなってしまうのだが、仕方がない。「悪い癖がつくから弾かない」というわけにはいかない。

数日前はヴァイオリンで小指を痛めていた。フォームを変更してから1の指の使い方はかなりスムーズになったのだが、それと引き換えに小指が届きにくくなり無理な伸ばし方が必要になっている。親指の位置を調整することでもうちょっと良くなるかな。

買い物

卵とバナナを買った。卵は6個入りを買ってゆで卵にしている。食べやすくてタンパク質の補給になる。バナナはなんか今日売ってるのはどれも小さかった。

口座の整理

資産は2つの銀行口座と投資信託に分けているのだが、その移動をサボっていたのでやった。

ふるさと納税

例年年末に慌てるので今年は早めに。例年全額出身地だったが、今年は福島が大変そうなので少しそちらにも寄付してみた。寄付先の自治体が増えると手続きが面倒になる。いやそもそもこの制度自体が面倒を引き受けて小金を稼ぐしょうもない制度なのだが…

最近見た動画

モーツァルト『協奏交響曲』

ヴァイオリンとヴィオラがソロパートを弾く珍しい形の協奏曲。多くの場合ヴァイオリンの方が音が高く目立ってしまうのだが、この演奏ではヴィオラソロのティモシー・リダウトがモーツァルトにしては激しい演奏を繰り出し、ヴァイオリンに負けない存在感を放っている。たぶん録音もいい仕事をしているのだろう。彼の楽器は1570年頃に作られたそうで、今日高く評価されているストラディヴァリウスが1710年ごろであることを考えると、それよりも100年以上古い。古いということは技術もまだ進んでいなかったということで、それでも現在これだけ鳴るというのはすごいことだと思う。

『世界の果てに、ひろゆき置いてきた』

YouTubeの切り抜きしか見てないのだが、これはだいぶ面白い。普段の彼のネットでの振る舞いは嫌いなのだが、異国でのトラブルにも動じず飄々としている彼を見るのは軽快な編集と相まって痛快だ。やはりインターネットというのは悪なのだなと気付かされる。

そう考えてみると、普段のネット上でのひろゆきを称賛している人々には、彼がふっかける喧嘩がアフリカで出会うトラブルと同じように見えているのかもしれない。

最近読んでいる本

『プログラミング言語の基礎概念』は読み終わったことにした。証明とか、最後の方のクロージャの概念は完全には理解していないのだが、これ以上粘っても得るものがなさそうだった。

次はAWSの資格の勉強をする。Associateチャレンジキャンペーンで10月中の受験まで半額になる。

https://pages.awscloud.com/jp-traincert-certification-challenge-associate-2023-reg.html

Dota2

最近びっくりするくらい負け続けている。7.32の勝率が49%だったのに対して、7.33では41%、7.34では36%。レーティング制の5vs5のゲームで勝率36%ってすごくないですか?

自分のプレイが現在のバージョンの何かと噛み合ってないのだろうが、自分にはそれがわからない。外界とのインタラクションを意識しながら自分の行動を変えるのは苦手だ。さらにDota2ほど複雑なゲームになるとどの部分が悪かったのかわからないので困ってしまう。

Raspberry Pi Zero WHでNASしたい、失敗録1

配線

私はRaspberry Pi Zero WHを所有していて、これまで自宅の室温を記録してサーバーに送信していた。突然NASを組みたくなり、これを利用できないかと考えた。

ざっくりラズパイにストレージが接続さえできればあとはどうにでもなりそうだったので、アダプター類を購入して準備した。具体的には、Zero WHはUSB micro-B凹端子しかないので、ここから

  • USB micro-B凸 to USB A凹 変換
  • USB A凸 to USB A凹 ハブ(給電機能つき)
  • USB A凸 to SATA

の3段階を経てタンスに打ち捨てられていたHDDに接続。つないでlsblkするだけで認識できた。

HDDの準備

母艦側PCでHDDの中身を適当にサルベージしたあと、フォーマットしてLUKS暗号化しつつBtrfsでパーティションを作成。このあたりは全部UbuntuのdiskユーティリティでGUI操作可能だった。ただしGUI上でBtrfsを選択するためにはaptでbtrfs-progsを入れておく必要がある。

Raspberry Piのソフトウェアレイヤーの準備

よくあるチュートリアルのようにOpenMediaVaultを入れようとしたのだが、そのためにはRaspberry PiのOSがGUIをサポートしないバージョンでなければならない。そのために面倒な思いをしてOSを入れ直した。しかし改めてOMVを入れようとしたが、一般的なインストールスクリプトはRaspberry Pi Zeroには対応してないらしい。

RPi revision code :: 9000c1
This RPi1 is not supported (not true armhf).  Exiting...

仕方なくsambaに方針転換してインストールしてみた。

HDDをマウントする

LUKSで暗号化されたストレージは先に

sudo cryptsetup luksOpen /dev/sda storage

する必要があるのだが、なんとこれを実行するメモリが足りなかった(そんなことある!?)

Not enough available memory to open a keyslot.

ので、HDDを再フォーマットしてチャレンジします…

Next.jsのReact EssentialsのServer Componentsの説明をオレオレ翻訳しながら読む

これの話

https://nextjs.org/docs/getting-started/react-essentials#server-components

正確さではなく(それならAIでいいよね)、自分の理解に引き寄せて翻訳してみる。

Server Components

Server and Client Components があるとサーバーとクライアントの両側にまたがるアプリケーションを作れます。クライアントサイドのリッチなインタラクティビティと伝統的なサーバーレンダリングのパフォーマンスを組み合わせることができます。

Thinking in Server Components

ReactはUI設計の考え方を変えました。同じようにReact Server Componentsは、サーバーとクライアントの両方を活かしたハイブリッドアプリケーションを作るための新しい考え方です。

Reactはこれまで全てをクライアントサイドでレンダリングしていました(SPAというやつです)。しかし、Server Componentsがあると目的に応じてどこでレンダリングするかを決められます。

たとえば、アプリケーションのpageを考えます。

ページをコンポーネントに分割すると、実は大多数のコンポーネントはインタラクティブではないです。つまりサーバーサイドでServer Componentとしてレンダリングできます。そしてその中にインタラクティブなクライアントコンポーネントを点在させるのです。これはNext.jsのサーバーファーストなアプローチと相性がいいです。

Why Server Components?

Server Componentsのメリットは何?という疑問が浮かぶでしょう。

Server Componentsはサーバーのインフラを活用しやすいです。たとえば、巨大な依存パッケージをクライアントに送信する必要がありません。こうなるとReactはPHPやRoRのよう(テンプレートエンジンのように?)に扱えます。

Server Componentsは初期ページ読み込みが早いです。バンドルサイズが小さくなります。根幹部分のクライアント側ランタイムはキャッシュ可能かつサイズの予測が可能で、アプリケーションが成長しても増えません。追加されるJavaScriptは、Client Componentsが使われたときだけ増えます(この辺あんまりわかってない)。

Next.jsでrouteが読み込まれたとき、初期HTMLがサーバーでレンダリングされます。このHTMLはブラウザで段階的に成長し、クライアントがアプリケーションを引き継ぎ、インタラクティビティが付加されます。このためのランタイムの読み込みは非同期的です。

Server Componentsに楽に移行できるように、App Router内のコンポーネントは全てデフォルトでServer Componentsにします。special filesやcolocated componentsも同様です。だからあなたは何もしなくてもServer Componentsを採用して優れたパフォーマンスを得られます。ここに use client directiveを使うことでClient Componentsをオプトインできます。

Client Components

Client Componentsを使うとクライアント側でのインタラクティビティを付加できます。これはNext.jsではサーバーでのpre-renderingとクライアント側でのhydrationで実現しています。Client ComponentsはこれまでのPage Routerと同じ動きです。

The "use client" directive

use client directiveはServer / Client Componentsの境界線を宣言するものです。

'use client';

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

"use client" はサーバーとクライアントのコードの間に配置します。ファイルの一番上、importsよりも上に書きます。"use client" が宣言されると、そのファイルがimportしている全てのモジュール(子コンポーネントなど)はクライアントバンドルの一部と認識されます。

デフォルトはServer Componentsなので、 "use client" 宣言がない限り全てはServer Component moduleに含まれます。

豆知識

  • Server Component moduleに含まれるComponentはサーバーでのみレンダリングされることが保証されます
  • Client Componentはクライアントでレンダリングされるものですが、Next.jsは事前にサーバーでレンダリングすることもあります
  • "use client" はファイルの一番上で宣言しなければなりません
  • "use client" は全てのファイルで宣言する必要はなく、Server Componentsとの境界でだけ宣言すれば十分です(そのファイルがimportしているファイルもクライアントバンドルになるので、"use client" が宣言されたファイルはentry pointとみなすことができます)

おわりに

Server Componentsが何なのか知りたくて調べていたんだけど、なんか概念的な記事しかなくてよくわからなくて、この記事もそうだった。完。でも概念はちょっとわかった。

goでmysqlにDATETIMEを入れるときにgo-mysql-driverとbunでタイムゾーンの扱いが違う

goのTimeは日時とタイムゾーンの情報を持っている。日本標準時のタイムゾーンを持ったTimeを作ってみる。

location, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
    log.Fatal(err)
}
t := time.Date(2019, 1, 2, 3, 4, 5, 0, location) // 2019年1月2日3時4分5秒

これをmysqlに突っ込む。mysqlとのコネクションはこんな感じ。

cfg := mysql.Config{
    User:      "root",
    Passwd:    "password",
    Net:       "tcp",
    Addr:      "127.0.0.1:4306",
    DBName:    "time",
    ParseTime: true,
}

Locは指定していない。その場合UTCになる。

https://github.com/go-sql-driver/mysql/blob/191a7c4c519ef60cf3e8656fde8728eee9194308/dsn.go#L73

// NewConfig creates a new Config and sets default values.
func NewConfig() *Config {
    return &Config{
        Collation:            defaultCollation,
        Loc:                  time.UTC,
        MaxAllowedPacket:     defaultMaxAllowedPacket,
        Logger:               defaultLogger,
        AllowNativePasswords: true,
        CheckConnLiveness:    true,
    }
}

go-mysql-driverで生のSQLを書いてmysqlにINSERTする

db.Exec("INSERT INTO `time` (`id`, `time`) VALUES (?, ?);", "Asia/Tokyo 2019-01-02T03:04:05", t)

このとき発行されるクエリをgeneral_logで確認すると、この段階でUTCの 2019-01-01 18:04:05 に変換されている。

2023-05-06T14:33:27.581647Z     9 Execute   INSERT INTO `time` (`id`, `time`) VALUES ('Asia/Tokyo 2019-01-02T03:04:05', '2019-01-01 18:04:05')

これはgo-mysql-driverがTimeをシリアライズする前にInでタイムゾーンをUTCに変換しているからだ。

https://github.com/go-sql-driver/mysql/blob/191a7c4c519ef60cf3e8656fde8728eee9194308/packets.go#L1119

b, err = appendDateTime(b, v.In(mc.cfg.Loc))

ではbunでTimeをINSERTするとどうなるか

type BTime struct {
    bun.BaseModel `bun:"time2"`
    ID            string
    Time          time.Time
}
bundb.NewInsert().Model(&BTime{ID: "BUN Asia/Tokyo 2019-01-02T03:04:05", Time: t}).Exec(ctx)

このとき発行されるクエリでは、Timeに設定されているタイムゾーンを無視して 2019-01-02 03:04:05 とシリアライズしている。

2023-05-06T14:41:39.343913Z    10 Query INSERT INTO `time2` (`id`, `time`) VALUES ('BUN Asia/Tokyo 2019-01-02T03:04:05', '2019-01-02 03:04:05')

どうやらこれは意図的な変更の結果らしい。mysqlやgo-mysql-driverなどと二重変換してしまって正常に動作しないという問題があったようだ(←わかってない)。

https://github.com/uptrace/bun/issues/168

とにかく、じゃあどうやれば安心してDATETIMEを扱えるんだよという話になるんだけど、mysqlとまたがる範囲で暗黙的な変換が入ると状態の把握が難しくなるので、goのアプリケーション側で確実にUTCにしちゃってからORMなりクエリビルダーなりに放り込むことにした。そうすれば少なくともバグったときにfmt.Printfでなんとかなる。

goとmysqlの間で苦しんでる人はたくさんいた(類似記事が多い)がbunの話してる人は全然いなかったのでテキトーに書いてみた。

おまけ dockerのmysqlでgeneral_logを見る

適当なファイルに以下を書いておいて

[mysqld]
general_log=1

そのファイルが入ったディレクトリを、コンテナの/etc/mysql/conf.dにマウントする

-v /path/to/cnf-dir/:/etc/mysql/conf.d

コンテナ起動後にコンテナに入ってファイルの場所を探してtailする

nerdctl exec -it <container_id> /bin/sh
mysql -u root --host 127.0.0.1 -p
> SHOW VARIABLES LIKE '%general_log%';
> exit;
tail /path/to/general.log

なんで general_log_file 使わないの?

なんか general_log_file=/var/log/mysql/general.log するとmysqlの設定値はそこになるんだけどファイルが作られないんだよね。パーミッションの問題とかあるのかな。

上記の方法でやるとログファイルは /var/lib/mysql/hoge.log に生える。

Ubuntu 23.04への式年遷宮 トラブル録

普段はLTSしか使わないのだが、なんかやりたい気持ちになったので珍しく奇数系を入れてみた。

https://www.ubuntulinux.jp/News/ubuntu2304-ja-remix

をUSBメモリに焼いて(今はUbuntu公式でもEtcher使えって書いてるんですね、昔はUbuntuにUbuntuのイメージを焼くためのソフトウェアが付属していた気がするが)、PC再起動。

インストールの手順については、特に変化はなさそう。今回はLVM使用、ディスク暗号化をつけてみた。

gnome-text-editorで日本語入力できない

毎度の鬼門(と言いつつ、熱心な日本Ubuntuユーザーが結構情報を残してくれているので言うほどではない)。こちらのページが参考になった。

https://www.kkaneko.jp/tools/server/gnome_ja_input_method.html

注意点として、23.04では(正確には22.10から)はgeditに代わってgnome-text-editorというソフトウェアが入っている。こいつがfcitxとの相性が悪いのか、日本語変換が一切効かない(日本語入力モードに入れない)。シェルから開いてみるとこんなエラーメッセージが残されていた。

(gnome-text-editor:9858): Gtk-WARNING **: 03:13:56.134: No IM module matching GTK_IM_MODULE=fcitx found

意味はよくわからないのだが、fcitxを使えと言われているがgnome-text-editorがそれを理解できていないように見える。実際にはgnome-text-editorのリポジトリにこのようなエラーメッセージはなさそうなので、GTK自体に何らかの問題があるのかもしれない。

20230507追記

fcitxじゃなくてfcitx5を入れると問題が起きなかった。

vscodeで日本語入力時に確定するまで文字が表示されない

確定するまでは変換ボックスを見ないと入力中の文字が確認できず、Enterで確定すると一気にエディタ上に文字が追加される状態になった。これはUbuntu Softwareからデフォルトのsnapで入れていたのが悪いので、消してdebで入れ直したら直った。

bluetoothヘッドホンで音が鳴らない・途切れる・汚い

Linuxでbluetoothヘッドホンを使うのはコツがいる。僕が使ってるのはWH-1000XM4ね。

まず音が途切れるのはドライバのバッファリングが下手くそなせいなので、pavucontrol→出力デバイス→WH-1000XM4→高度な設定でLatency offsetを10msくらいにしてやる。ほんの少しでも設定してやるといい感じになる。

音が汚いのはコーデックがHSPになっているから。bluetoothヘッドホンの通信コーデックにはA2DPとHSPがあり、前者は聞く専用で高音質、後者はヘッドホンの付属マイクからの音声入力も可能だが音質は悪い。なのでA2DPに設定してやることで解決(これはbluemanからでも標準のbluetooth設定からでも、たぶんCLIからでもできる)。

音が鳴らないのは今回初めて遭遇した。HSPでの接続は可能だったがA2DPが選べなかったり、A2DPにすると音が鳴らなくなったりした。これは理由が不明だが、ペアリング直後にbluemanからA2DPに設定してやることで解決した。

ストレージデバイスのマウント

今回はなるべくGUIでやっちゃうのを目標にした。普段はストレージデバイスのマウントは/etc/fstabを手で編集してやっているが、今回は「ディスク」から。デバイス→パーティションを選択して、歯車マークのメニューから「マウントオプションを編集」でGUIからマウントを設定できる。ここで設定した内容がfstabに書き込まれていた。

やはり奇数系のUbuntuを使うのはエキサイティングだ。時間のあるときに限るが、楽しいんだこれが。

TypeScriptの引数を分割代入したときの ‘A’ is assignable to the constraint of type ‘T’, but ‘T’ could be instantiated with a different subtype of constraint ‘B’ みたいなエラー

T型の引数を受け取ってそのまま返す関数だが、返り値の型をTにすると型エラーになる。

const f1 = <T extends { user: { id: number } }>({ user, ...rest }: T): T => {
  return {
    user,
    ...rest,
  }
}
Type '{ id: number; } & Omit<T, "id">' is not assignable to type 'T'.
  '{ id: number; } & Omit<T, "id">' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ id: number; }'.(2322)

このエラーメッセージが難しい。 T extends { user: { id: number } } に注目する。extendsだからTはたとえば

type T1 = {
  user: {
    id: number;
    name: string;
  }
}

かもしれない(狭い型)。そうだとしても、関数内でのuserの型は { id: number } だ。nameがあることにはならない。TypeScriptならわかるかと思ったがわからないらしい。

Tに何を渡されようと返り値のuserの型は { id: number } (広い型)なので、それを T["user"] (狭い型)に渡しても要求を満たせないことがある。だから型エラーになる。

という話はここに書いてあるのだが
https://github.com/microsoft/TypeScript/issues/33579#issuecomment-534789400

このように書くと引数の型を維持できる。

const f2 = <T extends { user: { id: number } }>({ user, ...rest }: T): Omit<T, "user"> & { user: T["user"] } => {
  return {
    user,
    ...rest,
  }
}

const b = f2({user:{id: 1, name: "chao", premium: false}})
const p = b.user.premium // アクセス可能

TypeScriptくんは書き方によって推論ができたりできなかったりする。かわいい。

サーバーゼニ節約&kubernetes入門

ふと毎月のサブスク費用を確認したらAWSとさくらインターネットで月額5000円くらいかかっていて、いくらなんでも辛いのでこれを圧縮する。

原因は必要以上に多くのサーバーを稼働していること。現在稼働しているのは3つ。

さくらインターネット 2GB

  • 1738円/月
  • 2017年から借りており、中途半端な技術的挑戦の残骸が最も多い
  • 金額に対するスペックが良い

積載物

AWS EC2 t2.micro

  • 1544円/月

積載物

AWS EC2 t4g.micro

  • 1076円/月
  • AWS謹製のGravitonプロセッサなので安くて高性能。ただしArm。

積載物

  • gotosocialインスタンス

移行計画

どうしたものか…

とりあえずEC2は新世代ほど高コスパの原則があり、t2.microはt4g.microに移行すべき。自分の学習を考えるとさくらはやめてEC2に揃えたい。全部t4g.microに相乗りを狙ってみようか。

そしてどうせならkubernetesに全部載せてみたい。複数のアプリケーションを相乗りさせるときのトラフィック管理をこれまではjwilder/nginx-proxyでやっていたが、もうちょっとナウいやつに移行して監視とかも統一的にやりたい。