JJUG CCC 2020 Fall ( #jjug_ccc ) - セッション資料の一覧

JJUG CCC 2020 Fall に参加しました!

今回は、初のオンライン開催ということで、いつもと違って自宅からの視聴。
でも、twitterハッシュタグを見るとみんなでわいわいやってる感は変わらず、質疑応答もありでいつもと変わらず楽しめました。

それに加えて、気軽にセッションを行ったり来たりできたり、放送中に登壇者の方が随時コメントをしていたりと、オンラインならではというところがあって、これがとてもよかったです。
とはいえ、懇親会がないので、、、ちょっといつもよりもモチベーションが低めでした。

自分の主な参加動機は一度にいろんな話を聞けるってところだし、人見知りだし、懇親会がないならないでもいいかなとか最初は思っていたんですが…。
いざなくなってみると、全国からいろんな人に会える機会というのは貴重だし、自分自身それを楽しみにしていたんだなって改めて気づきました。

次回は、オフライン開催ができるようになってるといいですね!
(人数制限で、オンライン&オフラインという可能性もあるのかな…)


さて、最後にいつものを。

今回、残念ながら時間がかぶってしまって参加できなかったセッションがいっぱいあったので、あとで読むために現時点で発表者の方が公開されている資料一覧をまとめしました。*1
(あとで JJUG CCC 2020 Fall のページにもリンクが載るかもしれませんが、とりあえず自分の方で調べました)

動画

JJUG CCC 2020 Fall の動画一覧

10:00

11:00

12:00

13:00

14:00

15:00

16:00

17:00

18:00

過去記事

*1:発表者のお名前は敬称略とさせていただきました。

ド・モルガンの法則は大事!大事!超大事!

「ド・モルガンの法則」ってなにそれ?おいしいの?って聞かれたことがあります。
おいしくはないのですが、プログラムを書く上では超大事!です。

ここでは、プログラムを読み書きするうえでド・モルガンの法則をどのように使えばいいかを紹介します。

ド・モルガンの法則とは?

(知っている人は、次の章まで読み飛ばして構いません)

ド・モルガンの法則とは、以下の規則性のことです。

!(A || B) は (!A && !B) に等しい
!(A && B) は (!A || !B) に等しい

(ここで、|| は OR, && は AND, ! は NOT )

確認してみましょう。

!(A || B) は (!A && !B) に等しい
 +-------+-------+-------+
 |       | True  | False |
 +-------+-------+-------+
 | True  | False | False |
 | False | False | True  |
 +-------+-------+-------+
!(A && B) は (!A || !B) に等しい
 +-------+-------+-------+
 |       | True  | False |
 +-------+-------+-------+
 | True  | False | True  |
 | False | True  | True  |
 +-------+-------+-------+

簡単ですね。

プログラミングで役立つ場面

その1: if-else

例えば、以下の処理を考えてみます。

private void logic(boolean a, boolean b){
    if (a && b) {
        // 処理1
    } else {
        // 処理2
    }
}

ここで、処理1が実行される条件が何かというと「a が true、かつ b が true のとき」
では、処理2が実行される条件は?

elseif が成立しないときに実行されます。
つまり、 「NOT(if 条件)」のときです。
「(a が true、かつ b が true のとき)ではないとき」

よくわかりませんね。
そこで、ド・モルガンの法則を使います。

!(a && b)
→ !a || !b

つまり、「a が false、または b が false のとき」

これならわかりやすいですね。

その2: if - else if

続いて、次の処理を考えてみます。

private void logic(boolean a, boolean b){
    if (a && b) {
        // 処理1
    } else if (b) {
        // 処理2
    }
}

ここで、処理2が実行される条件は?

「b が true のとき」ではないです。
else if はそれまでの if 条件が成立しないときに実行されるからです。

つまり、「NOT(if条件)かつ(else if 条件)」のときに実行されます。
「(a が true、かつ b が true のとき)ではないとき、かつ b が true のとき」

よくわかりませんね。
そこで、再びド・モルガンの法則を使います。

!(a && b) && b
→ (!a || !b) && b

さらに論理を展開します。

(!a || !b) && b
→ (!a && b) || (!b && b)
→ (!a && b) || false
→ !a && b

つまり、「a が false かつ b が true のとき」

最初から、以下のように書いてくれていればわかりやすかったのですが…。1

private void logic(boolean a, boolean b){
    if (b) {
        if (a) {
           // 処理1
        } else {
            // 処理2
        }
    }
}

その3: while

ループを考えています。

boolean a = true;
boolean b = true;
while (a && b) {
    // 処理(この中で、a, b を変更)
}

このループの継続条件は、 while 式の通り「a が true、かつ b が true のとき」

では終了条件は?
継続条件が満たされないとき、つまり NOT(継続条件)です。

!(a && b)
→ !a || !b

つまり、終了条件は「a が false、または b が false のとき」
else のときと、考え方は同じですね。

 まとめ

ソースコードには、「else の実行条件」や「 while の終了条件」など、直接書かれていない条件が存在します。
特に、いいコードほどこうした論理的に導きだせることは書かれていません。冗長になるからです。

それらを明確に理解しながら読むには、論理演算の法則を適用して処理をひとつづつ読み解いていくことが大事です。

その中でも、ド・モルガンの法則は役に立つことが多い法則です。
覚えておいて損はないと思います。

--
え、ド・モルガンの法則なんて知らなくてもコードは読めるし書ける? そういう人は、天才です。たまにいます。


  1. 最初は if だけがあって、あとから仕様変更で else if を継ぎ足すという経緯があると、最初に提示したようなコードになりがちです。

8進数リテラルはプログラミング言語ごとに異なる

8進数を表記する文法は、主に以下の3つがあります。 (二番目と三番目は分かりにくいですが ゼロ オー1 です)

これらのうち、各プログラミング言語がどの文法を採用しているかを表にまとめました。

言語 0 0o 0O
C × ×
PHP × ×
Perl × ×
Java × ×
Scala (~2.9) × ×
Scala (2.10~) × × ×
Kotlin × × ×
C# × × ×
Rust × ×
Swift × ×
Go
Ruby
Python2
Python3 ×
JavaScript1

考察

「8進数は 0123 って書くんだよ」というのは、もはや古いのかもしれません。


  1. ただし、0 のあとに 8 か 9 を含むなら10進数。参考:字句文法 - JavaScript | MDN

  2. octet (8個組) の o

SELECT文で本番環境を落としたお話

本番環境でやらかしちゃった人 Advent Calendarで、このパターンのやらかしはなかったのでキーボードを叩くことにしました。
番外編のつもりでお楽しみください。

この記事が、新たな障害発生を防ぐことにつながれば幸いです。

何をやったのか

ある日、ちょっとした調査のために本番データベースのデータを確認することになりました。
(個人情報が格納されているようなシステムではなかったので、必要であれば本番データベースへのアクセスが許されていました)

もしメンテナンスがあればそのタイミングでやればよかったのですが、直近では特に予定はないとのことでした。そのため、システムが動いている状態のまま作業をすることにしました。
ごく単純な SELECT を実行するだけのつもりだったので、システムに影響がないと判断したためです。

その際、万が一コピペをミスって更新系の SQL を実行してしまったら怖いので、念のためトランザクションをかけてからSQLを実行することにしました。
具体的には、psqlPostgreSQL のターミナル)で本番データベースに繋いで、以下の SQL を実行しました。

BEGIN;
SELECT * FROM user_setting WHERE xxx = 1;

結果はすぐに帰ってきました。確か2行程度だったと思います。

続けてさらに SQL を実行しようとしました。しかし、ここで同僚から「ソースコードでわからないところがあるんですが…」と声をかけられました。
こちらは急ぎの作業ではなかったので、ターミナルをそのままにして同僚の質問に回答することにしました。

そして約10分後…。

「システムがダウンしてるー!」

本番障害となりました。

何が悪かったのか

トランザクションをかけて SELECT 文を打ったお前が悪い」ということになりました。

何が起きていたのか

ログからシステムの動きを確認したところ、あるスレッドで user_setting テーブルをロックしようとしていたことが分かりました。具体的には、以下の SQL が発行されていました。

LOCK TABLE user_setting;

この SQL には、ロックモードの指定がありません。この場合、PostgreSQLACCESS EXCLUSIVE ロックが指定されたものとみなされます。
明示的ロック - PostgreSQL 9.4.5文書

この ACCESS EXCLUSIVE ロックは最も強いテーブルロックです。
SELECT 実行時に自動的に獲得される最も弱いテーブルロックである ACCESS SHARE ロックとも競合します。

ロックモードの競合

つまり、システムが LOCK TABLE 文によって user_setting テーブルの ACCESS EXCLUSIVE ロックを獲得しようとしましたが、私が先に SELECT 文によって ACCESS SHARE ロックを獲得していたことで、ロック解除待ちに入って処理が止まってしまいました。

さらに、このあと別のスレッドが user_setting テーブルに対し SELECT を実行しようとしていました。しかし、user_setting テーブルは ACCESS EXCLUSIVE ロックの獲得待ちが発生しているので、この SELECT 文も止まってしまっていました。

結果、一つのスレッドが LOCK TABLE で、多数のスレッドが SELECT で止まってしまい、データベースとのコネクションプールが枯渇。システムダウンに至りました。

どうすればよかったのか

二度と惨劇を起こさないために、以下の知見を得ました。

  • SELECT しかしないとはいえ、油断しない
  • トランザクションを開始したなら、放置しない
    • なるべく早く COMMITROLLBACK をする

また、私がシステムを設計する際に以下の点に気を付けるようになりました。

  • ロック粒度に注意する
  • LOCK TABLE は極力使用しない
    • やむを得ず LOCK TABLE を使用するなら、可能な限り弱いロックモードを使用する
      • その際は、LOCK TABLE との競合について周知する1

MySQL では

MySQL だとテーブルロックのロックモードには READ と WRITE の2種類があります。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.3.5 LOCK TABLES および UNLOCK TABLES 構文

このうち WRITE ロックは SELECT と競合します。
また、MySQL のテーブルロックはセッション単位です。ドキュメントにあるように、ROLLBACK しただけではロックが解除されないので注意が必要です。

  • トランザクションを (たとえば、START TRANSACTION で) 開始すると、現在のトランザクションはすべて暗黙的にコミットされ、既存のテーブルロックが解放されます。
  • (中略)
  • ROLLBACK は、テーブルロックを解放しません。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.3.5.1 テーブルロックとトランザクションの通信

後日談

この件などでストレスが重なったことで胃痙攣をやらして、今度は私がダウンしました2。人間の体も本番障害が起きるんですね…。

みなさんも、障害発生後の体調管理には十分ご注意ください!


  1. 今回の場合、私が LOCK TABLE するコードを書いたわけではなかったので、このシステムでこのような競合が起きることを知りませんでした。
  2. 布団の上で数時間のたうち回りました。

Object#clone() メソッドからスローされる CloneNotSupportedException はどのようにハンドリングするべきか

JavaObject#Clone() メソッドは throws CloneNotSupportedException が宣言されています。

protected native Object clone() throws CloneNotSupportedException;

しかし、クラスが Cloneable インタフェースを実装していれば CloneNotSupportedException はスローされません。 それにもかかわらず CloneNotSupportedException はキャッチ例外のため1、スローするかキャッチする必要があります。

                            /* 👇実装している! */
public class Example implements Cloneable {
    @Override
    public Object clone() {
        // エラー: 例外CloneNotSupportedExceptionは報告されません。
        // スローするには、捕捉または宣言する必要があります
        return super.clone();
    }
}

正しく実装していれば何もしなくてもいいハズなのですが…。 このようなとき、どのようにハンドリングすればいいのでしょうか。

そこで、Java API ではこのよう場合にどのように実装されているかを確認してみました。 Search · CloneNotSupportedException path:/src/java.base/share/classes/java/

InternalError でラップする

圧倒的に多いのが、CloneNotSupportedExceptionInternalError でラップするという実装です。 Cloneable インタフェースを実装しているのに CloneNotSupportedException がスローされたということは、VM 内で何か問題があったということでこのようにしているようです。

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

AssertionError でラップする

ArrayDeque, EnumMap, EnumSetCloneNotSupportedExceptionAssertionError でラップしていました。 プログラムがバグっているということを示すために、AssertionError を使用しているようです。直接、この例外を投げるのは珍しい気がします。2

public ArrayDeque<E> clone() {
    try {
        @SuppressWarnings("unchecked")
        ArrayDeque<E> result = (ArrayDeque<E>) super.clone();
        result.elements = Arrays.copyOf(elements, elements.length);
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

RuntimeException でラップする

発生しないはずだから、とりあえず非キャッチ例外にしてしまえ!ということのようです。 ただ、RuntimeException だと型でエラーの概要が分からない、catch(Excetption e) で捕まってしまうという問題があります。

public Object clone() {
    try {
        return super.clone();
    } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e.getMessage());
    }
}

握りつぶして、null を返す

java.util.Date では、キャッチして握りつぶしていました。 ただ、これは発生しないとはいえ null が返されてしまうルートができてしまうので、紛らわしいと思います。

public Object clone() {
    Date d = null;
    try {
        d = (Date)super.clone();
        if (cdate != null) {
            d.cdate = (BaseCalendar.Date) cdate.clone();
        }
    } catch (CloneNotSupportedException e) {} // Won't happen
    return d;
}

ちなみに

Cloneable インタフェースを実装しているクラスの clone メソッドで、throws CloneNotSupportedException を宣言しているのは見当たりませんでした。

まとめ

いかがでしたか。3
Cloneable インタフェースを実装していれば CloneNotSupportedException はスローされないので、どのようにハンドリングしても問題はないです。とはいえ、throws してしまうと呼び元に余計な手間をかけてしまいます。そのため、キャッチして何かしらのハンドリングをしておいた方がいいと思います。

そして、Java API の実装に合わせるのであれば InternalError にラップしてスローするのがいいようです。


  1. 設計ミスだと思います。

  2. 本来は、-enableassertions オプション付きで Java を実行しているときに、assert 文の条件が成立していないときにスローされるエラーです。

  3. これ書くとアフィリエイトブログっぽいですね。

Ansible で Mackerel を操作するプラグインを作りました!

Ansible で Mackerel の設定を取得したり変更したりできたらなーと思う時があります。 例えば…

  • アラートが鳴ってしまうのを防ぐために、Ansible 実行中はホストのステータスを standby にしたい
  • 特定のロールが設定されているホストの場合だけ、ファイルをコピーしたい

でも、Ansible の command モジュールで mackerel-agent を実行してこのような処理をさせるのは手間がかかります。 そこで、 Ansible の設計にのっとった形で簡単に Mackerel を操作できるように、プラグインを作ってみました! 1

https://github.com/YujiSoftware/ansible-mackerel-module

どういう風に使えるの?

例えば、Ansible 実行中はホストのステータスを standby にしたいとき。

- name: Set Mackerel Host Status
  mackerel_host:
    status: standby

# ~ホストの更新処理~

- name: Set Mackerel Host Status
  mackerel_host:
    status: working

ホストのロールを設定したいとき。

- name: Set Mackerel Host Role Fullnames
  mackerel_host:
    role_fullnames:
      - test_project:web

ロールによって動作を変えたいとき。

- name: Get Mackerel host configuration
  mackerel_host:
  register: mackerel

- name: Copy file if roles has "test_project"
  copy:
    src: ../files/example.conf
    dest: /etc/httpd/conf.d/
  when: mackerel['host']['roles']['test_project'] is defined

こんな感じに使えます! (ちなみに、API Key や Host ID はサーバ内にある mackerel-agent の設定ファイルをデフォルトで読み取るので、記述は不要です)

詳しい使い方は、GitHub の README をご覧ください!

まとめ

まだ ホストの情報取得/ステータス設定/ロール設定 しか対応していません。2 今後、JOJOに対応する操作を増やしていく予定です。

もし、こういうことができるようにしたい!という要望がありましたら、GitHub の Issues へ書き込んでいただければと思います。もしくは Up vote 👍 をお願いします。 https://github.com/YujiSoftware/ansible-mackerel-module/issues

ぜひご利用ください!


  1. すでに mackerelio/ansible-mackerel-agent というのがあるのですが、これは mackerel-agent をインストールするための Ansible テンプレートです。一方で、今回作ったプラグインは Mackerel API のラッパーです。

  2. Mackerel Advent Calendar が空いてる!チャンスだ!と思って急いで作りました。

JJUG CCC 2019 Fall ( #jjug_ccc ) - セッション資料の一覧

JJUG CCC 2019 Fall に行ってきました!


最近 Java のシステムをまたいじるようになって*1Java 欲が高まってきていたところにいろんなセッションを聞けてすごいテンション上がりました。
特に、「開け!ドメイン駆動設計の扉」がちょうどこれから一部のシステムをリプレースするにあたって導入したいけど「DDD、さっぱりわからん」と思っていたところだったのでとても助かりました。


また、マニアックな話も多数あってやっぱり JJUG CCC 最高!という気持ちです。
CLR の ValueType を起点に Project Valhala を覗いてみる」とか「オレ流OpenJDK「の」開発環境」とか、普段の JJUG では聞けない、でもとても知りたかった内容でした。こういうの、もっと聞きたい!


そして、懇親会で話しかけてくださった方、ありがとうございました!
すごく楽しかったです、そしてあっという間でした。もっとお話したいのでぜひ次の機会に!!


さて、最後にいつものを。

今回、残念ながら時間がかぶってしまって参加できなかったセッションがいっぱいあった*2ので、あとで読むために現時点で発表者の方が公開されている資料一覧をまとめしました。*3
(あとで JJUG CCC 2019 Fall のページにもリンクが載るかもしれませんが、とりあえず自分の方で調べました)

10:00-10:45

11:00-10:45

12:00-12:45 (ランチセッション)

13:30-14:15

14:30-15:15

15:45-16:30

*1:弊社、いろんな言語が混ざりすぎ!

*2:あと、今回はボランティアスタッフやっていたので、その間のセッションでも気になったのがありました。

*3:発表者のお名前は敬称略とさせていただきました。