なぜUsersテーブルの行でロックを取るのか?
下記ツイートを見て改めて考えてみました。
更新系APIでDBトランザクション張る時、対象のリソース群の親となるリソース(大抵はUser)を無条件で最初にロックする手法が当たり前だと思っていたけど、もしかして世間ではそうではないのかな...デッドロックリスクを忘れられる大きなメリットがあると考えていたんだけど。
— やまでぃ🤗 | YOUTRUST (@aiueo4u) 2021年2月16日
並行に書き込みをどのように処理するかという点も含めて整理してみました。
並行性の問題一覧
名称 | 内容 |
---|---|
ダーティリード | あるクライアントが他のクライアントのまだコミットされていない書き込みを見ることができる。 read commited分離レベル以上では生じない。 |
ダーティライト | あるクライアントが他のクライアントのまだコミットされていない書き込みを上書きできる。 |
ノンリピータブルリード | トランザクション内でクライアントが異なる時刻で異なるデータを取得してしまう(他のトランザクションでデータが変更されるとその内容が反映されてしまう)。 スナップショット分離で回避 |
ファントムリード | あるトランザクションでの書き込みが、他のトランザクションの検索クエリの結果を変化させる(行のINSERT、DELETE)。本来ファントムリードはREPEATABLE-READでは防がないが、MySQL InnoDBのREPEATABLE-READの分離レベルでは防ぐことが可能。 |
スナップショット分離(リピータブルリード)
それぞれのトランザクションがデータベースの一貫性のあるスナップショットから 読み取り(Read) を行うものです。
スナップショットに対して読み取りをすることで矛盾なく読み取りが出来ることを一貫性読み取り (Consistent Read) と言います。
MVCC
スナップショット分離(リピータブルリード)では、トランザクションに一貫したデータを参照させるために過去のバージョンのデータを見せる必要があります。
データベースはあるデータ(オブジェクト)について複数のバージョンを管理すること、これがMVCC(multi-version concurrency control)と呼ばれる手法です。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.2.12 InnoDB マルチバージョン
MySQL InnoDBにおけるスナップショット
スナップショット(過去のバージョンのデータ)は、UNDOログにコピー(退避)されています。
MySQL :: MySQL 5.7 Reference Manual :: 14.6.7 Undo Logs
それぞれの行にはローバックポインタがあり、 退避したUNDOレコードを指すようになっています。
さらに、UNDOレコードもさらに古いUNDOレコードのポインタを持っており、このローバックポインタをたどれば過去のバージョンのデータがわかるようになっています。
UNDOレコードの確認
関連しているトランザクションが全て終了しているUNDOレコードは削除できます。
削除(パージ)されていないUNDOレコードは、 SHOW ENGINE INNODB STATUS;
の History list length
で確認できます。
------------ TRANSACTIONS ------------ Trx id counter 334129 Purge done for trx's n:o < 334128 undo n:o < 0 state: running but idle History list length 18 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 421690295200296, not started 0 lock struct(s), heap size 1136, 0 row lock(s) ---TRANSACTION 421690295199440, not started 0 lock struct(s), heap size 1136, 0 row lock(s)
日々の覚書: InnoDBのHistory list lengthの監視と原因スレッドの特定と
SHOW ENGINE INNODB STATUS の History list length - ablog
MySQLのmetricに関する話 | エンジニアブログ | GREE Engineering
MySQL InnoDBにおけるスナップショット分離(REPEATABLE-READの分離レベル)
スナップショット分離の分離レベルでは、 ダーティーリード、ノンリピータブルリードは防ぐ必要がありますが、ファントムリードを防ぐかはその実装依存となります。
SQLの仕様ではREPEATABLE-READでは特にファントムは防がなくてもよいとなっています。
http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt ANSI SQL 92
A Critique of ANSI SQL Isolation Levels読んだ - tom__bo’s Blog
MySQL InnoDBでは binlog_format=STATEMENT
でのレプリケーションの整合性を保証するため、独自のロックのかけ方(ギャップロックとネクストキーロック)でそれを実現しています。
これは、STATEMENTの場合、sourceは並行で処理しているがreplicaはバイナリログを直列で処理しているため、sourceでファントムが起こる状態(並行で処理している)であってもreplicaではファントムが起こらないためデータの整合性が合わないことがあるためです。
MySQL InnoDBではREAD-COMMITEDの分離レベルで binlog_format=STATEMENT
にするとエラーになります。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 5.2.4.2 バイナリログ形式の設定
MySQLのレプリケーション設定で起きたトラブルの原因とその解決策 - Yahoo! JAPAN Tech Blog
この独自のロックのかけ方が、ファントムを防止しています。
ファントムとかギャップロックとネクストキーロック
すでにまとまっている資料がたくさんあるので割愛
スナップショットの取得
BEGIN句によるトランザクション開始
BEGIN句からトランザクションを開始すると、そのタイミングではスナップショットの作成されません。
スナップショット取得が行われるのは最初の SELECTのタイミングになります。
mysql> SHOW CREATE TABLE t \G *************************** 1. row *************************** Table: t Create Table: CREATE TABLE `t` ( `a` int unsigned NOT NULL, `b` int NOT NULL, PRIMARY KEY (`a`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci 1 row in set (0.00 sec) mysql> SELECT @@SESSION.transaction_isolation \G *************************** 1. row *************************** @@SESSION.transaction_isolation: REPEATABLE-READ 1 row in set (0.00 sec)
スナップショットの取るタイミングの確認その1
No. | session1 | session2 |
---|---|---|
1 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | +---+---+ 1 row in set (0.00 sec) |
|
2 | mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
3 | mysql> INSERT INTO t VALUES(2, 1); Query OK, 1 row affected (0.01 sec) |
|
4 | mysql> COMMIT; Query OK, 0 rows affected (0.01 sec) |
|
5 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | +---+---+ 2 rows in set (0.00 sec) (このタイミングでスナップショットの生成が行われるので、ファントムぽく見えるけどこれは正しい挙動) |
スナップショットの取るタイミングの確認その2
No. | session1 | session2 |
---|---|---|
1 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | +---+---+ 2 rows in set (0.01 sec) |
|
2 | mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
3 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | +---+---+ 2 rows in set (0.01 sec) ここでスナップショット取得 |
mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | +---+---+ 2 rows in set (0.00 sec) |
4 | mysql> INSERT INTO t VALUES(3, 1); Query OK, 1 row affected (0.01 sec) |
|
5 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | +---+---+ 2 rows in set (0.00 sec) ファントムが防がれている |
mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | +---+---+ 3 rows in set (0.00 sec) |
mysql> COMMIT; Query OK, 0 rows affected (0.01 sec) |
||
mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | +---+---+ 2 rows in set (0.00 sec) ファントムが防がれている |
mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | +---+---+ 3 rows in set (0.00 sec) |
START TRANSACTION WITH CONSISTENT SNAPSHOTでトランザクションが開始とスナップショットの作成を同時に行う
MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.3.1 START TRANSACTION、COMMIT、および ROLLBACK 構文
No. | session1 | session2 |
---|---|---|
1 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | +---+---+ 3 rows in set (0.00 sec) |
|
2 | mysql> START TRANSACTION WITH CONSISTENT SNAPSHOT; Query OK, 0 rows affected (0.01 sec) |
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
3 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | +---+---+ 3 rows in set (0.01 sec) |
|
4 | mysql> INSERT INTO t VALUES(4, 1); Query OK, 1 row affected (0.00 sec) 多分ここでスナップショットができていると予想 |
|
5 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | | 4 | 1 | +---+---+ 4 rows in set (0.00 sec) |
|
mysql> COMMIT; Query OK, 0 rows affected (0.01 sec) |
||
mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | +---+---+ 3 rows in set (0.00 sec) ファントムが防がれている |
SELECT FOR UPDATE
SELECT FOR UPDATEは最新のスナップショットの取得をし、それを使うために行および関連付けられたすべてのエントリに排他ロックをかけます。
この動作は、これらの行に UPDATE ステートメントを発行した場合と同じ挙動になります。
MySQL InnoDBにおけるスナップショット分離(REPEATABLE-READの分離レベル)でも起き得ること
更新ロスト(Lost Update)
アプリケーションが何らかの値を読み取り、その値を変更して書き戻す( read-modify-write )の場合に生じることがあります。
ひとことでいうと最後の書き込みを勝たせる(last write wins LWW)ことです。
PostgreSQLのリピータブルリードだと更新ロストが発生したことを自動的に検出して問題のトランザクションを中断してくれるとのことですが、MySQLのInnoDBでは更新のロストの検出はしてくれません。
更新ロストを防ぐ方法として、更新対象のデータを明示的にロックをする方法が挙げられます。
なにかしら更新処理を行う最初にSELECT FOR UPDATEで対象となる行をロックし、更新中は他のトランザクションが並行に読みだそうとしても先行するトランザクションが完了するまで待たされるようにします。
この場合、アプリケーションロジックの中で必要なロックの取得を忘れてしまうと簡単にレース条件が発生するの注意が必要です。
また、トランザクション内で単純なSELECTとSELECT FOR UPDATEを使ったデータ取得に差異がでることがあるので、ここも要注意です。
(単純なSELECTだと過去のスナップショットを参照するが、SELECT FOR UPDATEは必ず最新のスナップショットを見る)
漢(オトコ)のコンピュータ道: InnoDBのREPEATABLE READにおけるLocking Readについての注意点
MySQL :: MySQL 5.6 Reference Manual :: MySQL Glossary
書き込みスキュー
2つのトランザクションが同じデータ群から読み取りを行い、それらのいくつかを更新する(特にトランザクションごとに更新するデータが異なっている)場合に生じるものです。
複数のデータが関わっているので、単一のデータに対するアトミックな操作(ロック)では解決できません。
これも、複数のデータに対して明示的にロックを行うことで解消できます。
ただし、特定の条件を満たす行が存在しないことをチェックし書き込みによってその行が追加される場合、ロックする対象となる行が存在しないためロックがかけられないという問題があります。
これを回避するためには、衝突の実体化(materializing conflicts)を行う必要があります。
https://dsf.berkeley.edu/cs286/papers/ssi-tods2005.pdf
これらの回避するために
これらは分離レベルをSERIALIZABLEにすることで解消することがほとんどですが、SERIALIZABLEによって並行に処理できなくなることが多く(事実上1個の処理だけが動くような排他制御が行われる)パフォーマンスに大きな影響を出すことがほとんどです。
ほとんどの場合において書き込みスキューを考慮しなくてよい場合においては、一部分だけ直列にできるようなしくみがあればよさそうです。
そこで、アプリケーション全体でロックを行う対象を決め、それらを順序を守ってロックをかけることで直列に処理を行うようにします。
ロックを行う対象は、必ず存在するデータに対して行います。(そうでないとギャップロックがかかるので)
そのため、自分の大体のケースでUsersテーブルを使って対象となるユーザーの行に対してロックをかけるということになります。 (UsersテーブルにはユーザーIDと作成日くらいしかないことを想定しています)
ロックを掛ける際は、デッドロックを防ぐためUsersテーブルのプライマリキーで ソートしてから ロックをかけます。
例えば、PvPを考えると
- ユーザーAの攻撃でユーザーBのHPを減らし、ユーザーAに経験値を追加する
- ユーザーBの攻撃でユーザーAのHPを減らす、ユーザーBに経験値を追加する
のようにの相互の更新が同時に起こりうるので、ユーザーAとユーザーBのUsersテーブルにロックを掛ける場合ロックを取るときはロックを取る順番がどちらも同じになるようにします。
SELECT id FROM users WHERE a IN ("A", "B") ORDER BY a FOR UPDATE;
実際にどうなるかを簡単なテーブルで試した例も記載しておきます。
デッドロックになる例
No. | session1 | session2 |
---|---|---|
1 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | +---+---+ 3 rows in set (0.00 sec) |
|
2 | mysql> START TRANSACTION WITH CONSISTENT SNAPSHOT; Query OK, 0 rows affected (0.01 sec) |
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
3 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | +---+---+ 3 rows in set (0.01 sec) |
|
4 | mysql> INSERT INTO t VALUES(4, 1); Query OK, 1 row affected (0.00 sec) 多分ここでスナップショットができていると予想 |
|
5 | mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | | 4 | 1 | +---+---+ 4 rows in set (0.00 sec) |
|
mysql> COMMIT; Query OK, 0 rows affected (0.01 sec) |
||
mysql> SELECT * FROM t; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | | 3 | 1 | +---+---+ 3 rows in set (0.00 sec) ファントムが防がれている |
Lock Waitになる例
No. | session1 | session2 |
---|---|---|
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
|
mysql> SELECT * FROM t WHERE a IN (2, 1) ORDER BY a FOR UPDATE; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | +---+---+ 2 rows in set (0.01 sec) |
mysql> SELECT * FROM t WHERE a IN (2, 1) ORDER BY a FOR UPDATE; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
しかしこの方法では並行性の制御の仕組み(ロックを掛ける順番やロックを行うデータの選択)がアプリケーションのデータモデルに漏れ出していてアプリケーション側で意識することが増えてしまい、またルールで縛るため間違いも起こりやすくなります。
なので、本来であれば、分離レベルをSERIALIZABLEにするのがよいのかなと思います。。。
ただMySQLのSERIALIZABLEは容易にロックがかかります。
- SERIALIZABLEでロックが掛かる例
No. | session1 | session2 |
---|---|---|
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) |
|
mysql> SELECT * FROM t WHERE a IN (2, 1) ORDER BY a FOR UPDATE; +---+---+ | a | b | +---+---+ | 1 | 1 | | 2 | 1 | +---+---+ 2 rows in set (0.01 sec) |
mysql> SELECT * FROM t WHERE a IN (2, 1) ORDER BY a FOR UPDATE; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
PostgreSQLのSERIALIZABLEの分離レベルはSSIがだとどうなるかは気になるところです。
参考
InnoDBの分離レベルによるMySQLのパフォーマンスへの影響 | Yakst
漢(オトコ)のコンピュータ道: InnoDBのREPEATABLE READにおけるLocking Readについての注意点
Rails Developers Meetup 2018 で「MySQL/InnoDB の裏側」を発表しました - あらびき日記
MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.2.7 ネクストキーロックによるファントム問題の回避
トランザクション技術とリカバリとInnoDBパラメータを調べた - たにしきんぐダム
MySQL InnoDB で大きなトランザクションを見つける - ablog
InnoDBのロールバックがあとどれくらいかかるかをなんとなく見積もる | GMOメディア エンジニアブログ
https://15445.courses.cs.cmu.edu/fall2017/slides/18-indexconcurrency.pdf
MySQL の Repeatable Read と RocksDB の楽観的トランザクション解説 | 株式会社インフィニットループ技術ブログ
Java SDKでApache Beam(Dataflow)でGradleを使うサンプル
Java SDKを使ったApache Beamのパイプライン構築については公式ドキュメントなどを見るとMavenを使うことが多いのですが、Gradleを使った例が見当たらなかったので作ってみました。
個人的な感想なのですが、Mavenだと依存関係をpomで定義書くのが辛かったのですが、Gradleのほうが素直に書けてそこだけでもGradleを使うメリットはあるかなあと思っています。
Gradle経由でDataflowにdeployする定義も作っています。
apache-beam-gradle-sample/build.gradle at main · SpringMT/apache-beam-gradle-sample · GitHub
サンプルプロジェクト構成について
pipeline
パイプラインの構築だけに責務があります。
pipelineの中には具体的な処理内容は書かず、エントリーポイントと処理を担当するクラス(ステージ)を定義するだけにしています。
transform
PTransform
を使い、具体的な処理内容を書きます。
ステージに相当する部分となります。
単一責任の原則に則り1クラス1処理とし、できる限り単体テストを書くようにします。
ステージを跨ぐ場合は、 PCollection
を使ってデータを受け渡しを行います。
application
アプリケーションロジックなどをここにまとめます。
ステージを跨ぐデータのクラスを置いたりしています。
参考
Apache BeamでPTransformを使って分割したステップについてテストをする(Java)
簡単なまとめ
- Apache Beamでは
PTransform
PCollection
を使ってパイプラインを小さいステップに分割できる - Apache Beamは分割してステップを簡単にテストできるテスティングフレームワークが整備されている
- パイプラインを細かく分割して、それらのテストを書くことでパイプラインの開発がしやすくなる
Apache Beamでは Pipeline
クラスのオブジェクトがデータ処理のタスク全体を管理しています。
この Pipeline
の中におけるデータ処理は PTransform
を使って処理を複数のステップに分割することが可能です。
PTransform
によって分割されたステップ間における入出力のデータは PCollection
という分散データセットとして受け渡すことが可能です。
Pipeline
は一つの PTransform
で記述することも可能ですが、単一責任の原則に則って一つのステップは一つの役割に分割することでコードの管理がしやすくなります。
ステップの分割を PTransform
と PCollection
で簡単に表現できるのがApache Beamが便利なところかなと思います。(他の分散処理基盤はあんまり触ったことないですが。。。)
やっていることが小さくなると、テスト設計もしやすくなるため、テストを書きたくなるかと思います。
Apache Beamはパイプラインを簡単にテストできるテスティングフレームワークを提供しています。
ここでは、 PTransform
を使った単体テストの書き方の例を示せればと思います。
以降ではJavaでの例のみとなります。
パイプラインの分割
まずはパイプラインの最初の入力と最後の出力をステップとして分けます。
最初の入力はマネージドサービスからの入力( Cloud Pub/Subなど )が多く、ここを分離しておくことで後続のステップを単体でテストしやすくなります。
最初のステップでは特にデータの内容に手を加えず、後続のステップにわたすための PCollection
への簡単な変換にとどめます。
最後の出力もマネージドサービスへの出力( BigQueryなど )が多いため、ここも分離しておきます。
最後の出力も、前段でデータを加工しておき、そのデータを出力するだけのシンプルなステップにします。
なにかしらの入力を PCollection に変換するステップ ↓ 処理したい内容のステップ <- ここのテストを充実させる ↓ 出力ステップ
このようにステップを分け、処理の内容の実体を単体でテストできるようにします。
PTransform
を使ったステップのテストの書き方
Apcahe Beamには TestPipeline
というクラスが用意されています。
TestPipeline (Apache Beam 2.27.0-SNAPSHOT)
このクラスを使って、PTransform
のステップのテストを書きます。
ステップ
まずはサンプルとして Foo
というステップを用意します
@AllArgsConstructor public class Foo extends PTransform<PCollection<String>, PCollection<FooDto>> { @Override public PCollection<FooDto> expand(PCollection<String> input) { return input.apply(ParDo.of(new ParseJson())); } public static class ParseJson extends DoFn<String, FooDto> { @ProcessElement public void processElement(@Element String element, OutputReceiver<FooDto> receiver) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); FooDto[] parsedData = objectMapper.readValue(element, FooDto[].class); for (FooDto d : parsedData) { receiver.output(d); } } } }
PTransform
で受け渡すデータはSerializableである必要があります。
したがってontputのデータ FooDto
は Serializableなクラスとして定義します。
public class FooDto implements Serializable { @JsonProperty("user_id") @NonNull private String userId; @JsonProperty("account_id") @NonNull private Integer accountId; }
テストコード
上記の Foo
のテストは下記のようになります。
class FooTest { @Test public void testFoo() throws Exception { TestPipeline p = TestPipeline.create().enableAbandonedNodeEnforcement(false); PCollection<String> input = p.apply( Create.of( "[" + "{\"user_id\": \"aaa\", \"account_id\": 123}" + "]") .withCoder(StringUtf8Coder.of())); PCollection<FooDto> output = input.apply(new Foo()); PAssert.that(output) .containsInAnyOrder( new FooDto("aaa", 123); p.run().waitUntilFinish(); } }
TestPipeline
はテスト用のパイプラインを生成します。
ここで生成したパイプラインにテスト対象のステップをapplyすることで、このステップについてのみテストが可能になります。
PAssert
は PCollection
の内容に関するアサーションを提供しています。
PAssert (Apache Beam 2.27.0-SNAPSHOT)
これらを使うことで、処理結果のoutputを簡単に確認することができます。
テストを作ることで、手元で動作確認をしながら開発を進めることが可能です。
まとめ
Apache Beamのパイプラインはステップを適切に分割しそれぞれのテストを書くことで格段に開発、メンテがしやすくなります。
ぜひテストも書きながらApache Beamのパイプラインを構築してみてください。
サービスを作る
雑多にまとめておく
MVPとかPocとか
InVisionやFigmaで作る。
それで、プロダクトのコアコンセプトのテスト、仮説検証できるところまでやる。
最初の文化
なぜこのサービスをやるのかを最初からチームで話す
すべてのタイミングで見直すようにする。
最初のチーム
メンバー
少人数でチームを作る。5〜6人くらいがMax。
人を増やして対応するのではなく、作業効率を上げること、やることの取捨選択によってチーム規模を維持する。
リリース後に余裕が出てきたら、改めてメンバー構成を考える。
QAメンバー
QAをメインで担当する人は最初からチームに入れる。
品質をどう作り上げていくかを最初からチームで考える。
テスト観点から仕様の漏れを突っ込む人
CI
最初からやる。 CIのパイプラインを作り、その後に最初のQAをする。
最初のリリース
なんでもいいからQAチームへ受け渡し確認してもらう。
Hello, World!
だけ表示される何かだけでもよい
セキュリティ
最初からやる。 専門家がいるなら早めに相談。
評価
目標設定などはチーム全員でやる。 管理方法はなんでもいいけど、チーム全員でやるのが重要。
ユビキタス言語
scrapboxで管理が相性が良さそうと思いつつやってない。
ドメイン
ライブラリとか使わずにPOROとかPOJOみたいなもので作り上げる。
www2.slideshare.net
モノレポ
最初はまとめておく。
分割はあとで
DB
難しいが、DB分割は最初のほうでしっかりやっておく。
ツール群
お金使って便利なものを積極的に使う
IDEとか。
文化と暗黙知と属人性
属人性は排除していく。
ドキュメントで解決する部分としない部分がある。
解決しない部分として、根底に流れるコンセプトは一回書いただけでは浸透しないとかそういう部分。
これはちゃんと資料に起こしつつ、繰り返し事ある毎に言及し、文化として醸成していく。
暗黙知として知識ベースのものは、ちゃんとドキュメント化する
最初の一歩
仮説段階
開発者として考えることはドメインモデルの構築にどこまで時間を割けるか
プロトを作る工数に意外と時間が取られる その時間をドメインモデル構築に当てたほうが軸が定まってサービス開発がうまくいくのでは?という感触がある
読み切れていない資料
ドメイン関連が多い
docker build時のキャッシュの判定
メモ書き
スタートはdispatchersから https://github.com/moby/moby/blob/cf0ce96eb129ebcc7d07f0f47a8683c16f228c7d/builder/dockerfile/dispatchers.go
ADD や COPY
createCopyInstructionでキャッシュなどの判定も含めて行う
performCopyがファイルの変更を行う本体 https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/internals.go#L160
createCopyInstruction
getCopyInfosForSourcePathsからgetCopyInfoForSourcePathがよばれて、copyInfoを作成する。 https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/copy.go#L134-L151
getCopyInfoForSourcePathでもろもろ生成される。 https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/copy.go#L40-L45
キャッシュの判定で使われるハッシュ値はここで生成される
moby/versioning.go at 46cdcd206c56172b95ba5c77b827a722dab426c5 · moby/moby · GitHub
performCopy
キャッシュの判定には上記のcreateCopyInstructionで作られたcopyInstructionを元に行う
probeCacheが担う https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/internals.go#L428-L437
でキャッシュがあればそれを使い、そうでなければ新しくlayerを作る。
"プログラミングの基礎" を読んだ
2ヶ月かけてプログラミングの基礎という本を読みました。
この本を読んだきっかけ
この本は2013年くらいに @kis におすすめされた本でした。
購入日は2013/05/29
買ってすぐ読んだ記憶があるのですが、2章くらいまでで読むのをやめた気がします。
その後、数回本を開いた記憶があるのですが、どの場合も途中で読むのを止めていました。
で、2020年になってLeetCodeなどをやり始めたのですが、解法やアルゴリズムは理解できるがプログラムに落とし込めない(特に再帰が想像できない)という課題がでてきました。
問題に一対一対応させて覚えていくという力技よりは、もっと根本となるコードを書く能力を高めないと先がないなあと思って、色々本を読んでみました。
最初はSICPとかを眺めて見ましたが、理解が進まず挫折。。。
その後、SICPより読みやすくてなにかないかと探してみたところ、再度この本を取ることにしました。
きっかけもやっぱり @kis
(タイトルは若干あおり気味ですが、、、)
このブログを読んで改めてプログラミングの基礎を手にとりました。
読み進め方
今までなぜ読みきれなかったかを振り返ってみると、単に読んでいるだけだと何が問題なのか実感が得にくく、だんだんモチベーションが下がるという感じだった気がしました。
そこで、今回は下記を実践しました。
- 演習は全部やる
- 時間を決めてその時間を集中して手を動かしながら本を読む
今は夜に時間が取れないので、業務開始前 9:00〜10:00 を確保し、その間に手を動かしながらやっていきました。
それで、大体2ヶ月で読み切りました。
この本から学んだこと
課題に思っていたプログラムに落とし込めない問題(特に再帰)に関しては、自分が何がわかっていなかったかがわかるようになりました。
- 再帰的なデータ構造(型)を知り理解できた(気がする)
- 再帰的なデータ構造(型)とプログラムの構造が対応できることを理解できた(気がする)
- 構造に従わない再帰であっても、部分問題に分割すること、停止性を考えることでプログラムに落とし込みができるようになった
データ構造が重要なのはなんとなく思っていたのですが、この本を読むまではデータ構造をプログラムの構造に対応させる方法がわからなかったからだと気が付きました。
この本ではOCamlを使っていくのですが、パターンマッチの力をやっと理解できました。
この本の良かった点
- 日本語がわかりやすい
- 章の構成がよく、読み進めやすい
- 説明が丁寧で理解しやすい
- 単語の説明がすごく平易な表現になっていてわかりやすい(多相性 -> どのような型でもよい性質 などなど)
- 演習の答えが全て揃っている
次に
同じく @kis に勧められていたプログラミング言語の基礎概念を読みつつ、再度プログラムを書く練習をしていきます。
LeetCodeはOCamlサポートしてないけど、他の言語でトライしてみる予定です。
雑感
もっと早く読んでおけばよかったと思う反面、課題がないまま読んでもまた読むのを止めると思うのでこのタイミングでよかったんだろうと思っています。
自分はCSの勉強を大学でしてこなかったで、こういう本があると自力でCSの勉強をする取っ掛かりとしてはとてもいいのではないかなと思いました。
2020/07から2020/10までのGKEのリリースノートからGKEの機能で気になる部分をまとめる
セキュリティ関連は書いてないです。
kubernetesのアップデート内容には触れていません。
July 2, 2020
https://cloud.google.com/kubernetes-engine/docs/release-notes#july_2_2020
NodeLocal DNSCacheがGA
Setting up NodeLocal DNSCache | Kubernetes Engine Documentation
既存のクラスタでもonにできる
名前解決が速くなるのonにしておくのがよいかと
GKE Node System ConfigurationがGA
Nodeの設定を指定できる機能
Linux kernel parameters (sysctls) や kubelet configsもある程度設定できる
net.core.somaxconn
とかも設定できる
Nodeを作成するときに、gcloud コマンド経由でファイル(JSON or YAML)を指定する
July 17, 2020
https://cloud.google.com/kubernetes-engine/docs/release-notes#july_17_2020
SSL Policiesがexternal Ingress and multi-cluster Ingressでβ 、Custom health checks が external, internal, and multi-cluster Ingressでβ(1.17.6-gke.11以上かな)
下記のリンクに全部まとまっている
https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-features
SSLPolicyを予めGCP上で作成しておき、それをFrontendConfigで設定して、それをIngressの設定で指定する
https://cloud.google.com/load-balancing/docs/ssl-policies-concepts
apiVersion: networking.gke.io/v1beta1 kind: FrontendConfig metadata: name: my-frontend-config spec: sslPolicy: gke-ingress-ssl-policy # apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: annotations: networking.gke.io/v1beta1.FrontendConfig: "my-frontend-config"
health checkのほうはBackendConfigで設定する
BackendConfig CRDがGKE 1.16-gke.3以上でGA
IAP, timeouts, affinity, user-defined request headerなどの機能が追加されている
IAPもか
GKE バージョン 1.16-gke.3 以降を使用している場合は、cloud.google.com/backend-config アノテーションを使うようにすること。
GKE 1.17.6-gke.7以上のクラスタで、新規 Serviceをデプロイすると、NEGを使うContainer-native Ingress が デフォルトに
cloud.google.com/neg: '{"ingress": true}'
のannotaionがデフォルトでつくので、自分で書く必要がなくなる
あくまで 新規 Serviceのみなので要注意
https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#container-native_load_balancing
CMEKがGKEでGA
July 28, 2020 (R25)
https://cloud.google.com/kubernetes-engine/docs/release-notes#july_28_2020_r25
デフォルトのマシンタイプがE2に
https://cloud.google.com/compute/docs/machine-types?hl=ja#machine_types
Google Cloudに安価な汎用仮想マシン「E2ファミリー」を追加 | TechCrunch Japan
E2は継続利用割引がないので注意してください。
https://cloud.google.com/compute/docs/machine-types?hl=ja#machine_type_comparison
August 21, 2020
https://cloud.google.com/kubernetes-engine/docs/release-notes#august_21_2020
Internal load balancer Serviceが GKE 1.17.9-gke.600以上でGA
https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing
type: LoadBalancer
のServiceのannotationで cloud.google.com/load-balancer-type: "Internal"
で使えるようになる
この internal load balancer ServiceではGlobal access と configurable subnets が使える
https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing#global_access
https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing#lb_subnet
リージョンまたいでinternal load balancer Serviceが使える
GKE versions 1.17.9-gke.600以上で Dataplane V2がβ
Dataplane V2があれば、Network policy loggingも使える
https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy-logging
RFC 1918以外のプライベートIPアドレスが利用可能に
https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips#enable_reserved_ip_ranges
使える範囲は下記を参照
https://cloud.google.com/vpc/docs/vpc#valid-ranges
August 28, 2020
https://cloud.google.com/kubernetes-engine/docs/release-notes#august_28_2020
どのリージョンからもprivate clusterのマスター( Contol plane )へアクセス可能にする
デフォルトは同じリージョンからしかアクセスできない
https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#cp-global-access
September 8, 2020
https://cloud.google.com/kubernetes-engine/docs/release-notes#september_8_2020
Error状態のclusterを自動的に削除する
Errorの定義は下記を参照
https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.locations.clusters#status
TaintBasedEvictionsがGKEの 1.18 でGA
あれ、このタイミングなのか
Kubernetes 1.13: SIG Scheduling の変更内容 - チェシャ猫の消滅定理
September 25, 2020 (R31)
https://cloud.google.com/kubernetes-engine/docs/release-notes#september_25_2020_r31
Node Auto-Provisioningで CMEK、Secure Boot and Integrity Monitoring、Boot disk type and sizeをデフォルトでセットしてくれるようになった
今まではセットしてくれてなかったってことか。。
October 02, 2020 (R32)
https://cloud.google.com/kubernetes-engine/docs/release-notes#october_02_2020_r32
GKE 1.18以上でオートスケールのプロファイルのロジックが optimize-utilization
になる
クラスタをより積極的にスケールダウンしてくれるようになる。
リソースの使用率を最大化するぽい。
https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler#autoscaling_profiles