Lightsailのプライベートデータベースにポートフォワーディングする.ssh/configを書きたい

あんまり同じような人はいないかもしれませんが以下のような人達に向けて…

  1. AWSでLightsailのデータベースを使っていてパブリックモードを無効にしている
  2. データベースと同じリージョンにあるLightsailのインスタンスからポートフォワードでssh接続したい
  3. 長いコマンドを毎回打つのがしんどい
  4. .ssh/config を使って楽に打ちたい

configファイルがない場合は作成する必要があります。
ホームディレクトリの.sshに作成します。
例: C:\Users\hiroaki muramoto\.ssh

「ssh [ユーザー名]@[ホスト名] -i [秘密鍵の場所] -L 3306:[プライベートデータベースのエンドポイント]:3306」

上のコマンドをconfigで設定します。

configの内容(ssh remote-dbと打って繋がるようにしたい)

# ssh remote-db で繋がる 
Host remote-db

    # ユーザー名
    User [ユーザー名]

    # LightsailインスタンスのドメインまたはIPアドレス
    HostName [ドメインまたはIPアドレス]

    # ポートフォワード 3306の場合
    LocalForward 3306 [データベースのエンドポイント]:3306

    # pem(鍵の場所)
    IdentityFile [鍵のpath]
    IdentitiesOnly yes

無事に繋がりました。

参考

.ssh/configファイルでSSH接続を管理する
多段SSHやポートフォワーディングを ‘.ssh/config’ に書く

Windows Terminalを使って、CLIを出来るだけ一元化したい。

背景

これまでは、
sshクライアント…RLogin(少し前はputty)
windows用のCLI…Windows PowerShell(少し前はコマンドプロンプト)
を使っておりました。

まず色々開くのは好きじゃないのでRLoginのようなタブで複数操作できるのは結構魅力だったりします。
windows用のcli もそうなったらいいと思っていた中、たどり着いたのはPowerShell 7(Windows PowerShellではない)とWindows Terminalの組み合わせです。
どちらもMicrosoftが提供しているので、安心感もありますね。

Windows Terminalをインストール

まずはWindows Terminalです。
Windows11にしてから入っているのか元々入っていたのかは知らないですが、自分の環境では既に入っていました。
もしかしたら過去に同じ悩みを抱え、インストールだけしたのかもしれません。
全く記憶にないのですが…。

Microsoft StoreにありますのでMicrosoft Storeアプリで「Windows Terminal」と探せば見つかります。

Windows Terminalについて

PowerShell 7 をインストール

正直「Windows PowerShell」でもよかったのですが、PowerShell 7にした理由は、Windows PowerShellを開くと出てくる以下のメッセージが出なくなると嬉しいなぐらいの気持ちです。

「新機能と改善のために最新の PowerShell をインストールしてください! https://aka.ms/PSWindows」

https://aka.ms/PSWindows

上のリンク先から「PowerShell 7 のインストール」の
MSI パッケージを使用して PowerShell を展開する
からmsiをダウンロードしてインストールしました。

Windows Terminalを好きなように設定

最初は既定で設定されているCLIが立ちあがると思います。
こんな感じです。

タブが使えるのは嬉しいですね。

「Ctrl + 、」で設定画面を開きます。

ここからはお好みですが…

【スタートアップ】

  1. 既定のプロファイルでPowerShellを選択
  2. 既定のターミナルアプリケーションを Windows ターミナル に設定

【操作】

  1. URLを自動的に検出して、クリックできるようにするをオフ

cronとかがあった場合に誤ってクリックしないようにオフにしました。

【外観】

  1. テーマをダークに変更

やっぱりダークが好き

【 プロファイルのPowerShell 】

追加の設定にある外観をクリック後、 テキストの配色、フォント、背景を好きなものに設定して保存。

結果

良い感じになったのではないでしょうか。

単純CASE式と検索CASE式

CASE式みたいな処理って、自分の場合はこれまでアプリケーション内で行うことが多くてMySQLで行うことが少なかったです。
しかし、使う機会があって若干はまったので、備忘録として残します。

やりたかったことは、「NULLか空文字だったら値を0として取得する」ことでした。
CASE式には単純CASE式と検索CASE式があってそれが頭に入っておらず、はまっておりました。

サンプルテーブルを以下に用意します。

START TRANSACTION;
    CREATE TABLE member (
        id int(11) NOT NULL,
        name varchar(64) NOT NULL,
        gender enum('male','female') NOT NULL,
        age int(11) NOT NULL,
        memo mediumtext,
        created datetime NOT NULL,
        updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    ALTER TABLE member
        ADD PRIMARY KEY (id),
        ADD KEY gender (gender),
        ADD KEY age (age);

    ALTER TABLE member
        MODIFY id int(11) NOT NULL AUTO_INCREMENT;

    INSERT INTO member (name, gender, age, memo, created) VALUES
        ('suzuki', 'male', 25, 'イケメン', '2021-04-01 00:00:00'),
        ('ohtani', 'female', 30, NULL, '2021-05-01 00:00:00'),
        ('sato', 'female', 35, '', '2021-06-01 00:00:00'),
        ('nakamura', 'female', 40, 'おもしろい', '2021-07-01 00:00:00'),
        ('tanaka', 'female', 45, NULL, '2021-08-01 00:00:00'),
        ('inoue', 'male', 50, '博識', '2021-09-01 00:00:00'),
        ('ishida', 'female', 55, '優しい', '2021-10-01 00:00:00'),
        ('matsumoto',  'male', 60, '穏やか', '2021-11-01 00:00:00'),
        ('sasaki', 'male', 65, '', '2021-12-01 00:00:00'),
        ('kato', 'female', 70, '', '2022-01-01 00:00:00');
COMMIT;

テーブルの中身はざっとこんな感じ。

idnamegenderagememocreatedupdated
1suzukimale25イケメン2021-04-01 00:00:002022-05-13 14:18:42
2ohtanifemale30NULL2021-05-01 00:00:002022-05-13 14:18:42
3satofemale352021-06-01 00:00:002022-05-13 14:18:42
4nakamurafemale40おもしろい2021-07-01 00:00:00 2022-05-13 14:18:42
5tanakafemale45NULL2021-08-01 00:00:002022-05-13 14:18:42
6inouemale50博識2021-09-01 00:00:00 2022-05-13 14:18:42
7ishidafemale55優しい2021-10-01 00:00:002022-05-13 14:18:42
8matsumotomale60穏やか2021-11-01 00:00:002022-05-13 14:18:42
9sasakimale652021-12-01 00:00:002022-05-13 14:18:42
10katofemale702022-01-01 00:00:002022-05-13 14:18:42

性別が英語で入っているので日本語で取得してみたいと思います。
比較しやすいように名前と英語での性別も取得します。
以下のようにCASEのすぐ後ろにカラム名が来て、そのWHENの値と同値のCASE式を単純CASE式と呼ぶそうです。

# 単純CASE式
SELECT
    name,
    gender,
    CASE gender
        WHEN 'male' THEN '男性'
        WHEN 'female' THEN '女性'
    END AS gender_ja
FROM member;

検索結果

namegendergender_ja
suzukimale男性
ohtanifemale女性
satofemale女性
nakamurafemale女性
tanakafemale女性
inouemale男性
ishidafemale女性
matsumotomale男性
sasakimale男性
katofemale女性

maleの場合は「男性」、femaleの場合は「女性」と表示されます。

次は
60歳以上であれば「senior」
40歳以上60歳未満であれば「middle-age」
40歳未満を「young」
と出力するようにします。

検索CASE式ではWHENの後に検索条件が入ります。
CASEの後に列名は必要ありません。

# 検索CASE式
SELECT
    name,
    age,
    CASE 
        WHEN age >= 60 THEN 'senior'
        WHEN age >= 40 && age < 60 THEN 'middle-age'
        ELSE 'young'
    END AS generation
FROM member;

検索結果

nameagegeneration
suzuki25young
ohtani30young
sato35young
nakamura40middle-age
tanaka45middle-age
inoue50middle-age
ishida55middle-age
matsumoto60senior
sasaki65senior
kato70senior

応用が効くのは検索CASE式で単純CASE式は、検索CASE式でも行うことが出来ます。

最初に行った性別を日本語で表示する単純CASE式を検索CASE式で書いてみます。

# 検索CASE式
# 結果は二つ上の表と同じ
SELECT
    name,
    gender,
    CASE
        WHEN gender LIKE 'male' THEN '男性'
        WHEN gender LIKE 'female' THEN '女性'
    END AS gender_ja
FROM member;

今回やりたかったことは、「NULLか空文字だったら値を0として取得する」だったので、検索CASE式を使えば簡単に出来そうです。
CASEの後ろに memo と書いていたので若干はまっていました。

SELECT
    name,
    memo,
    CASE
        WHEN (memo IS NULL || memo = '') THEN 0
        ELSE 1
    END AS memo_exists
FROM member;

検索結果

namememomemo_exists
suzukiイケメン1
ohtaniNULL0
sato0
nakamuraおもしろい1
tanakaNULL0
inoue博識1
ishida優しい1
matsumoto穏やか1
sasaki0
kato0

めでたしです。
と、最初は上のSQLを書いたのですがCHAR_LENGTHを使えば空文字判定出来そうですね…。
TRIMも必要なら一緒にすれば良さそうです(全角スペースは除かれないので注意)。

SELECT
    name,
    memo,
    CASE
        WHEN CHAR_LENGTH(memo) > 0 THEN 1
        ELSE 0
    END AS memo_exists
FROM member;

上のIF文を使って書くことも可能です。

SELECT
    name,
    memo,
    IF(CHAR_LENGTH(memo) > 0, 1, 0) AS memo_exists
FROM member;

単純CASEで書けるところも検索CASE式で書けるので、検索CASE式 → 単純CASE式の順に学んだ方が覚えやすい気がします。

大量のレコードをDELETEしたならOPTIMIZE TABLEをしようというお話

そもそも大量にDELETEしない方が安心ですが…。
LIMITをつけて刻んだDELETEでも同様です。
DBのストレージ容量の肥大化を防ぐため、ログテーブルなどのもう利用しない古いレコードを大量に物理削除したいときとかってあります。
物理削除した場合は、

OPTIMIZE TABLE table_name;

を実行しないとディスク領域が解放されません。
ただし、実行した場合はテーブルロックがかかります。
サービスがリアルタイムで稼働中の場合は、実行タイミングに注意が必要です。

試しに実行してみます。
以下のような適当なサンプルテーブルを用意してみます。

CREATE TABLE people (
    id int(11) NOT NULL,
    name varchar(64) NOT NULL,
    height int(11) NOT NULL,
    weight int(11) NOT NULL,
    gender enum('male','female') NOT NULL,
    updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE people
    ADD PRIMARY KEY (id),
    ADD KEY height (height),
    ADD KEY weight (weight);

ALTER TABLE people
    MODIFY id int(11) NOT NULL AUTO_INCREMENT;

まず、10行入れます。

INSERT INTO people (name, height, weight, gender) VALUES
 ('suzuki', 170, 65, 'male'),
 ('ohtani', 190, 90, 'male'),
 ('sato', 150, 45, 'female'),
 ('nakamura', 180, 75, 'male'),
 ('tanaka', 160, 55, 'female'),
 ('inoue', 155, 50, 'female'),
 ('ishida', 160, 60, 'male'),
 ('matsumoto', 165, 60, 'female'),
 ('sasaki', 175, 70, 'male'),
 ('kato', 145, 40, 'female');

10000010行に増やします。
自分の環境では2分30秒ぐらいかかりました。

INSERT INTO people (name, height, weight, gender)
SELECT p1.name, p1.height, p1.weight, p1.gender
FROM people p1, people p2, people p3, people p4, people p5, people p6, people p7;

ここで容量を確認しておきます。

SELECT table_name,
       table_rows AS rows,
       floor((data_length+index_length) / 1024 / 1024) AS all_mb,
       floor(data_length / 1024 / 1024) AS data_mb,
       floor(index_length / 1024 / 1024) AS index_mb
FROM information_schema.tables
WHERE table_schema = database()
ORDER BY (data_length + index_length) DESC;
table_name rows all_mb data_mb index_mb
people 9739621 660 406 253

およそ660MBになっています。
DELETE文でおよそ900万行削除したいと思います。
時間かかるのでブレイクタイムにすることをオススメします。
実行したら自分の環境では12分でした。

DELETE FROM people WHERE id > 1000000;

消し終えたらもう一回、容量を確認します。

table_name rows all_mb data_mb index_mb
people 1191489 660 406 253

行数は変わったけど容量は変わってないんですよね…。
そこで最適化します。

OPTIMIZE TABLE people;

もしくは

ALTER TABLE people ENGINE INNODB;

14秒くらいかかりました。

table_name rows all_mb data_mb index_mb
people 997458 81 50 31

減りましたのでめでたしです。
しかし、DELETE文が遅いのとテーブルがロックされるのが嫌がられるのがわかりますね。
一時テーブルを作成する方法が取られるのもこちらの結果を考えると納得がいきます。

参考サイト

エリック・エヴァンスのドメイン駆動設計を読みました③

前回の投稿

■蒸留
蒸留とは混ざり合ったコンポーネントを分離するプロセス。
モデルとは知識が蒸留されたもの。
今までの章で行ってきたドメインモデルの洗練も蒸留ではあると思いますが、この本の15章の蒸留はここまで洗練してきたドメインモデル群を更に大事なものから優先順位をつけて分離していくことを言っていると思います。
この蒸留の目的は、システム内で最大の価値を付加すべきものをたった1つ抽出すること。
抽出されたものがコアドメイン。
そのコアドメインは当然我々のソフトウェアを特徴づけ、構築する価値のあるものにする。
化学の蒸留と同様に、蒸留プロセスにおいて分離されたかなり価値がある副産物(汎用サブドメインなど)が出来上がる。

・コアドメイン
ドメインに関心があって技術的にも優秀なメンバーがコアドメインに当たるべきとのこと。
ただ技術的に優れていてもドメインに関心がない人が多い傾向にあるためなかなかそんな人はいない。
ただそういう場合はドメインに関心がある優秀なメンバーを集め、ドメインエキスパートが参加するチームを収集し補佐する。
コアドメインの選択はその「システム内で最大の価値を付加すべきもの」なので、システムによって様々。

・汎用サブドメイン
システムを機能させて、モデルを完全に表現するためには欠かせないもので補佐役を果たす。
相当数のビジネスで必要になる概念を抽象化している。
例としては、企業の組織図、財務に関するもの、タイムゾーンなど。

汎用という言葉があるがこれはコードが再利用可能ということではない。
再利用性を考慮していては、蒸留のコアドメインに集中するという動機から外れてしまう。
汎用的な概念の範囲内に収めるということには集中する。

・ドメインビジョン声明文
コアドメインとそれをもたらす価値に関する簡潔な記述を作成する
チームに共通の方向性を与える。
新しい洞察を得たら改訂すること。
ドキュメントって最初は気合い入れて作るけど、改訂を怠りがちになりますよね。。。

・強調されたコア
ドメインビジョン声明文は広い観点から見たコアドメインを識別するもの。
強調されたコアは具体的なコアドメインの要素の識別をするための文書。
これがないと個人の解釈によって識別することになってしまうため、好ましくない。
記述方法として「蒸留ドキュメント」や「コアにフラグを立てる」というものがある。
蒸留ドキュメントはコアドメインとコアを構成する要素間の主要な相互作用を簡潔に記述する。
コアにフラグを立てるというのはUML図などでコアとなる要素に印をつけておくこと。

・凝集されたメカニズム
オブジェクト思考設計にとってカプセル化は標準的なプラクティス。
「何が」(what)と「どのように」(how)を分離するが、「何が」(what)が、「どのように(how)」によって肥大化し、複雑化することがある。問題を解決するためのアルゴリズムが多くなって問題を表現するメソッドがわかりにくくなる。
こういうことが多くなってきたらモデルに問題がある兆候。
この問題を解決するためのアルゴリズムなどを概念的に凝集された部分を切り分けて、フレームワーク化する。

汎用サブドメインも凝集されたメカニズムもコアドメインの負担を軽減したいという願望に基づいているが、汎用サブドメインは表現力豊かであるモデルで、凝集されたメカニズムはドメインを表現しない。
モデルにより提起された面倒な処理の問題を解決するためにある。

・隔離されたコア
モデルをリファクタリングして、補助的な役割を果たすものから分離すること。
そうすることで凝集度が高まり、他のコードへの結合が低くなる。

隔離するために

  1. コアサブドメインを識別する
  2. 関連するクラスを、それに関係づけている概念に由来する新しいモジュールへ移動する。
  3. コードをリファクタリングして、概念を直接表現していないデータと機能を切り離す。
  4. 他のモジュールとの関係を最小限におさえて明確化する。
  5. 1から繰り返す

・抽象化されたコア
モデルにおけるもっとも基本的な概念を識別し、それを別のクラス、抽象クラスまたはインターフェースに括り出す。
この抽象的なモデルは、重要なコンポーネント間をほとんど表現するように設計する。
この抽象的なモデル全体を独自のモジュールに入れる。
ただし、特殊で詳細な実証クラスは、サブドメインによって定義されたモジュールに残す。

■大規模な構造
ようやく森を見る。
森を見るためにそれぞれの木の役割や関係性を明確にしておく。
概念上の大規模な構造をアプリケーションと共に進化させ、場合によって、全く別の種類の構造に変更する。
大規模な構造は一般に、境界付けられたコンテキストを横断して適用できる必要があり、現実の制約にも対応しなければならない。

大規模な構造を適用すべきなのは、モデルの開発に不自然な制約を強いることなく、システムを大幅に明確化する構造が見つけられたときでうまくいかなければ別になくてもよい。

・責務のレイヤ(システムのメタファ、知識レベル、着脱可能なフレームワークは略)
責務駆動設計は大規模な構造に対しても適用される。
責務駆動設計と同じように、上位層は下位層に依存するが、下位層は上位層からは独立するような形。
レイヤ化すると見通しがよくなるかもしれない。
過度なレイヤ化は逆にわからなくなるので注意。
モデルの依存関係を調べ、ドメインに自然な階層が認められたら責務を与える。

■感想
2019年12月、自分は初めてPHPカンファレンスに参加しました。
その初ペチコンで初めて受講した講義が、

MVCにおける「モデル」とはなにか
https://fortee.jp/phpcon-2019/proposal/b6302d4a-a8b8-41a7-ad0c-98704fd18c6c

でした。
正直内容が難しく、後々の講義もこのレベルが続くのか…と絶望していました。
後々、参加者の感想を見ると熟練者の方でもこのトークは難しいレベルだったみたいです。

ただ今回、当時のトークで

(飲食店等において)
ホールの人は「客とオーダーが満たされているか」が関心事であり、
キッチンの人にとっては「料理をどの順番で提供しなければならないかが関心事」

と聞いたことを思い出し、この関心事に注目してモデリングすべきなんだと本を読みながら感じることが出来ました。
誰にとっては何が値オブジェクトで何がエンティティなのかとかですね。
当時わからなかったことが少しでもわかるようになったのは嬉しかったです。

設計の本を読むとプログラマに最も必要なのはコミュ力ではないかと思ってきますね。

どうしてコミュ障に優しい世界じゃないんですか…?